diff options
255 files changed, 5302 insertions, 1667 deletions
diff --git a/api/current.txt b/api/current.txt index 31acb35..653e25a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2477,6 +2477,11 @@ package android.animation { method public void setPropertyName(java.lang.String); } + public class RectEvaluator implements android.animation.TypeEvaluator { + ctor public RectEvaluator(); + method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect); + } + public class TimeAnimator extends android.animation.ValueAnimator { ctor public TimeAnimator(); method public void setTimeListener(android.animation.TimeAnimator.TimeListener); @@ -4851,7 +4856,7 @@ package android.bluetooth { } public final class BluetoothDevice implements android.os.Parcelable { - method public android.bluetooth.BluetoothGatt connectGattServer(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback); + method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback); method public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException; method public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException; method public int describeContents(); @@ -4860,6 +4865,7 @@ package android.bluetooth { method public android.bluetooth.BluetoothClass getBluetoothClass(); method public int getBondState(); method public java.lang.String getName(); + method public int getType(); method public android.os.ParcelUuid[] getUuids(); method public void writeToParcel(android.os.Parcel, int); field public static final java.lang.String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED"; @@ -4874,6 +4880,10 @@ package android.bluetooth { field public static final int BOND_BONDING = 11; // 0xb field public static final int BOND_NONE = 10; // 0xa field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DEVICE_TYPE_CLASSIC = 1; // 0x1 + field public static final int DEVICE_TYPE_DUAL = 3; // 0x3 + field public static final int DEVICE_TYPE_LE = 2; // 0x2 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int ERROR = -2147483648; // 0x80000000 field public static final java.lang.String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"; field public static final java.lang.String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; @@ -4887,11 +4897,14 @@ package android.bluetooth { public final class BluetoothGatt implements android.bluetooth.BluetoothProfile { method public void abortReliableWrite(android.bluetooth.BluetoothDevice); method public boolean beginReliableWrite(); + method public void close(); + method public boolean connect(); method public void disconnect(); method public boolean discoverServices(); method public boolean executeReliableWrite(); method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public android.bluetooth.BluetoothDevice getDevice(); method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); method public android.bluetooth.BluetoothGattService getService(java.util.UUID); method public java.util.List<android.bluetooth.BluetoothGattService> getServices(); @@ -4914,15 +4927,15 @@ package android.bluetooth { public abstract class BluetoothGattCallback { ctor public BluetoothGattCallback(); - method public void onCharacteristicChanged(android.bluetooth.BluetoothGattCharacteristic); - method public void onCharacteristicRead(android.bluetooth.BluetoothGattCharacteristic, int); - method public void onCharacteristicWrite(android.bluetooth.BluetoothGattCharacteristic, int); - method public void onConnectionStateChange(android.bluetooth.BluetoothDevice, int, int); - method public void onDescriptorRead(android.bluetooth.BluetoothGattDescriptor, int); - method public void onDescriptorWrite(android.bluetooth.BluetoothGattDescriptor, int); - method public void onReadRemoteRssi(android.bluetooth.BluetoothDevice, int, int); - method public void onReliableWriteCompleted(android.bluetooth.BluetoothDevice, int); - method public void onServicesDiscovered(android.bluetooth.BluetoothDevice, int); + method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic); + method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int); + method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int); + method public void onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int); + method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int); + method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int); + method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int); + method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int); + method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int); } public class BluetoothGattCharacteristic { @@ -16509,7 +16522,7 @@ package android.os { method public abstract android.os.IBinder asBinder(); } - public class Looper { + public final class Looper { method public void dump(android.util.Printer, java.lang.String); method public static android.os.Looper getMainLooper(); method public java.lang.Thread getThread(); @@ -16564,9 +16577,9 @@ package android.os { field public int what; } - public class MessageQueue { - method public final void addIdleHandler(android.os.MessageQueue.IdleHandler); - method public final void removeIdleHandler(android.os.MessageQueue.IdleHandler); + public final class MessageQueue { + method public void addIdleHandler(android.os.MessageQueue.IdleHandler); + method public void removeIdleHandler(android.os.MessageQueue.IdleHandler); } public static abstract interface MessageQueue.IdleHandler { @@ -16940,6 +16953,7 @@ package android.os { method public android.os.StrictMode.VmPolicy build(); method public android.os.StrictMode.VmPolicy.Builder detectActivityLeaks(); method public android.os.StrictMode.VmPolicy.Builder detectAll(); + method public android.os.StrictMode.VmPolicy.Builder detectFileUriExposure(); method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects(); method public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects(); method public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects(); @@ -20756,6 +20770,8 @@ package android.security { method public static android.content.Intent createInstallIntent(); method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; + method public static boolean isBoundKeyAlgorithm(java.lang.String); + method public static boolean isKeyAlgorithmSupported(java.lang.String); field public static final java.lang.String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED"; field public static final java.lang.String EXTRA_CERTIFICATE = "CERT"; field public static final java.lang.String EXTRA_NAME = "name"; @@ -21446,6 +21462,7 @@ package android.telephony { method public int getDataState(); method public java.lang.String getDeviceId(); method public java.lang.String getDeviceSoftwareVersion(); + method public java.lang.String getGroupIdLevel1(); method public java.lang.String getLine1Number(); method public java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); method public java.lang.String getNetworkCountryIso(); @@ -22803,6 +22820,7 @@ package android.text.format { method public static java.lang.CharSequence format(java.lang.CharSequence, long); method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Date); method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Calendar); + method public static java.lang.String getBestDateTimePattern(java.util.Locale, java.lang.String); method public static java.text.DateFormat getDateFormat(android.content.Context); method public static char[] getDateFormatOrder(android.content.Context); method public static java.text.DateFormat getLongDateFormat(android.content.Context); diff --git a/core/java/android/animation/RectEvaluator.java b/core/java/android/animation/RectEvaluator.java new file mode 100644 index 0000000..10932bb --- /dev/null +++ b/core/java/android/animation/RectEvaluator.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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.animation; + +import android.graphics.Rect; + +/** + * This evaluator can be used to perform type interpolation between <code>int</code> values. + */ +public class RectEvaluator implements TypeEvaluator<Rect> { + + /** + * This function returns the result of linearly interpolating the start and + * end Rect values, with <code>fraction</code> representing the proportion + * between the start and end values. The calculation is a simple parametric + * calculation on each of the separate components in the Rect objects + * (left, top, right, and bottom). + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start Rect + * @param endValue The end Rect + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + @Override + public Rect evaluate(float fraction, Rect startValue, Rect endValue) { + return new Rect(startValue.left + (int)((endValue.left - startValue.left) * fraction), + startValue.top + (int)((endValue.top - startValue.top) * fraction), + startValue.right + (int)((endValue.right - startValue.right) * fraction), + startValue.bottom + (int)((endValue.bottom - startValue.bottom) * fraction)); + } +} diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 87c2d8c..31074e2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3513,7 +3513,8 @@ public class Activity extends ContextThemeWrapper try { String resolvedType = null; if (fillInIntent != null) { - fillInIntent.setAllowFds(false); + fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.prepareToLeaveProcess(); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -3738,7 +3739,8 @@ public class Activity extends ContextThemeWrapper if (mParent == null) { int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; try { - intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); result = ActivityManagerNative.getDefault() .startActivity(mMainThread.getApplicationThread(), getBasePackageName(), intent, intent.resolveTypeIfNeeded(getContentResolver()), @@ -3808,7 +3810,8 @@ public class Activity extends ContextThemeWrapper public boolean startNextMatchingActivity(Intent intent, Bundle options) { if (mParent == null) { try { - intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); return ActivityManagerNative.getDefault() .startNextMatchingActivity(mToken, intent, options); } catch (RemoteException e) { @@ -4162,7 +4165,7 @@ public class Activity extends ContextThemeWrapper if (false) Log.v(TAG, "Finishing self: token=" + mToken); try { if (resultData != null) { - resultData.setAllowFds(false); + resultData.prepareToLeaveProcess(); } if (ActivityManagerNative.getDefault() .finishActivity(mToken, resultCode, resultData)) { @@ -4314,7 +4317,7 @@ public class Activity extends ContextThemeWrapper int flags) { String packageName = getPackageName(); try { - data.setAllowFds(false); + data.prepareToLeaveProcess(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, @@ -4993,9 +4996,10 @@ public class Activity extends ContextThemeWrapper resultData = mResultData; } if (resultData != null) { - resultData.setAllowFds(false); + resultData.prepareToLeaveProcess(); } try { + upIntent.prepareToLeaveProcess(); return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent, resultCode, resultData); } catch (RemoteException e) { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 459e49c..9bf8830 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1020,7 +1020,8 @@ class ContextImpl extends Context { try { String resolvedType = null; if (fillInIntent != null) { - fillInIntent.setAllowFds(false); + fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.prepareToLeaveProcess(); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -1040,7 +1041,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false, @@ -1054,7 +1055,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, @@ -1068,7 +1069,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermission, appOp, false, false, @@ -1083,7 +1084,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, true, false, @@ -1126,7 +1127,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermission, appOp, @@ -1139,7 +1140,7 @@ class ContextImpl extends Context { public void sendBroadcastAsUser(Intent intent, UserHandle user) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false, user.getIdentifier()); @@ -1152,7 +1153,7 @@ class ContextImpl extends Context { String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, false, false, @@ -1184,7 +1185,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermission, @@ -1198,7 +1199,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true, @@ -1232,7 +1233,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, @@ -1249,7 +1250,7 @@ class ContextImpl extends Context { intent.setDataAndType(intent.getData(), resolvedType); } try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().unbroadcastIntent( mMainThread.getApplicationThread(), intent, getUserId()); } catch (RemoteException e) { @@ -1260,7 +1261,7 @@ class ContextImpl extends Context { public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true, user.getIdentifier()); @@ -1292,7 +1293,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, @@ -1309,7 +1310,7 @@ class ContextImpl extends Context { intent.setDataAndType(intent.getData(), resolvedType); } try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().unbroadcastIntent( mMainThread.getApplicationThread(), intent, user.getIdentifier()); } catch (RemoteException e) { @@ -1393,7 +1394,7 @@ class ContextImpl extends Context { @Override public ComponentName startServiceAsUser(Intent service, UserHandle user) { try { - service.setAllowFds(false); + service.prepareToLeaveProcess(); ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier()); @@ -1417,7 +1418,7 @@ class ContextImpl extends Context { @Override public boolean stopServiceAsUser(Intent service, UserHandle user) { try { - service.setAllowFds(false); + service.prepareToLeaveProcess(); int res = ActivityManagerNative.getDefault().stopService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier()); @@ -1459,7 +1460,7 @@ class ContextImpl extends Context { < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } - service.setAllowFds(false); + service.prepareToLeaveProcess(); int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e7bf305..e0dfb25 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1410,8 +1410,8 @@ public class Instrumentation { } } try { - intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), @@ -1467,7 +1467,8 @@ public class Instrumentation { try { String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { - intents[i].setAllowFds(false); + intents[i].migrateExtraStreamToClipData(); + intents[i].prepareToLeaveProcess(); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -1526,8 +1527,8 @@ public class Instrumentation { } } try { - intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), @@ -1586,8 +1587,8 @@ public class Instrumentation { } } try { - intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivityAsUser(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 5e69128..dbafc78 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.UserHandle; import android.util.Log; @@ -126,6 +127,9 @@ public class NotificationManager String pkg = mContext.getPackageName(); if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); + if (StrictMode.vmFileUriExposureEnabled()) { + notification.sound.checkFileUriExposed("Notification.sound"); + } } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { @@ -148,6 +152,9 @@ public class NotificationManager String pkg = mContext.getPackageName(); if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); + if (StrictMode.vmFileUriExposureEnabled()) { + notification.sound.checkFileUriExposed("Notification.sound"); + } } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 20114cc..25c790f 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -260,8 +260,8 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, @@ -285,8 +285,8 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, @@ -401,7 +401,8 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { - intents[i].setAllowFds(false); + intents[i].migrateExtraStreamToClipData(); + intents[i].prepareToLeaveProcess(); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } try { @@ -426,7 +427,8 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { - intents[i].setAllowFds(false); + intents[i].migrateExtraStreamToClipData(); + intents[i].prepareToLeaveProcess(); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } try { @@ -482,7 +484,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_BROADCAST, packageName, @@ -526,7 +528,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.setAllowFds(false); + intent.prepareToLeaveProcess(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_SERVICE, packageName, diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 83e95ca..3c1ec90 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -262,6 +262,26 @@ public final class BluetoothDevice implements Parcelable { public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; /** + * Bluetooth device type, Unknown + */ + public static final int DEVICE_TYPE_UNKNOWN = 0; + + /** + * Bluetooth device type, Classic - BR/EDR devices + */ + public static final int DEVICE_TYPE_CLASSIC = 1; + + /** + * Bluetooth device type, Low Energy - LE-only + */ + public static final int DEVICE_TYPE_LE = 2; + + /** + * Bluetooth device type, Dual Mode - BR/EDR/LE + */ + public static final int DEVICE_TYPE_DUAL = 3; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -602,6 +622,26 @@ public final class BluetoothDevice implements Parcelable { } /** + * Get the Bluetooth device type of the remote device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} + * {@link #DEVICE_TYPE_DUAL}. + * {@link #DEVICE_TYPE_UNKNOWN} if it's not available + */ + public int getType() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Remote Device type"); + return DEVICE_TYPE_UNKNOWN; + } + try { + return sService.getRemoteType(this); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return DEVICE_TYPE_UNKNOWN; + } + + /** * Get the Bluetooth alias of the remote device. * <p>Alias is the locally modified name of a remote device. * @@ -1139,8 +1179,8 @@ public final class BluetoothDevice implements Parcelable { * device becomes available (true). * @throws IllegalArgumentException if callback is null */ - public BluetoothGatt connectGattServer(Context context, boolean autoConnect, - BluetoothGattCallback callback) { + public BluetoothGatt connectGatt(Context context, boolean autoConnect, + BluetoothGattCallback callback) { // TODO(Bluetooth) check whether platform support BLE // Do the check here or in GattServer? BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index f9ce6ea..bffe64b 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -43,7 +43,7 @@ import java.util.UUID; * with Bluetooth Smart or Smart Ready devices. * * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback} - * and call {@link BluetoothDevice#connectGattServer} to get a instance of this class. + * and call {@link BluetoothDevice#connectGatt} to get a instance of this class. * GATT capable devices can be discovered using the Bluetooth device discovery or BLE * scan process. */ @@ -66,6 +66,7 @@ public final class BluetoothGatt implements BluetoothProfile { private static final int CONN_STATE_CONNECTING = 1; private static final int CONN_STATE_CONNECTED = 2; private static final int CONN_STATE_DISCONNECTING = 3; + private static final int CONN_STATE_CLOSED = 4; private List<BluetoothGattService> mServices; @@ -135,7 +136,7 @@ public final class BluetoothGatt implements BluetoothProfile { } mClientIf = clientIf; if (status != GATT_SUCCESS) { - mCallback.onConnectionStateChange(mDevice, GATT_FAILURE, + mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE, BluetoothProfile.STATE_DISCONNECTED); synchronized(mStateLock) { mConnState = CONN_STATE_IDLE; @@ -164,7 +165,7 @@ public final class BluetoothGatt implements BluetoothProfile { int profileState = connected ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED; try { - mCallback.onConnectionStateChange(mDevice, status, profileState); + mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -291,7 +292,7 @@ public final class BluetoothGatt implements BluetoothProfile { return; } try { - mCallback.onServicesDiscovered(mDevice, status); + mCallback.onServicesDiscovered(BluetoothGatt.this, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -338,7 +339,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (status == 0) characteristic.setValue(value); try { - mCallback.onCharacteristicRead(characteristic, status); + mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -384,7 +385,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAuthRetry = false; try { - mCallback.onCharacteristicWrite(characteristic, status); + mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -415,7 +416,7 @@ public final class BluetoothGatt implements BluetoothProfile { characteristic.setValue(value); try { - mCallback.onCharacteristicChanged(characteristic); + mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -464,7 +465,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAuthRetry = true; try { - mCallback.onDescriptorRead(descriptor, status); + mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -512,7 +513,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAuthRetry = false; try { - mCallback.onDescriptorWrite(descriptor, status); + mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -529,7 +530,7 @@ public final class BluetoothGatt implements BluetoothProfile { return; } try { - mCallback.onReliableWriteCompleted(mDevice, status); + mCallback.onReliableWriteCompleted(BluetoothGatt.this, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -546,7 +547,7 @@ public final class BluetoothGatt implements BluetoothProfile { return; } try { - mCallback.onReadRemoteRssi(mDevice, rssi, status); + mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } @@ -563,12 +564,13 @@ public final class BluetoothGatt implements BluetoothProfile { } /** - * Close the connection to the gatt service. + * Close this Bluetooth GATT client. */ - /*package*/ void close() { + public void close() { if (DBG) Log.d(TAG, "close()"); unregisterApp(); + mConnState = CONN_STATE_CLOSED; } /** @@ -694,7 +696,35 @@ public final class BluetoothGatt implements BluetoothProfile { } catch (RemoteException e) { Log.e(TAG,"",e); } - // TBD deregister after conneciton is torn down + } + + /** + * Connect back to remote device. + * + * <p>This method is used to re-connect to a remote device after the + * connection has been dropped. If the device is not in range, the + * re-connection will be triggered once the device is back in range. + * + * @return true, if the connection attempt was initiated successfully + */ + public boolean connect() { + try { + mService.clientConnect(mClientIf, mDevice.getAddress(), + false); // autoConnect is inverse of "isDirect" + return true; + } catch (RemoteException e) { + Log.e(TAG,"",e); + return false; + } + } + + /** + * Return the remote bluetooth device this GATT client targets to + * + * @return remote bluetooth device + */ + public BluetoothDevice getDevice() { + return mDevice; } /** diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java index c9e5fea..2259c1e 100644 --- a/core/java/android/bluetooth/BluetoothGattCallback.java +++ b/core/java/android/bluetooth/BluetoothGattCallback.java @@ -16,23 +16,22 @@ package android.bluetooth; -import android.bluetooth.BluetoothDevice; - /** * This abstract class is used to implement {@link BluetoothGatt} callbacks. */ public abstract class BluetoothGattCallback { /** - * Callback indicating when a remote device has been connected or disconnected. + * Callback indicating when GATT client has connected/disconnected to/from a remote + * GATT server. * - * @param device Remote device that has been connected or disconnected. + * @param gatt GATT client * @param status Status of the connect or disconnect operation. * @param newState Returns the new connection state. Can be one of * {@link BluetoothProfile#STATE_DISCONNECTED} or * {@link BluetoothProfile#STATE_CONNECTED} */ - public void onConnectionStateChange(BluetoothDevice device, int status, + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { } @@ -40,22 +39,23 @@ public abstract class BluetoothGattCallback { * Callback invoked when the list of remote services, characteristics and descriptors * for the remote device have been updated, ie new services have been discovered. * - * @param device Remote device + * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices} * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device * has been explored successfully. */ - public void onServicesDiscovered(BluetoothDevice device, int status) { + public void onServicesDiscovered(BluetoothGatt gatt, int status) { } /** * Callback reporting the result of a characteristic read operation. * + * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic} * @param characteristic Characteristic that was read from the associated * remote device. * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation * was completed successfully. */ - public void onCharacteristicRead(BluetoothGattCharacteristic characteristic, + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @@ -68,52 +68,59 @@ public abstract class BluetoothGattCallback { * value to the desired value to be written. If the values don't match, * the application must abort the reliable write transaction. * + * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic} * @param characteristic Characteristic that was written to the associated * remote device. * @param status The result of the write operation */ - public void onCharacteristicWrite(BluetoothGattCharacteristic characteristic, - int status) { + public void onCharacteristicWrite(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { } /** * Callback triggered as a result of a remote characteristic notification. * + * @param gatt GATT client the characteristic is associated with * @param characteristic Characteristic that has been updated as a result * of a remote notification event. */ - public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) { + public void onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { } /** * Callback reporting the result of a descriptor read operation. * + * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} * @param descriptor Descriptor that was read from the associated * remote device. * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation * was completed successfully */ - public void onDescriptorRead(BluetoothGattDescriptor descriptor, int status) { + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, + int status) { } /** * Callback indicating the result of a descriptor write operation. * + * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor} * @param descriptor Descriptor that was writte to the associated * remote device. * @param status The result of the write operation */ - public void onDescriptorWrite(BluetoothGattDescriptor descriptor, int status) { + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, + int status) { } /** * Callback invoked when a reliable write transaction has been completed. * - * @param device Remote device + * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite} * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write * transaction was executed successfully */ - public void onReliableWriteCompleted(BluetoothDevice device, int status) { + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { } /** @@ -122,10 +129,10 @@ public abstract class BluetoothGattCallback { * This callback is triggered in response to the * {@link BluetoothGatt#readRemoteRssi} function. * - * @param device Identifies the remote device + * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi} * @param rssi The RSSI value for the remote device * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully */ - public void onReadRemoteRssi(BluetoothDevice device, int rssi, int status) { + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { } } diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index d63d97e..033f079 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -22,6 +22,10 @@ import java.util.UUID; /** * Represents a Bluetooth GATT Characteristic + * + * <p>A GATT characteristic is a basic data element used to construct a GATT service, + * {@link BluetoothGattService}. The characteristic contains a value as well as + * additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}. */ public class BluetoothGattCharacteristic { diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java index 6ba2db7..1cd6878 100644 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java @@ -20,6 +20,10 @@ import java.util.UUID; /** * Represents a Bluetooth GATT Descriptor + * + * <p> GATT Descriptors contain additional information and attributes of a GATT + * characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe + * the characteristic's features or to control certain behaviours of the characteristic. */ public class BluetoothGattDescriptor { diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index d1f4b82..644c619 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -554,9 +554,10 @@ public final class BluetoothGattServer implements BluetoothProfile { List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors(); for (BluetoothGattDescriptor descriptor: descriptors) { + permission = ((characteristic.getKeySize() - 7) << 12) + + descriptor.getPermissions(); mService.addDescriptor(mServerIf, - new ParcelUuid(descriptor.getUuid()), - descriptor.getPermissions()); + new ParcelUuid(descriptor.getUuid()), permission); } } diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java index c3b3cfe..39a435b 100644 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ b/core/java/android/bluetooth/BluetoothGattService.java @@ -23,6 +23,9 @@ import java.util.UUID; /** * Represents a Bluetooth GATT Service + * + * <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic}, + * as well as referenced services. */ public class BluetoothGattService { diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index d016c26..80806f9 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -60,6 +60,7 @@ interface IBluetooth int getBondState(in BluetoothDevice device); String getRemoteName(in BluetoothDevice device); + int getRemoteType(in BluetoothDevice device); String getRemoteAlias(in BluetoothDevice device); boolean setRemoteAlias(in BluetoothDevice device, in String name); int getRemoteClass(in BluetoothDevice device); diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 4f42d50..9a32fdf 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -520,7 +520,7 @@ public abstract class BroadcastReceiver { IActivityManager am = ActivityManagerNative.getDefault(); IBinder binder = null; try { - service.setAllowFds(false); + service.prepareToLeaveProcess(); binder = am.peekService(service, service.resolveTypeIfNeeded( myContext.getContentResolver())); } catch (RemoteException e) { diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 88f1a3d..50c4fed 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.os.StrictMode; import android.text.Html; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -790,6 +791,24 @@ public class ClipData implements Parcelable { return mItems.get(index); } + /** + * Prepare this {@link ClipData} to leave an app process. + * + * @hide + */ + public void prepareToLeaveProcess() { + final int size = mItems.size(); + for (int i = 0; i < size; i++) { + final Item item = mItems.get(i); + if (item.mIntent != null) { + item.mIntent.prepareToLeaveProcess(); + } + if (item.mUri != null && StrictMode.vmFileUriExposureEnabled()) { + item.mUri.checkFileUriExposed("ClipData.Item.getUri()"); + } + } + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 88a4229..69f9d4a 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -22,6 +22,7 @@ import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; import android.os.ServiceManager; +import android.os.StrictMode; import android.util.Log; import java.util.ArrayList; @@ -118,6 +119,9 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public void setPrimaryClip(ClipData clip) { try { + if (clip != null) { + clip.prepareToLeaveProcess(); + } getService().setPrimaryClip(clip, mContext.getBasePackageName()); } catch (RemoteException e) { } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 0efd6b0..97ad7dd 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.StrictMode; import android.util.AttributeSet; import android.util.Log; @@ -2578,6 +2579,14 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_BRIGHTNESS_DIALOG = "android.intent.action.SHOW_BRIGHTNESS_DIALOG"; + /** + * Broadcast Action: A global button was pressed. Includes a single + * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that + * caused the broadcast. + * @hide + */ + public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -6958,6 +6967,32 @@ public class Intent implements Parcelable, Cloneable { } /** + * Prepare this {@link Intent} to leave an app process. + * + * @hide + */ + public void prepareToLeaveProcess() { + setAllowFds(false); + + if (mSelector != null) { + mSelector.prepareToLeaveProcess(); + } + if (mClipData != null) { + mClipData.prepareToLeaveProcess(); + } + + if (mData != null && StrictMode.vmFileUriExposureEnabled()) { + // There are several ACTION_MEDIA_* broadcasts that send file:// + // Uris, so only check common actions. + if (ACTION_VIEW.equals(mAction) || + ACTION_EDIT.equals(mAction) || + ACTION_ATTACH_DATA.equals(mAction)) { + mData.checkFileUriExposed("Intent.getData()"); + } + } + } + + /** * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested * intents in {@link #ACTION_CHOOSER}. diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java index 75505bc..409b5ae 100644 --- a/core/java/android/content/pm/ManifestDigest.java +++ b/core/java/android/content/pm/ManifestDigest.java @@ -18,10 +18,17 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; -import android.util.Base64; - +import android.util.Slog; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import java.util.jar.Attributes; + +import libcore.io.IoUtils; /** * Represents the manifest digest for a package. This is suitable for comparison @@ -30,17 +37,17 @@ import java.util.jar.Attributes; * @hide */ public class ManifestDigest implements Parcelable { + private static final String TAG = "ManifestDigest"; + /** The digest of the manifest in our preferred order. */ private final byte[] mDigest; - /** Digest field names to look for in preferred order. */ - private static final String[] DIGEST_TYPES = { - "SHA1-Digest", "SHA-Digest", "MD5-Digest", - }; - /** What we print out first when toString() is called. */ private static final String TO_STRING_PREFIX = "ManifestDigest {mDigest="; + /** Digest algorithm to use. */ + private static final String DIGEST_ALGORITHM = "SHA-256"; + ManifestDigest(byte[] digest) { mDigest = digest; } @@ -49,26 +56,32 @@ public class ManifestDigest implements Parcelable { mDigest = source.createByteArray(); } - static ManifestDigest fromAttributes(Attributes attributes) { - if (attributes == null) { + static ManifestDigest fromInputStream(InputStream fileIs) { + if (fileIs == null) { return null; } - String encodedDigest = null; - - for (int i = 0; i < DIGEST_TYPES.length; i++) { - final String value = attributes.getValue(DIGEST_TYPES[i]); - if (value != null) { - encodedDigest = value; - break; - } + final MessageDigest md; + try { + md = MessageDigest.getInstance(DIGEST_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(DIGEST_ALGORITHM + " must be available", e); } - if (encodedDigest == null) { + final DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fileIs), md); + try { + byte[] readBuffer = new byte[8192]; + while (dis.read(readBuffer, 0, readBuffer.length) != -1) { + // not using + } + } catch (IOException e) { + Slog.w(TAG, "Could not read manifest"); return null; + } finally { + IoUtils.closeQuietly(dis); } - final byte[] digest = Base64.decode(encodedDigest, Base64.DEFAULT); + final byte[] digest = md.digest(); return new ManifestDigest(digest); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 11f9be9..384aed8 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,7 +24,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; @@ -54,10 +53,9 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import com.android.internal.util.XmlUtils; @@ -567,6 +565,28 @@ public class PackageParser { return pkg; } + /** + * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the + * APK. If it successfully scanned the package and found the + * {@code AndroidManifest.xml}, {@code true} is returned. + */ + public boolean collectManifestDigest(Package pkg) { + try { + final JarFile jarFile = new JarFile(mArchiveSourcePath); + try { + final ZipEntry je = jarFile.getEntry(ANDROID_MANIFEST_FILENAME); + if (je != null) { + pkg.manifestDigest = ManifestDigest.fromInputStream(jarFile.getInputStream(je)); + } + } finally { + jarFile.close(); + } + return true; + } catch (IOException e) { + return false; + } + } + public boolean collectCertificates(Package pkg, int flags) { pkg.mSignatures = null; @@ -618,7 +638,6 @@ public class PackageParser { } } else { Enumeration<JarEntry> entries = jarFile.entries(); - final Manifest manifest = jarFile.getManifest(); while (entries.hasMoreElements()) { final JarEntry je = entries.nextElement(); if (je.isDirectory()) continue; @@ -629,8 +648,8 @@ public class PackageParser { continue; if (ANDROID_MANIFEST_FILENAME.equals(name)) { - final Attributes attributes = manifest.getAttributes(name); - pkg.manifestDigest = ManifestDigest.fromAttributes(attributes); + pkg.manifestDigest = + ManifestDigest.fromInputStream(jarFile.getInputStream(je)); } final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer); @@ -1017,25 +1036,10 @@ public class PackageParser { return null; } } else if (tagName.equals("uses-permission")) { - sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AndroidManifestUsesPermission); - - // Note: don't allow this value to be a reference to a resource - // that may change. - String name = sa.getNonResourceString( - com.android.internal.R.styleable.AndroidManifestUsesPermission_name); - boolean required = sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true); - - sa.recycle(); - - if (name != null && !pkg.requestedPermissions.contains(name)) { - pkg.requestedPermissions.add(name.intern()); - pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); + if (!parseUsesPermission(pkg, res, parser, attrs, outError)) { + return null; } - XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("uses-configuration")) { ConfigurationInfo cPref = new ConfigurationInfo(); sa = res.obtainAttributes(attrs, @@ -1379,9 +1383,53 @@ public class PackageParser { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } + /* + * b/8528162: Ignore the <uses-permission android:required> attribute if + * targetSdkVersion < JELLY_BEAN_MR2. There are lots of apps in the wild + * which are improperly using this attribute, even though it never worked. + */ + if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) { + for (int i = 0; i < pkg.requestedPermissionsRequired.size(); i++) { + pkg.requestedPermissionsRequired.set(i, Boolean.TRUE); + } + } + return pkg; } + private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser, + AttributeSet attrs, String[] outError) + throws XmlPullParserException, IOException { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestUsesPermission); + + // Note: don't allow this value to be a reference to a resource + // that may change. + String name = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestUsesPermission_name); + boolean required = sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true); + + sa.recycle(); + + if (name != null) { + int index = pkg.requestedPermissions.indexOf(name); + if (index == -1) { + pkg.requestedPermissions.add(name.intern()); + pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); + } else { + if (pkg.requestedPermissionsRequired.get(index) != required) { + outError[0] = "conflicting <uses-permission> entries"; + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return false; + } + } + } + + XmlUtils.skipCurrentTag(parser); + return true; + } + private static String buildClassName(String pkg, CharSequence clsSeq, String[] outError) { if (clsSeq == null || clsSeq.length() <= 0) { diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 52b238f..75f8b59 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -378,7 +378,7 @@ public class LinkProperties implements Parcelable { * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalStackedLinks(LinkProperties target) { - if (!mStackedLinks.keys().equals(target.mStackedLinks.keys())) { + if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) { return false; } for (LinkProperties stacked : mStackedLinks.values()) { diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index cc6903d..4b022d9 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -20,6 +20,7 @@ import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.os.Environment.UserEnvironment; +import android.os.StrictMode; import android.util.Log; import java.io.File; import java.io.IOException; @@ -2326,4 +2327,16 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return this; } } + + /** + * If this is a {@code file://} Uri, it will be reported to + * {@link StrictMode}. + * + * @hide + */ + public void checkFileUriExposed(String location) { + if ("file".equals(getScheme())) { + StrictMode.onFileUriExposed(location); + } + } } diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 02135bc..38f4d5e 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -50,7 +50,7 @@ import android.util.PrefixPrinter; * } * }</pre> */ -public class Looper { +public final class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare(). @@ -223,7 +223,7 @@ public class Looper { * * @hide */ - public final int postSyncBarrier() { + public int postSyncBarrier() { return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis()); } @@ -238,7 +238,7 @@ public class Looper { * * @hide */ - public final void removeSyncBarrier(int token) { + public void removeSyncBarrier(int token) { mQueue.removeSyncBarrier(token); } diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 222578a..e0d40c9 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -29,7 +29,7 @@ import java.util.ArrayList; * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ -public class MessageQueue { +public final class MessageQueue { // True if the message queue can be quit. private final boolean mQuitAllowed; @@ -78,7 +78,7 @@ public class MessageQueue { * * @param handler The IdleHandler to be added. */ - public final void addIdleHandler(IdleHandler handler) { + public void addIdleHandler(IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } @@ -94,7 +94,7 @@ public class MessageQueue { * * @param handler The IdleHandler to be removed. */ - public final void removeIdleHandler(IdleHandler handler) { + public void removeIdleHandler(IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); } @@ -121,7 +121,7 @@ public class MessageQueue { } } - final Message next() { + Message next() { int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; @@ -218,7 +218,7 @@ public class MessageQueue { } } - final void quit() { + void quit() { if (!mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit."); } @@ -232,7 +232,7 @@ public class MessageQueue { nativeWake(mPtr); } - final int enqueueSyncBarrier(long when) { + int enqueueSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { @@ -259,7 +259,7 @@ public class MessageQueue { } } - final void removeSyncBarrier(int token) { + void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. final boolean needWake; @@ -288,7 +288,7 @@ public class MessageQueue { } } - final boolean enqueueMessage(Message msg, long when) { + boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } @@ -338,7 +338,7 @@ public class MessageQueue { return true; } - final boolean hasMessages(Handler h, int what, Object object) { + boolean hasMessages(Handler h, int what, Object object) { if (h == null) { return false; } @@ -355,7 +355,7 @@ public class MessageQueue { } } - final boolean hasMessages(Handler h, Runnable r, Object object) { + boolean hasMessages(Handler h, Runnable r, Object object) { if (h == null) { return false; } @@ -372,7 +372,7 @@ public class MessageQueue { } } - final void removeMessages(Handler h, int what, Object object) { + void removeMessages(Handler h, int what, Object object) { if (h == null) { return; } @@ -406,7 +406,7 @@ public class MessageQueue { } } - final void removeMessages(Handler h, Runnable r, Object object) { + void removeMessages(Handler h, Runnable r, Object object) { if (h == null || r == null) { return; } @@ -440,7 +440,7 @@ public class MessageQueue { } } - final void removeCallbacksAndMessages(Handler h, Object object) { + void removeCallbacksAndMessages(Handler h, Object object) { if (h == null) { return; } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index f682abe..3267939 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -203,10 +203,15 @@ public final class StrictMode { */ public static final int DETECT_VM_REGISTRATION_LEAKS = 0x2000; // for VmPolicy + /** + * @hide + */ + private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x4000; // for VmPolicy + private static final int ALL_VM_DETECT_BITS = DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS | - DETECT_VM_REGISTRATION_LEAKS; + DETECT_VM_REGISTRATION_LEAKS | DETECT_VM_FILE_URI_EXPOSURE; /** * @hide @@ -628,7 +633,8 @@ public final class StrictMode { */ public Builder detectAll() { return enable(DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_CURSOR_LEAKS - | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS); + | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS + | DETECT_VM_FILE_URI_EXPOSURE); } /** @@ -666,6 +672,16 @@ public final class StrictMode { } /** + * Detect when a {@code file://} {@link android.net.Uri} is exposed beyond this + * app. The receiving app may not have access to the sent path. + * Instead, when sharing files between apps, {@code content://} + * should be used with permission grants. + */ + public Builder detectFileUriExposure() { + return enable(DETECT_VM_FILE_URI_EXPOSURE); + } + + /** * Crashes the whole process on violation. This penalty runs at * the end of all enabled penalties so yo you'll still get * your logging or other violations before the process dies. @@ -1524,6 +1540,13 @@ public final class StrictMode { /** * @hide */ + public static boolean vmFileUriExposureEnabled() { + return (sVmPolicyMask & DETECT_VM_FILE_URI_EXPOSURE) != 0; + } + + /** + * @hide + */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { onVmPolicyViolation(message, originStack); } @@ -1549,6 +1572,14 @@ public final class StrictMode { onVmPolicyViolation(null, originStack); } + /** + * @hide + */ + public static void onFileUriExposed(String location) { + final String message = "file:// Uri exposed through " + location; + onVmPolicyViolation(message, new Throwable(message)); + } + // Map from VM violation fingerprint to uptime millis. private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>(); diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 2dd27f8..25af209 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -106,16 +106,13 @@ public final class CalendarContract { * {@link Activity#RESULT_OK} or {@link Activity#RESULT_CANCELED} to * acknowledge whether the action was handled or not. * - * The custom app should have an intent-filter like the following + * The custom app should have an intent filter like the following: * <pre> - * {@code - * <intent-filter> - * <action android:name="android.provider.calendar.action.HANDLE_CUSTOM_EVENT" /> - * <category android:name="android.intent.category.DEFAULT" /> - * <data android:mimeType="vnd.android.cursor.item/event" /> - * </intent-filter> - * } - * </pre> + * <intent-filter> + * <action android:name="android.provider.calendar.action.HANDLE_CUSTOM_EVENT" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:mimeType="vnd.android.cursor.item/event" /> + * </intent-filter></pre> * <p> * Input: {@link Intent#getData} has the event URI. The extra * {@link #EXTRA_EVENT_BEGIN_TIME} has the start time of the instance. The @@ -123,7 +120,7 @@ public final class CalendarContract { * {@link EventsColumns#CUSTOM_APP_URI}. * <p> * Output: {@link Activity#RESULT_OK} if this was handled; otherwise - * {@link Activity#RESULT_CANCELED} + * {@link Activity#RESULT_CANCELED}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_HANDLE_CUSTOM_EVENT = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 03ee9eb..a0473a4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5398,6 +5398,20 @@ public final class Settings { public static final String CERT_PIN_UPDATE_METADATA_URL = "cert_pin_metadata_url"; /** + * URL for intent firewall updates + * @hide + */ + public static final String INTENT_FIREWALL_UPDATE_CONTENT_URL = + "intent_firewall_content_url"; + + /** + * URL for intent firewall update metadata + * @hide + */ + public static final String INTENT_FIREWALL_UPDATE_METADATA_URL = + "intent_firewall_metadata_url"; + + /** * SELinux enforcement status. If 0, permissive; if 1, enforcing. * @hide */ diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java index c365643..e1cc90e 100644 --- a/core/java/android/security/IKeystoreService.java +++ b/core/java/android/security/IKeystoreService.java @@ -444,6 +444,24 @@ public interface IKeystoreService extends IInterface { } return _result; } + + @Override + public int clear_uid(long uid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeLong(uid); + mRemote.transact(Stub.TRANSACTION_clear_uid, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } } private static final String DESCRIPTOR = "android.security.keystore"; @@ -470,6 +488,7 @@ public interface IKeystoreService extends IInterface { static final int TRANSACTION_getmtime = IBinder.FIRST_CALL_TRANSACTION + 19; static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20; static final int TRANSACTION_is_hardware_backed = IBinder.FIRST_CALL_TRANSACTION + 21; + static final int TRANSACTION_clear_uid = IBinder.FIRST_CALL_TRANSACTION + 22; /** * Cast an IBinder object into an IKeystoreService interface, generating @@ -559,4 +578,6 @@ public interface IKeystoreService extends IInterface { throws RemoteException; public int is_hardware_backed() throws RemoteException; + + public int clear_uid(long uid) throws RemoteException; } diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index f813df3..c497e35 100644 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -31,6 +31,7 @@ import java.util.Locale; import java.util.TimeZone; import java.text.SimpleDateFormat; +import libcore.icu.ICU; import libcore.icu.LocaleData; /** @@ -43,6 +44,9 @@ import libcore.icu.LocaleData; * for both formatting and parsing dates. For the canonical documentation * of format strings, see {@link java.text.SimpleDateFormat}. * + * <p>In cases where the system does not provide a suitable pattern, + * this class offers the {@link #getBestDateTimePattern} method. + * * <p>The {@code format} methods in this class implement a subset of Unicode * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns. * The subset currently supported by this class includes the following format characters: @@ -164,6 +168,37 @@ public class DateFormat { } /** + * Returns the best possible localized form of the given skeleton for the given + * locale. A skeleton is similar to, and uses the same format characters as, a Unicode + * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> + * pattern. + * + * <p>One difference is that order is irrelevant. For example, "MMMMd" will return + * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale. + * + * <p>Note also in that second example that the necessary punctuation for German was + * added. For the same input in {@code es_ES}, we'd have even more extra text: + * "d 'de' MMMM". + * + * <p>This method will automatically correct for grammatical necessity. Given the + * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale, + * where stand-alone months are necessary. Lengths are preserved where meaningful, + * so "Md" would give a different result to "MMMd", say, except in a locale such as + * {@code ja_JP} where there is only one length of month. + * + * <p>This method will only return patterns that are in CLDR, and is useful whenever + * you know what elements you want in your format string but don't want to make your + * code specific to any one locale. + * + * @param locale the locale into which the skeleton should be localized + * @param skeleton a skeleton as described above + * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}. + */ + public static String getBestDateTimePattern(Locale locale, String skeleton) { + return ICU.getBestDateTimePattern(skeleton, locale.toString()); + } + + /** * Returns a {@link java.text.DateFormat} object that can format the time according * to the current locale and the user's 12-/24-hour clock preference. * @param context the application context diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 9860588..2bc1c6a 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -16,6 +16,7 @@ package android.text.util; +import android.telephony.PhoneNumberUtils; import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.text.style.URLSpan; @@ -32,9 +33,14 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.android.i18n.phonenumbers.PhoneNumberMatch; +import com.android.i18n.phonenumbers.PhoneNumberUtil; +import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency; + /** * Linkify take a piece of text and a regular expression and turns all of the * regex matches in the text into clickable links. This is particularly @@ -221,9 +227,7 @@ public class Linkify { } if ((mask & PHONE_NUMBERS) != 0) { - gatherLinks(links, text, Patterns.PHONE, - new String[] { "tel:" }, - sPhoneNumberMatchFilter, sPhoneNumberTransformFilter); + gatherTelLinks(links, text); } if ((mask & MAP_ADDRESSES) != 0) { @@ -443,6 +447,19 @@ public class Linkify { } } + private static final void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) { + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(), + Locale.getDefault().getCountry(), Leniency.POSSIBLE, Long.MAX_VALUE); + for (PhoneNumberMatch match : matches) { + LinkSpec spec = new LinkSpec(); + spec.url = "tel:" + PhoneNumberUtils.normalizeNumber(match.rawString()); + spec.start = match.start(); + spec.end = match.end(); + links.add(spec); + } + } + private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) { String string = s.toString(); String address; diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 7918823..8055077 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -451,10 +451,8 @@ public abstract class HardwareRenderer { * @param attachInfo AttachInfo tied to the specified view. * @param callbacks Callbacks invoked when drawing happens. * @param dirty The dirty rectangle to update, can be null. - * - * @return true if the dirty rect was ignored, false otherwise */ - abstract boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, + abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty); /** @@ -992,11 +990,7 @@ public abstract class HardwareRenderer { mCanvas = createCanvas(); mCanvas.setName(mName); } - if (mCanvas != null) { - setEnabled(true); - } else { - Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created"); - } + setEnabled(true); } return mCanvas != null; @@ -1340,7 +1334,7 @@ public abstract class HardwareRenderer { } @Override - boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, + void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { if (canDraw()) { if (!hasDirtyRegions()) { @@ -1401,11 +1395,8 @@ public abstract class HardwareRenderer { } attachInfo.mIgnoreDirtyState = false; - return dirty == null; } } - - return false; } private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a85a558..8ed4a86 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -175,6 +175,12 @@ interface IWindowManager int watchRotation(IRotationWatcher watcher); /** + * Remove a rotation watcher set using watchRotation. + * @hide + */ + void removeRotationWatcher(IRotationWatcher watcher); + + /** * Determine the preferred edge of the screen to pin the compact options menu against. * @return a Gravity value for the options menu panel * @hide diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index 117c101..f5ee7ed 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -23,6 +23,8 @@ import android.os.MessageQueue; import android.util.Log; import android.util.SparseIntArray; +import java.lang.ref.WeakReference; + /** * Provides a low-level mechanism for an application to receive input events. * @hide @@ -42,7 +44,7 @@ public abstract class InputEventReceiver { // Map from InputEvent sequence numbers to dispatcher sequence numbers. private final SparseIntArray mSeqMap = new SparseIntArray(); - private static native int nativeInit(InputEventReceiver receiver, + private static native int nativeInit(WeakReference<InputEventReceiver> receiver, InputChannel inputChannel, MessageQueue messageQueue); private static native void nativeDispose(int receiverPtr); private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled); @@ -65,7 +67,8 @@ public abstract class InputEventReceiver { mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); - mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue); + mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), + inputChannel, mMessageQueue); mCloseGuard.open("dispose"); } diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java index adf63fe..be6a623 100644 --- a/core/java/android/view/InputEventSender.java +++ b/core/java/android/view/InputEventSender.java @@ -22,6 +22,8 @@ import android.os.Looper; import android.os.MessageQueue; import android.util.Log; +import java.lang.ref.WeakReference; + /** * Provides a low-level mechanism for an application to send input events. * @hide @@ -38,7 +40,7 @@ public abstract class InputEventSender { private InputChannel mInputChannel; private MessageQueue mMessageQueue; - private static native int nativeInit(InputEventSender sender, + private static native int nativeInit(WeakReference<InputEventSender> sender, InputChannel inputChannel, MessageQueue messageQueue); private static native void nativeDispose(int senderPtr); private static native boolean nativeSendKeyEvent(int senderPtr, int seq, KeyEvent event); @@ -60,7 +62,8 @@ public abstract class InputEventSender { mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); - mSenderPtr = nativeInit(this, inputChannel, mMessageQueue); + mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this), + inputChannel, mMessageQueue); mCloseGuard.open("dispose"); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a5b3c8f..3b06da7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6889,7 +6889,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Adds the children of a given View for accessibility. Since some Views are * not important for accessibility the children for accessibility are not - * necessarily direct children of the riew, rather they are the first level of + * necessarily direct children of the view, rather they are the first level of * descendants important for accessibility. * * @param children The list of children for accessibility. @@ -9485,17 +9485,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is * completely transparent and 1 means the view is completely opaque.</p> * + * <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant + * performance implications, especially for large views. It is best to use the alpha property + * sparingly and transiently, as in the case of fading animations.</p> + * + * <p>For a view with a frequently changing alpha, such as during a fading animation, it is + * strongly recommended for performance reasons to either override + * {@link #hasOverlappingRendering()} to return false if appropriate, or setting a + * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view.</p> + * * <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is - * responsible for applying the opacity itself. Otherwise, calling this method is - * equivalent to calling {@link #setLayerType(int, android.graphics.Paint)} and - * setting a hardware layer.</p> + * responsible for applying the opacity itself.</p> * - * <p>Note that setting alpha to a translucent value (0 < alpha < 1) may have - * performance implications. It is generally best to use the alpha property sparingly and - * transiently, as in the case of fading animations.</p> + * <p>Note that if the view is backed by a + * {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a + * {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than + * 1.0 will supercede the alpha of the layer paint.</p> * * @param alpha The opacity of the view. * + * @see #hasOverlappingRendering() * @see #setLayerType(int, android.graphics.Paint) * * @attr ref android.R.styleable#View_alpha @@ -12365,13 +12374,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ul> * * <p>If this view has an alpha value set to < 1.0 by calling - * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by - * this view's alpha value. Calling {@link #setAlpha(float)} is therefore - * equivalent to setting a hardware layer on this view and providing a paint with - * the desired alpha value.</p> + * {@link #setAlpha(float)}, the alpha value of the layer's paint is superceded + * by this view's alpha value.</p> * - * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE disabled}, - * {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware} + * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE}, + * {@link #LAYER_TYPE_SOFTWARE} and {@link #LAYER_TYPE_HARDWARE} * for more information on when and how to use layers.</p> * * @param layerType The type of layer to use with this view, must be one of @@ -12441,11 +12448,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li> * </ul> * - * <p>If this view has an alpha value set to < 1.0 by calling - * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by - * this view's alpha value. Calling {@link #setAlpha(float)} is therefore - * equivalent to setting a hardware layer on this view and providing a paint with - * the desired alpha value.</p> + * <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the + * alpha value of the layer's paint is superceded by this view's alpha value.</p> * * @param paint The paint used to compose the layer. This argument is optional * and can be null. It is ignored when the layer type is diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 725502d..f615e1bc 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -406,10 +406,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private View[] mChildren; // Number of valid children in the mChildren array, the rest should be null or not // considered as children + private int mChildrenCount; - private boolean mLayoutSuppressed = false; + // Whether layout calls are currently being suppressed, controlled by calls to + // suppressLayout() + boolean mSuppressLayout = false; - private int mChildrenCount; + // Whether any layout calls have actually been suppressed while mSuppressLayout + // has been true. This tracks whether we need to issue a requestLayout() when + // layout is later re-enabled. + private boolean mLayoutCalledWhileSuppressed = false; private static final int ARRAY_INITIAL_CAPACITY = 12; private static final int ARRAY_CAPACITY_INCREMENT = 12; @@ -2564,7 +2570,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager exitHoverTargets(); // In case view is detached while transition is running - mLayoutSuppressed = false; + mLayoutCalledWhileSuppressed = false; // Tear down our drag tracking mDragNotifiedChildren = null; @@ -4525,7 +4531,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes - mLayoutSuppressed = true; + mLayoutCalledWhileSuppressed = true; } } @@ -5201,9 +5207,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { - if (mLayoutSuppressed && !transition.isChangingLayout()) { + if (mLayoutCalledWhileSuppressed && !transition.isChangingLayout()) { requestLayout(); - mLayoutSuppressed = false; + mLayoutCalledWhileSuppressed = false; } if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) { endViewTransition(view); @@ -5212,6 +5218,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager }; /** + * Tells this ViewGroup to suppress all layout() calls until layout + * suppression is disabled with a later call to suppressLayout(false). + * When layout suppression is disabled, a requestLayout() call is sent + * if layout() was attempted while layout was being suppressed. + * + * @hide + */ + public void suppressLayout(boolean suppress) { + mSuppressLayout = suppress; + if (!suppress) { + if (mLayoutCalledWhileSuppressed) { + requestLayout(); + mLayoutCalledWhileSuppressed = false; + } + } + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7790f92..efa8a9e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -197,7 +197,6 @@ public final class ViewRootImpl implements ViewParent, int mHeight; Rect mDirty; final Rect mCurrentDirty = new Rect(); - final Rect mPreviousDirty = new Rect(); boolean mIsAnimating; CompatibilityInfo.Translator mTranslator; @@ -767,10 +766,11 @@ public final class ViewRootImpl implements ViewParent, final boolean translucent = attrs.format != PixelFormat.OPAQUE; mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); - mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); - mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested - = mAttachInfo.mHardwareRenderer != null; - + if (mAttachInfo.mHardwareRenderer != null) { + mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); + mAttachInfo.mHardwareAccelerated = + mAttachInfo.mHardwareAccelerationRequested = true; + } } else if (fakeHwAccelerated) { // The window had wanted to use hardware acceleration, but this // is not allowed in its process. By setting this flag, it can @@ -1425,6 +1425,8 @@ public final class ViewRootImpl implements ViewParent, final int surfaceGenerationId = mSurface.getGenerationId(); relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); + mWindowsAnimating |= + (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0; if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() + " overscan=" + mPendingOverscanInsets.toShortString() @@ -2385,14 +2387,10 @@ public final class ViewRootImpl implements ViewParent, mResizeAlpha = resizeAlpha; mCurrentDirty.set(dirty); - mCurrentDirty.union(mPreviousDirty); - mPreviousDirty.set(dirty); dirty.setEmpty(); - if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, - animating ? null : mCurrentDirty)) { - mPreviousDirty.set(0, 0, mWidth, mHeight); - } + attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, + animating ? null : mCurrentDirty); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface @@ -2447,6 +2445,8 @@ public final class ViewRootImpl implements ViewParent, canvas = mSurface.lockCanvas(dirty); + // The dirty rectangle can be modified by Surface.lockCanvas() + //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; @@ -3097,8 +3097,7 @@ public final class ViewRootImpl implements ViewParent, boolean inTouchMode = msg.arg2 != 0; ensureTouchModeLocally(inTouchMode); - if (mAttachInfo.mHardwareRenderer != null && - mSurface != null && mSurface.isValid()) { + if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){ mFullRedrawNeeded = true; try { mAttachInfo.mHardwareRenderer.initializeIfNeeded( diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 4207832..855b6d4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -325,7 +325,8 @@ public final class InputMethodManager { PendingEvent mPendingEventPool; int mPendingEventPoolSize; - PendingEvent mFirstPendingEvent; + PendingEvent mPendingEventHead; + PendingEvent mPendingEventTail; // ----------------------------------------------------------- @@ -366,17 +367,14 @@ public final class InputMethodManager { if (mBindSequence < 0 || mBindSequence != res.sequence) { Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence + ", given seq=" + res.sequence); - if (res.channel != null) { + if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } return; } - + + setInputChannelLocked(res.channel); mCurMethod = res.method; - if (mCurChannel != null) { - mCurChannel.dispose(); - } - mCurChannel = res.channel; mCurId = res.id; mBindSequence = res.sequence; } @@ -718,19 +716,26 @@ public final class InputMethodManager { */ void clearBindingLocked() { clearConnectionLocked(); + setInputChannelLocked(null); mBindSequence = -1; mCurId = null; mCurMethod = null; - if (mCurSender != null) { - mCurSender.dispose(); - mCurSender = null; - } - if (mCurChannel != null) { - mCurChannel.dispose(); - mCurChannel = null; + } + + void setInputChannelLocked(InputChannel channel) { + if (mCurChannel != channel) { + if (mCurSender != null) { + flushPendingEventsLocked(); + mCurSender.dispose(); + mCurSender = null; + } + if (mCurChannel != null) { + mCurChannel.dispose(); + } + mCurChannel = channel; } } - + /** * Reset all of the state associated with a served view being connected * to an input method @@ -1172,15 +1177,12 @@ public final class InputMethodManager { if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res != null) { if (res.id != null) { + setInputChannelLocked(res.channel); mBindSequence = res.sequence; mCurMethod = res.method; - if (mCurChannel != null) { - mCurChannel.dispose(); - } - mCurChannel = res.channel; mCurId = res.id; } else { - if (res.channel != null) { + if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } if (mCurMethod == null) { @@ -1653,8 +1655,13 @@ public final class InputMethodManager { private void enqueuePendingEventLocked( long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback); - p.mNext = mFirstPendingEvent; - mFirstPendingEvent = p; + if (mPendingEventTail != null) { + mPendingEventTail.mNext = p; + mPendingEventTail = p; + } else { + mPendingEventHead = p; + mPendingEventTail = p; + } Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p); msg.setAsynchronous(true); @@ -1662,12 +1669,15 @@ public final class InputMethodManager { } private PendingEvent dequeuePendingEventLocked(int seq) { - PendingEvent p = mFirstPendingEvent; + PendingEvent p = mPendingEventHead; if (p == null) { return null; } if (p.mSeq == seq) { - mFirstPendingEvent = p.mNext; + mPendingEventHead = p.mNext; + if (mPendingEventHead == null) { + mPendingEventTail = null; + } } else { PendingEvent prev; do { @@ -1678,6 +1688,9 @@ public final class InputMethodManager { } } while (p.mSeq != seq); prev.mNext = p.mNext; + if (mPendingEventTail == p) { + mPendingEventTail = prev; + } } p.mNext = null; return p; @@ -1712,6 +1725,18 @@ public final class InputMethodManager { } } + private void flushPendingEventsLocked() { + mH.removeMessages(MSG_EVENT_TIMEOUT); + + PendingEvent p = mPendingEventHead; + while (p != null) { + Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, p.mSeq, 0, p); + msg.setAsynchronous(true); + mH.sendMessage(msg); + p = p.mNext; + } + } + public void showInputMethodPicker() { synchronized (mH) { showInputMethodPickerLocked(); diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 18df0b1..00d87bd 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -25,8 +25,13 @@ import dalvik.system.PathClassLoader; /** * Top level factory, used creating all the main WebView implementation classes. + * + * @hide */ -class WebViewFactory { +public final class WebViewFactory { + public static final String WEBVIEW_EXPERIMENTAL_PROPERTY = "persist.sys.webview.exp"; + private static final String DEPRECATED_CHROMIUM_PROPERTY = "webview.use_chromium"; + // Default Provider factory class name. // TODO: When the Chromium powered WebView is ready, it should be the default factory class. private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory"; @@ -43,16 +48,17 @@ class WebViewFactory { private static WebViewFactoryProvider sProviderInstance; private static final Object sProviderLock = new Object(); + public static boolean isExperimentalWebViewAvailable() { + return Build.IS_DEBUGGABLE && (new java.io.File(CHROMIUM_WEBVIEW_JAR).exists()); + } + static WebViewFactoryProvider getProvider() { synchronized (sProviderLock) { // For now the main purpose of this function (and the factory abstraction) is to keep // us honest and minimize usage of WebViewClassic internals when binding the proxy. if (sProviderInstance != null) return sProviderInstance; - // For debug builds, we allow a system property to specify that we should use the - // Chromium powered WebView. This enables us to switch between implementations - // at runtime. For user (release) builds, don't allow this. - if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("webview.use_chromium", false)) { + if (isExperimentalWebViewEnabled()) { StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try { sProviderInstance = loadChromiumProvider(); @@ -76,6 +82,20 @@ class WebViewFactory { } } + // For debug builds, we allow a system property to specify that we should use the + // experimtanl Chromium powered WebView. This enables us to switch between + // implementations at runtime. For user (release) builds, don't allow this. + private static boolean isExperimentalWebViewEnabled() { + if (!isExperimentalWebViewAvailable()) + return false; + if (SystemProperties.getBoolean(DEPRECATED_CHROMIUM_PROPERTY, false)) { + Log.w(LOGTAG, String.format("The property %s has been deprecated. Please use %s.", + DEPRECATED_CHROMIUM_PROPERTY, WEBVIEW_EXPERIMENTAL_PROPERTY)); + return true; + } + return SystemProperties.getBoolean(WEBVIEW_EXPERIMENTAL_PROPERTY, false); + } + // TODO: This allows us to have the legacy and Chromium WebView coexist for development // and side-by-side testing. After transition, remove this when no longer required. private static WebViewFactoryProvider loadChromiumProvider() { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 79fc51e..83e2e79 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -34,6 +34,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.StrictMode; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -2263,8 +2264,13 @@ public class RemoteViews implements Parcelable, Filter { * @param value The value to pass to the method. */ public void setUri(int viewId, String methodName, Uri value) { - // Resolve any filesystem path before sending remotely - value = value.getCanonicalUri(); + if (value != null) { + // Resolve any filesystem path before sending remotely + value = value.getCanonicalUri(); + if (StrictMode.vmFileUriExposureEnabled()) { + value.checkFileUriExposed("RemoteViews.setUri()"); + } + } addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); } diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index 4045497..62afd2e 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -39,31 +39,26 @@ import com.android.internal.R; * <p> * Here is how to use the action provider with custom backing file in a {@link MenuItem}: * </p> - * <p> * <pre> - * <code> - * // In Activity#onCreateOptionsMenu - * public boolean onCreateOptionsMenu(Menu menu) { - * // Get the menu item. - * MenuItem menuItem = menu.findItem(R.id.my_menu_item); - * // Get the provider and hold onto it to set/change the share intent. - * mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); - * // Set history different from the default before getting the action - * // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls - * // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this - * // line if using the default share history file is desired. - * mShareActionProvider.setShareHistoryFileName("custom_share_history.xml"); - * . . . - * } + * // In Activity#onCreateOptionsMenu + * public boolean onCreateOptionsMenu(Menu menu) { + * // Get the menu item. + * MenuItem menuItem = menu.findItem(R.id.my_menu_item); + * // Get the provider and hold onto it to set/change the share intent. + * mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); + * // Set history different from the default before getting the action + * // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls + * // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this + * // line if using the default share history file is desired. + * mShareActionProvider.setShareHistoryFileName("custom_share_history.xml"); + * . . . + * } * - * // Somewhere in the application. - * public void doShare(Intent shareIntent) { - * // When you want to share set the share intent. - * mShareActionProvider.setShareIntent(shareIntent); - * } - * </pre> - * </code> - * </p> + * // Somewhere in the application. + * public void doShare(Intent shareIntent) { + * // When you want to share set the share intent. + * mShareActionProvider.setShareIntent(shareIntent); + * }</pre> * <p> * <strong>Note:</strong> While the sample snippet demonstrates how to use this provider * in the context of a menu item, the use of the provider is not limited to menu items. @@ -245,9 +240,9 @@ public class ShareActionProvider extends ActionProvider { * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the * action view. You should <strong>not</strong> call * {@link android.app.Activity#invalidateOptionsMenu()} from - * {@link android.app.Activity#onCreateOptionsMenu(Menu)}." - * <p> - * <code> + * {@link android.app.Activity#onCreateOptionsMenu(Menu)}. + * </p> + * <pre> * private void doShare(Intent intent) { * if (IMAGE.equals(intent.getMimeType())) { * mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME); @@ -256,9 +251,7 @@ public class ShareActionProvider extends ActionProvider { * } * mShareActionProvider.setIntent(intent); * invalidateOptionsMenu(); - * } - * <code> - * + * }</pre> * @param shareHistoryFile The share history file name. */ public void setShareHistoryFileName(String shareHistoryFile) { @@ -269,16 +262,11 @@ public class ShareActionProvider extends ActionProvider { /** * Sets an intent with information about the share action. Here is a * sample for constructing a share intent: - * <p> * <pre> - * <code> - * Intent shareIntent = new Intent(Intent.ACTION_SEND); - * shareIntent.setType("image/*"); - * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg")); - * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString()); - * </pre> - * </code> - * </p> + * Intent shareIntent = new Intent(Intent.ACTION_SEND); + * shareIntent.setType("image/*"); + * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg")); + * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());</pre> * * @param shareIntent The share intent. * diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index d1db230..b99b39a 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -36,7 +36,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -125,6 +124,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mWasHomeEnabled; // Was it enabled before action view expansion? private MenuBuilder mOptionsMenu; + private boolean mMenuPrepared; private ActionBarContextView mContextView; @@ -164,7 +164,10 @@ public class ActionBarView extends AbsActionBarView { private final OnClickListener mUpClickListener = new OnClickListener() { public void onClick(View v) { - mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem); + if (mMenuPrepared) { + // Only invoke the window callback if the options menu has been initialized. + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem); + } } }; @@ -402,6 +405,10 @@ public class ActionBarView extends AbsActionBarView { mCallback = callback; } + public void setMenuPrepared() { + mMenuPrepared = true; + } + public void setMenu(Menu menu, MenuPresenter.Callback cb) { if (menu == mOptionsMenu) return; diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java index 8ebe94c..ca797eb 100644 --- a/core/java/com/android/internal/widget/TransportControlView.java +++ b/core/java/com/android/internal/widget/TransportControlView.java @@ -158,7 +158,7 @@ public class TransportControlView extends FrameLayout implements OnClickListener } } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index a7eede2..8766cf9 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -93,14 +93,6 @@ static struct debug_offsets_t // ---------------------------------------------------------------------------- -static struct weakreference_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mGet; - -} gWeakReferenceOffsets; - static struct error_offsets_t { jclass mClass; @@ -570,7 +562,7 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) // Someone else's... do we know about it? jobject object = (jobject)val->findObject(&gBinderProxyOffsets); if (object != NULL) { - jobject res = env->CallObjectMethod(object, gWeakReferenceOffsets.mGet); + jobject res = jniGetReferent(env, object); if (res != NULL) { ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res); return res; @@ -1211,13 +1203,6 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) { jclass clazz; - clazz = env->FindClass("java/lang/ref/WeakReference"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.ref.WeakReference"); - gWeakReferenceOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gWeakReferenceOffsets.mGet - = env->GetMethodID(clazz, "get", "()Ljava/lang/Object;"); - assert(gWeakReferenceOffsets.mGet); - clazz = env->FindClass("java/lang/Error"); LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.Error"); gErrorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 198814a..c350521 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -34,6 +34,8 @@ #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" +#include <ScopedLocalRef.h> + namespace android { static struct { @@ -47,7 +49,7 @@ static struct { class NativeInputEventReceiver : public LooperCallback { public: NativeInputEventReceiver(JNIEnv* env, - jobject receiverObj, const sp<InputChannel>& inputChannel, + jobject receiverWeak, const sp<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue); status_t initialize(); @@ -59,7 +61,7 @@ protected: virtual ~NativeInputEventReceiver(); private: - jobject mReceiverObjGlobal; + jobject mReceiverWeakGlobal; InputConsumer mInputConsumer; sp<MessageQueue> mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; @@ -74,9 +76,9 @@ private: NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, - jobject receiverObj, const sp<InputChannel>& inputChannel, + jobject receiverWeak, const sp<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) : - mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), + mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), mInputConsumer(inputChannel), mMessageQueue(messageQueue), mBatchedInputEventPending(false) { #if DEBUG_DISPATCH_CYCLE @@ -86,7 +88,7 @@ NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, NativeInputEventReceiver::~NativeInputEventReceiver() { JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->DeleteGlobalRef(mReceiverObjGlobal); + env->DeleteGlobalRef(mReceiverWeakGlobal); } status_t NativeInputEventReceiver::initialize() { @@ -151,6 +153,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, mBatchedInputEventPending = false; } + ScopedLocalRef<jobject> receiverObj(env, NULL); bool skipCallbacks = false; for (;;) { uint32_t seq; @@ -162,12 +165,21 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer.hasPendingBatch()) { // There is a pending batch. Come back later. + if (!receiverObj.get()) { + receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal)); + if (!receiverObj.get()) { + ALOGW("channel '%s' ~ Receiver object was finalized " + "without being disposed.", getInputChannelName()); + return DEAD_OBJECT; + } + } + mBatchedInputEventPending = true; #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Dispatching batched input event pending notification.", getInputChannelName()); #endif - env->CallVoidMethod(mReceiverObjGlobal, + env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchBatchedInputEventPending); if (env->ExceptionCheck()) { ALOGE("Exception dispatching batched input events."); @@ -183,6 +195,15 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, assert(inputEvent); if (!skipCallbacks) { + if (!receiverObj.get()) { + receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal)); + if (!receiverObj.get()) { + ALOGW("channel '%s' ~ Receiver object was finalized " + "without being disposed.", getInputChannelName()); + return DEAD_OBJECT; + } + } + jobject inputEventObj; switch (inputEvent->getType()) { case AINPUT_EVENT_TYPE_KEY: @@ -210,12 +231,13 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName()); #endif - env->CallVoidMethod(mReceiverObjGlobal, + env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); if (env->ExceptionCheck()) { ALOGE("Exception dispatching input event."); skipCallbacks = true; } + env->DeleteLocalRef(inputEventObj); } else { ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName()); skipCallbacks = true; @@ -229,7 +251,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, } -static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, +static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) { sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); @@ -245,7 +267,7 @@ static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, } sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, - receiverObj, inputChannel, messageQueue); + receiverWeak, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { String8 message; @@ -293,7 +315,7 @@ static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint rece static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "nativeInit", - "(Landroid/view/InputEventReceiver;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", + "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", (void*)nativeInit }, { "nativeDispose", "(I)V", (void*)nativeDispose }, diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 2306cf2..b46eb4b 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -35,6 +35,8 @@ #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" +#include <ScopedLocalRef.h> + namespace android { static struct { @@ -47,7 +49,7 @@ static struct { class NativeInputEventSender : public LooperCallback { public: NativeInputEventSender(JNIEnv* env, - jobject senderObj, const sp<InputChannel>& inputChannel, + jobject senderWeak, const sp<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue); status_t initialize(); @@ -59,7 +61,7 @@ protected: virtual ~NativeInputEventSender(); private: - jobject mSenderObjGlobal; + jobject mSenderWeakGlobal; InputPublisher mInputPublisher; sp<MessageQueue> mMessageQueue; KeyedVector<uint32_t, uint32_t> mPublishedSeqMap; @@ -75,11 +77,11 @@ private: NativeInputEventSender::NativeInputEventSender(JNIEnv* env, - jobject senderObj, const sp<InputChannel>& inputChannel, + jobject senderWeak, const sp<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) : - mSenderObjGlobal(env->NewGlobalRef(senderObj)), + mSenderWeakGlobal(env->NewGlobalRef(senderWeak)), mInputPublisher(inputChannel), mMessageQueue(messageQueue), - mNextPublishedSeq(0) { + mNextPublishedSeq(1) { #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Initializing input event sender.", getInputChannelName()); #endif @@ -87,7 +89,7 @@ NativeInputEventSender::NativeInputEventSender(JNIEnv* env, NativeInputEventSender::~NativeInputEventSender() { JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->DeleteGlobalRef(mSenderObjGlobal); + env->DeleteGlobalRef(mSenderWeakGlobal); } status_t NativeInputEventSender::initialize() { @@ -178,6 +180,7 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName()); #endif + ScopedLocalRef<jobject> senderObj(env, NULL); bool skipCallbacks = false; for (;;) { uint32_t publishedSeq; @@ -205,7 +208,16 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { #endif if (!skipCallbacks) { - env->CallVoidMethod(mSenderObjGlobal, + if (!senderObj.get()) { + senderObj.reset(jniGetReferent(env, mSenderWeakGlobal)); + if (!senderObj.get()) { + ALOGW("channel '%s' ~ Sender object was finalized " + "without being disposed.", getInputChannelName()); + return DEAD_OBJECT; + } + } + + env->CallVoidMethod(senderObj.get(), gInputEventSenderClassInfo.dispatchInputEventFinished, jint(seq), jboolean(handled)); if (env->ExceptionCheck()) { @@ -218,7 +230,7 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { } -static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderObj, +static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak, jobject inputChannelObj, jobject messageQueueObj) { sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); @@ -234,7 +246,7 @@ static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderObj, } sp<NativeInputEventSender> sender = new NativeInputEventSender(env, - senderObj, inputChannel, messageQueue); + senderWeak, inputChannel, messageQueue); status_t status = sender->initialize(); if (status) { String8 message; @@ -277,7 +289,7 @@ static jboolean nativeSendMotionEvent(JNIEnv* env, jclass clazz, jint senderPtr, static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "nativeInit", - "(Landroid/view/InputEventSender;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", + "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", (void*)nativeInit }, { "nativeDispose", "(I)V", (void*)nativeDispose }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 666d1c6..ffceb68 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2301,6 +2301,12 @@ </intent-filter> </receiver> + <receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver" > + <intent-filter> + <action android:name="android.intent.action.UPDATE_INTENT_FIREWALL" /> + </intent-filter> + </receiver> + <receiver android:name="com.android.server.updates.SmsShortCodesInstallReceiver" > <intent-filter> <action android:name="android.intent.action.UPDATE_SMS_SHORT_CODES" /> diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_dark.9.png Binary files differnew file mode 100644 index 0000000..7c5826f --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_dark.9.png diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_light.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_light.9.png Binary files differnew file mode 100644 index 0000000..974a292 --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_focused_light.9.png diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_dark.9.png Binary files differnew file mode 100644 index 0000000..b3196c9 --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_dark.9.png diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_light.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_light.9.png Binary files differnew file mode 100644 index 0000000..1f833d3 --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_normal_light.9.png diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_dark.9.png Binary files differnew file mode 100644 index 0000000..e969abc --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_dark.9.png diff --git a/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_light.9.png b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_light.9.png Binary files differnew file mode 100644 index 0000000..3adbc84 --- /dev/null +++ b/core/res/res/drawable-ldrtl-hdpi/quickcontact_badge_overlay_pressed_light.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_dark.9.png Binary files differnew file mode 100644 index 0000000..a321836 --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_dark.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_light.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_light.9.png Binary files differnew file mode 100644 index 0000000..4c5d692 --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_focused_light.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_dark.9.png Binary files differnew file mode 100644 index 0000000..6199dc5 --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_dark.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_light.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_light.9.png Binary files differnew file mode 100644 index 0000000..1b0905a --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_normal_light.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_dark.9.png Binary files differnew file mode 100644 index 0000000..c6d7868 --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_dark.9.png diff --git a/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_light.9.png b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_light.9.png Binary files differnew file mode 100644 index 0000000..179644c --- /dev/null +++ b/core/res/res/drawable-ldrtl-mdpi/quickcontact_badge_overlay_pressed_light.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_dark.9.png Binary files differnew file mode 100644 index 0000000..039a056 --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_dark.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_light.9.png Binary files differnew file mode 100644 index 0000000..c8d68c5 --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_focused_light.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_dark.9.png Binary files differnew file mode 100644 index 0000000..1fef1ad --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_dark.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_light.9.png Binary files differnew file mode 100644 index 0000000..6b22d44 --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_normal_light.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_dark.9.png Binary files differnew file mode 100644 index 0000000..c219527 --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_dark.9.png diff --git a/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_light.9.png Binary files differnew file mode 100644 index 0000000..2a1d508 --- /dev/null +++ b/core/res/res/drawable-ldrtl-xhdpi/quickcontact_badge_overlay_pressed_light.9.png diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 8a956d0..1e266ed 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1470,10 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Jy het jou ontsluitpatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer verkeerdelik geteken. Na nog <xliff:g id="NUMBER_1">%d</xliff:g> onsuksesvolle pogings, sal jy gevra word om jou foon te ontsluit deur middel van \'n e-posrekening."\n\n" Probeer weer oor <xliff:g id="NUMBER_2">%d</xliff:g> sekondes."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Verwyder"</string> - <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Verhoog volume bo aanbevole vlak?"\n"Deur vir lang tydperke na hoë volume te luister, kan jou gehoor beskadig word."</string> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Verhoog volume bo aanbevole vlak?"\n"Deur vir lang tydperke teen hoë volume te luister, kan jou gehoor beskadig word."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Hou aan met twee vingers inhou om toeganklikheid te aktiveer."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Toeganklikheid geaktiveer."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Toeganklikheid gekanselleer."</string> <string name="user_switched" msgid="3768006783166984410">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g> ."</string> <string name="owner_name" msgid="2716755460376028154">"Eienaar"</string> + <string name="error_message_title" msgid="4510373083082500195">"Fout"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Hierdie program werk nie met rekeninge vir beperkte gebruikers nie"</string> + <string name="app_not_found" msgid="3429141853498927379">"Geen program gevind om hierdie handeling te hanteer nie"</string> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 103f0b7..54dfd50 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"ድምጽ ለመቅረጽ ወደ ማይክሮፎኑ ቀጥተኛ መዳረሻ።"</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"ካሜራ"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"ለካሜራ ምስል ወይም ቪዲዮ ለመቅረጽ ቀጥተኛ መዳረሻ።"</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"ማያ ገጽ ቆልፍ"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"በመሣሪያዎ ላይ ያለውን የመቆለፊያ ማያ ገጽ ባህሪያት ላይ ተጽዕኖ የመፍጠር ችሎታ።"</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"የመተግበሪያዎችህ መረጃ"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"በመሣሪያህ ላይ ያሉ የሌሎች መተግበሪያዎች ባህሪዎች ላይ ተፅዕኖ የማሳረፍ ችሎታ።"</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"ልጣፍ"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"የፅሁፍ እርምጃዎች"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"የማከማቻ ቦታ እያለቀ ነው"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> በማሄድ ላይ"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> በአሁኑ ጊዜ እያሄደ ነው"</string> <string name="ok" msgid="5970060430562524910">"እሺ"</string> <string name="cancel" msgid="6442560571259935130">"ይቅር"</string> <string name="yes" msgid="5362982303337969312">"እሺ"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"ለ፦"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"የሚፈለገውን ፒን ተይብ፦"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ፒን፦"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"ጡባዊው ከ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ጋር ተገናኝቶ ባለበት ጊዜ በጊዜያዊነት ከWi-Fi ጋር ይላቀቃል"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"ስልኩ ከ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ጋር ተገናኝቶ ባለበት ጊዜ በጊዜያዊነት ከWi-Fi ጋር ያለው ግንኙነት ይቋረጣል"</string> <string name="select_character" msgid="3365550120617701745">"ቁምፊ አስገባ"</string> <string name="sms_control_title" msgid="7296612781128917719">"የSMS መልዕክቶች መበላክ ላይ"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"የመክፈቻ ስርዓተ ጥለቱን <xliff:g id="NUMBER_0">%d</xliff:g> ጊዜ በትክክል አልሳሉትም። ከ<xliff:g id="NUMBER_1">%d</xliff:g> ተጨማሪ ያልተሳኩ ሙከራዎች በኋላ የኢሜይል መለያ ተጠቅመው ስልክዎን እንዲከፍቱ ይጠየቃሉ።"\n\n"እባክዎ ከ<xliff:g id="NUMBER_2">%d</xliff:g> ሰከንዶች በኋላ እንደገና ይሞክሩ።"</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"አስወግድ"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"ድምጽ ከሚመከረው መጠን በላይ ይጨመር?"\n"ለረጅም ጊዜ በከፍተኛ ድምጽ መስማት የመስማት ችሎታዎን ሊጎዳ ይችላል።"</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"ተደራሽነትን ለማንቃት ሁለት ጣቶችዎን ባሉበት ያቆዩዋቸው።"</string> <string name="accessibility_enabled" msgid="1381972048564547685">"ተደራሽነት ነቅቷል።"</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"ተደራሽነት ተሰርዟል።"</string> <string name="user_switched" msgid="3768006783166984410">"የአሁኑ ተጠቃሚ <xliff:g id="NAME">%1$s</xliff:g>።"</string> <string name="owner_name" msgid="2716755460376028154">"ባለቤት"</string> + <string name="error_message_title" msgid="4510373083082500195">"ስህተት"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"ይህ መተግበሪያ የተገደቡ ተጠቃሚዎች መለያዎችን አይደግፍም"</string> + <string name="app_not_found" msgid="3429141853498927379">"ይህን እርምጃ የሚያከናውን ምንም መተግበሪያ አልተገኘም"</string> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index b8fc281..0c6cd78 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"الدخول المباشر إلى الميكروفون لتسجيل الصوت."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"الكاميرا"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"الدخول المباشر إلى الكاميرا لالتقاط صورة أو تصوير مقطع فيديو."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"تأمين الشاشة"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"القدرة على التأثير على سلوك شاشة التأمين في جهازك."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"معلومات التطبيقات"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"القدرة على التأثير في سلوك التطبيقات الأخرى بجهازك."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"الخلفية"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"إجراءات النص"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"مساحة التخزين منخفضة"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"قد لا تعمل بعض وظائف النظام"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> قيد التشغيل"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> قيد التشغيل حاليًا"</string> <string name="ok" msgid="5970060430562524910">"موافق"</string> <string name="cancel" msgid="6442560571259935130">"إلغاء"</string> <string name="yes" msgid="5362982303337969312">"موافق"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"إلى:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"اكتب رقم التعريف الشخصي المطلوب:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"رقم التعريف الشخصي:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"سيتم قطع اتصال الجهاز اللوحي مؤقتًا بشبكة Wi-Fi في الوقت الذي يكون فيه متصلاً بـ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"سيتم قطع اتصال الهاتف مؤقتًا بشبكة Wi-Fi في الوقت الذي يكون فيه متصلاً بـ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"إدراج حرف"</string> <string name="sms_control_title" msgid="7296612781128917719">"إرسال رسائل قصيرة SMS"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"لقد رسمت نقش إلغاء التأمين بشكل غير صحيح <xliff:g id="NUMBER_0">%d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%d</xliff:g> من المحاولات غير الناجحة الأخرى، ستُطالب بإلغاء تأمين الهاتف باستخدام حساب بريد إلكتروني لإلغاء تأمين الهاتف."\n\n" أعد المحاولة خلال <xliff:g id="NUMBER_2">%d</xliff:g> ثانية."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"إزالة"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟"\n"قد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"اضغط بإصبعين لأسفل مع الاستمرار لتمكين تسهيل الدخول."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"تم تمكين إمكانية الدخول."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"تم إلغاء تسهيل الدخول."</string> <string name="user_switched" msgid="3768006783166984410">"المستخدم الحالي <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"المالك"</string> + <string name="error_message_title" msgid="4510373083082500195">"خطأ"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"لا يوفر هذا التطبيق حسابات للمستخدمين المقيّدين"</string> + <string name="app_not_found" msgid="3429141853498927379">"لم يتم العثور على تطبيق يمكنه التعامل مع هذا الإجراء."</string> </resources> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index eb7c6cd..e6c9ee7 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Даступнасць адменена."</string> <string name="user_switched" msgid="3768006783166984410">"Бягучы карыстальнік <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Уладальнік"</string> + <string name="error_message_title" msgid="4510373083082500195">"Памылка"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Гэтае прыкладанне не падтрымлівае уліковыя запісы для карыстальнікаў з абмежаванымі правамі"</string> + <string name="app_not_found" msgid="3429141853498927379">"Прыкладанне для гэтага дзеяння не знойдзенае"</string> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 4831909..79600f7 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Осъществяване на директен достъп до микрофона с цел записване на звук."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Камера"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Осъществяване на директен достъп до камерата с цел заснемане на снимки или видеоклипове."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Заключване на екрана"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Възможност за оказване на влияние върху поведението на заключения екран на устройството ви."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Информация за приложенията ви"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Възможност за оказване на влияние върху поведението на други приложения на устройството ви."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Тапет"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Действия с текста"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Мястото в хранилището е на изчерпване"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Възможно е някои функции на системата да не работят"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> се изпълнява"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"Понастоящем <xliff:g id="APP_NAME">%1$s</xliff:g> се изпълнява"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> <string name="cancel" msgid="6442560571259935130">"Отказ"</string> <string name="yes" msgid="5362982303337969312">"OK"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"До:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Въведете задължителния ПИН:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ПИН:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Таблетът временно ще прекъсне връзката с Wi-Fi, докато е свързан с/ъс <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Телефонът временно ще прекрати връзката с Wi-Fi, докато е свързан с/ъс <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Вмъкване на знак"</string> <string name="sms_control_title" msgid="7296612781128917719">"Изпращане на SMS съобщения"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Начертахте неправилно фигурата си за отключване <xliff:g id="NUMBER_0">%d</xliff:g> пъти. След още <xliff:g id="NUMBER_1">%d</xliff:g> неуспешни опита ще бъдете помолени да отключите телефона посредством имейл адрес."\n\n" Опитайте отново след <xliff:g id="NUMBER_2">%d</xliff:g> секунди."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Премахване"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Да се увеличи ли силата на звука над препоръчаното ниво?"\n"Продължителното слушане при висока сила на звука може да увреди слуха ви."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Продължете да натискате с два пръста, за да активирате функцията за достъпност."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Достъпността е активирана."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Функцията за достъпност е анулирана."</string> <string name="user_switched" msgid="3768006783166984410">"Текущ потребител <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Собственик"</string> + <string name="error_message_title" msgid="4510373083082500195">"Грешка"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Това приложение не поддържа профили за потребители с ограничения"</string> + <string name="app_not_found" msgid="3429141853498927379">"Няма намерено приложение за извършване на това действие"</string> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 365186c..975dc40 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accessibilitat cancel·lada."</string> <string name="user_switched" msgid="3768006783166984410">"Usuari actual: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Propietari"</string> + <string name="error_message_title" msgid="4510373083082500195">"Error"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Aquesta aplicació no admet comptes per a usuaris limitats"</string> + <string name="app_not_found" msgid="3429141853498927379">"No s\'ha trobat cap aplicació per processar aquesta acció"</string> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 621067f..0adebe9 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Usnadnění zrušeno."</string> <string name="user_switched" msgid="3768006783166984410">"Aktuální uživatel je <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Vlastník"</string> + <string name="error_message_title" msgid="4510373083082500195">"Chyba"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Tato aplikace u omezeného počtu uživatelů nepodporuje účty"</string> + <string name="app_not_found" msgid="3429141853498927379">"Aplikace potřebná k provedení této akce nebyla nalezena"</string> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 3dcd8f3..d2676c9 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -201,7 +201,7 @@ <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Direkte adgang til kamera, så der kan tages billeder eller optages video."</string> <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Lås skærm"</string> - <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Evne til at påvirke låseskærmens adfærd på enheden."</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Evne til at påvirke skærmlåsens adfærd på enheden."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Oplysninger om dine applikationer"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Evne til at påvirke andre applikationers adfærd på din enhed."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Baggrund"</string> @@ -1143,7 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Til:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv den påkrævede pinkode:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pinkode:"</string> - <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Telefonens Wi-Fi-forbindelse vil midlertidigt blive afbrudt, når den er tilsluttet <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Wi-Fi-forbindelse til tabletten vil midlertidigt blive afbrudt, når den er tilsluttet <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Telefonens Wi-Fi-forbindelse vil midlertidigt blive afbrudt, når den er tilsluttet <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Indsæt tegn"</string> <string name="sms_control_title" msgid="7296612781128917719">"Sender sms-beskeder"</string> @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Tilgængelighed er annulleret."</string> <string name="user_switched" msgid="3768006783166984410">"Nuværende bruger <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Ejer"</string> + <string name="error_message_title" msgid="4510373083082500195">"Fejl"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Denne applikation understøtter ikke konti for brugere med begrænsede rettigheder"</string> + <string name="app_not_found" msgid="3429141853498927379">"Der blev ikke fundet nogen applikation, der kan håndtere denne handling"</string> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index e15a39f..6a488c7 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Bedienungshilfen abgebrochen"</string> <string name="user_switched" msgid="3768006783166984410">"Aktueller Nutzer <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="owner_name" msgid="2716755460376028154">"Eigentümer"</string> + <string name="error_message_title" msgid="4510373083082500195">"Fehler"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Diese App unterstützt keine Konten für eingeschränkte Nutzer."</string> + <string name="app_not_found" msgid="3429141853498927379">"Für diese Aktion wurde keine App gefunden."</string> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 2dbb82b..21b014c 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Η λειτουργία προσβασιμότητας ακυρώθηκε."</string> <string name="user_switched" msgid="3768006783166984410">"Τρέχων χρήστης <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Κάτοχος"</string> + <string name="error_message_title" msgid="4510373083082500195">"Σφάλμα"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Αυτή η εφαρμογή δεν υποστηρίζει λογαριασμούς για περιορισμένους χρήστες"</string> + <string name="app_not_found" msgid="3429141853498927379">"Δεν υπάρχει εφαρμογή για τη διαχείριση αυτής της ενέργειας"</string> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 9fab86e..ae698e1 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accessibility cancelled."</string> <string name="user_switched" msgid="3768006783166984410">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Owner"</string> + <string name="error_message_title" msgid="4510373083082500195">"Error"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"This application does not support accounts for limited users"</string> + <string name="app_not_found" msgid="3429141853498927379">"No application found to handle this action"</string> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index ee18e9e..f3f0afc 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Se canceló la accesibilidad."</string> <string name="user_switched" msgid="3768006783166984410">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="owner_name" msgid="2716755460376028154">"Propietario"</string> + <string name="error_message_title" msgid="4510373083082500195">"Error"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Esta aplicación no admite cuentas para usuarios restringidos."</string> + <string name="app_not_found" msgid="3429141853498927379">"No se encontró una aplicación para manejar esta acción."</string> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 50fb83c..d3e63af 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accesibilidad cancelada"</string> <string name="user_switched" msgid="3768006783166984410">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="owner_name" msgid="2716755460376028154">"Propietario"</string> + <string name="error_message_title" msgid="4510373083082500195">"Error"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Esta aplicación no admite cuentas de usuarios limitados."</string> + <string name="app_not_found" msgid="3429141853498927379">"No se ha encontrado ninguna aplicación que pueda realizar esta acción."</string> </resources> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 3abaf62..bb00bfc 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Otsene juurdepääs mikrofonile heli salvestamiseks."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Kaamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Otsene juurdepääs kaamerale fotode või videote jäädvustamiseks."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Lukustuskuva"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Võime mõjutada lukustuskuva käitumist seadmes."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Teie rakenduste teave"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Võime mõjutada teiste seadmes olevate rakenduste käitumist."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Taustapilt"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoimingud"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Talletusruum saab täis"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Mõned süsteemifunktsioonid ei pruugi töötada"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> töötab"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> töötab praegu"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> <string name="cancel" msgid="6442560571259935130">"Tühista"</string> <string name="yes" msgid="5362982303337969312">"OK"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Saaja:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Sisestage nõutav PIN-kood:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kood:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tahvelarvuti ühendus WiFi-ga katkestatakse ajutiselt, kui see on ühendatud seadmega <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Telefoni ühendus WiFi-ga katkestatakse ajutiselt, kui see on ühendatud seadmega <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Sisesta tähemärk"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS-sõnumite saatmine"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Joonistasite oma avamismustri <xliff:g id="NUMBER_0">%d</xliff:g> korda valesti. Pärast veel <xliff:g id="NUMBER_1">%d</xliff:g> ebaõnnestunud katset palutakse teil telefon avada meilikontoga."\n\n" Proovige uuesti <xliff:g id="NUMBER_2">%d</xliff:g> sekundi pärast."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Eemalda"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Kas suurendada helitugevust üle soovitatud taseme?"\n"Pikaajaline suure helitugevusega muusika kuulamine võib kahjustada kuulmist."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Hõlbustuse lubamiseks hoidke kaht sõrme all."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Hõlbustus on lubatud."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Hõlbustus on tühistatud."</string> <string name="user_switched" msgid="3768006783166984410">"Praegune kasutaja <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Omanik"</string> + <string name="error_message_title" msgid="4510373083082500195">"Viga"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Rakendus ei toeta piiratud õigustega kasutajate kontosid"</string> + <string name="app_not_found" msgid="3429141853498927379">"Selle toimingu käsitlemiseks ei leitud ühtegi rakendust"</string> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index f8c1629..322cbee 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"مستقیم به میکروفن برای ضبط صدا دسترسی داشته باشید."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"دوربین"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"مستقیم به دوربین برای عکس گرفتن یا ضبط فیلم دسترسی داشته باشید."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"صفحه قفل"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"امکان تاثیرگذاری بر روی رفتار دستگاه در زمانی که صفحه آن قفل شده است."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"اطلاعات برنامههای شما"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"میتواند بر عملکرد برنامههای دیگر روی دستگاه اثر بگذارد."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"تصویر زمینه"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"عملکردهای متنی"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"فضای ذخیرهسازی رو به اتمام است"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"برخی از عملکردهای سیستم ممکن است کار نکنند"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> در حال اجرا"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> در حال حاضر در حال اجرا است"</string> <string name="ok" msgid="5970060430562524910">"تأیید"</string> <string name="cancel" msgid="6442560571259935130">"لغو"</string> <string name="yes" msgid="5362982303337969312">"تأیید"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"به:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"پین لازم را تایپ کنید:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"پین:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"در حین اتصال به <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ارتباط این رایانه لوحی با Wi-Fi موقتاً قطع خواهد شد."</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"این گوشی بهطور موقت از Wi-Fi قطع خواهد شد، در حالی که به <xliff:g id="DEVICE_NAME">%1$s</xliff:g> وصل است"</string> <string name="select_character" msgid="3365550120617701745">"درج نویسه"</string> <string name="sms_control_title" msgid="7296612781128917719">"ارسال پیامک ها"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل تلفن خود را باز کنید."\n\n" لطفاً پس از <xliff:g id="NUMBER_2">%d</xliff:g> ثانیه دوباره امتحان کنید."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"حذف"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"صدا به بالاتر از سطح توصیه شده افزایش یابد؟"\n"گوش دادن به صدای بلند برای مدت طولانی میتواند به شنوایی شما آسیب برساند."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"برای فعال کردن قابلیت دسترسی، با دو انگشت خود همچنان به طرف پایین فشار دهید."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"قابلیت دسترسی فعال شد."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"قابلیت دسترسی لغو شد."</string> <string name="user_switched" msgid="3768006783166984410">"کاربر کنونی <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"دارنده"</string> + <string name="error_message_title" msgid="4510373083082500195">"خطا"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"این برنامه حسابهای تعداد محدودی از کاربران را پشتیبانی نمیکند"</string> + <string name="app_not_found" msgid="3429141853498927379">"برنامهای برای انجام این عملکرد موجود نیست"</string> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index da5c2db..0f71020 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Äänen tallentamiseen käytettävän mikrofonin käyttöoikeus."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Kuvien tai videon tallentamiseen käytettävän kameran käyttöoikeus."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Lukitusruutu"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Lupa vaikuttaa laitteesi lukitusruudun toimintaan."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Sovelluksiesi tiedot"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Mahdollisuus vaikuttaa muiden laitteen sovelluksien käytökseen."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Taustakuva"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoiminnot"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Tallennustila loppumassa"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Kaikki järjestelmätoiminnot eivät välttämättä toimi"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> on käynnissä"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> on käynnissä"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> <string name="cancel" msgid="6442560571259935130">"Peruuta"</string> <string name="yes" msgid="5362982303337969312">"OK"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kohde:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Kirjoita pyydetty PIN-koodi:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-koodi:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tablet-laitteen yhteys wifi-verkkoon katkaistaan väliaikaisesti tabletin ollessa yhdistettynä laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Puhelimen yhteys wifi-verkkoon katkaistaan väliaikaisesti puhelimen ollessa yhdistettynä laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Lisää merkki"</string> <string name="sms_control_title" msgid="7296612781128917719">"Tekstiviestien lähettäminen"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Piirsit lukituksenpoistokuvion väärin <xliff:g id="NUMBER_0">%d</xliff:g> kertaa. Jos piirrät kuvion väärin vielä <xliff:g id="NUMBER_1">%d</xliff:g> kertaa, sinua pyydetään poistamaan puhelimesi lukitus sähköpostitilin avulla."\n\n" Yritä uudelleen <xliff:g id="NUMBER_2">%d</xliff:g> sekunnin kuluttua."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Poista"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Nostetaanko äänenvoimakkuus suositeltua tasoa voimakkaammaksi?"\n"Jos kuuntelet suurella äänenvoimakkuudella pitkiä aikoja, kuulosi voi vahingoittua."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Ota esteettömyystila käyttöön koskettamalla pitkään kahdella sormella."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Esteettömyystila käytössä."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Esteettömyystila peruutettu."</string> <string name="user_switched" msgid="3768006783166984410">"Nykyinen käyttäjä: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Omistaja"</string> + <string name="error_message_title" msgid="4510373083082500195">"Virhe"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Tämä sovellus ei tue rajoitettujen käyttäjien tilejä"</string> + <string name="app_not_found" msgid="3429141853498927379">"Tätä toimintoa käsittelevää sovellusta ei löydy"</string> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 1bc0fe4..2026f56 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accessibilité annulée."</string> <string name="user_switched" msgid="3768006783166984410">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="owner_name" msgid="2716755460376028154">"Propriétaire"</string> + <string name="error_message_title" msgid="4510373083082500195">"Erreur"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Les comptes des utilisateurs en accès limité ne sont pas acceptés pour cette application."</string> + <string name="app_not_found" msgid="3429141853498927379">"Aucune application trouvée pour gérer cette action."</string> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index cbec3fe..1b3e6d7 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"ऑडियो रिकॉर्ड करने के लिए माइक्रोफ़ोन पर सीधी पहुंच."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"कैमरा"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"चित्र या वीडियो कैप्चर के लिए कैमरे पर सीधी पहुंच."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"स्क्रीन लॉक करें"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"आपके उपकरण की लॉक स्क्रीन का व्यवहार प्रभावित करने की क्षमता."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"आपके एप्लिकेशन की जानकारी"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"अपने उपकरण पर अन्य एप्लिकेशन के व्यवहार को प्रभावित करने की क्षमता."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"वॉलपेपर"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"पाठ क्रियाएं"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"संग्रहण स्थान समाप्त हो रहा है"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"हो सकता है कुछ सिस्टम फ़ंक्शन कार्य न करें"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> चल रहा है"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> वर्तमान में चल रहा है"</string> <string name="ok" msgid="5970060430562524910">"ठीक"</string> <string name="cancel" msgid="6442560571259935130">"रद्द करें"</string> <string name="yes" msgid="5362982303337969312">"ठीक"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"प्रति:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"आवश्यक पिन लिखें:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"पिन:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> से कनेक्ट रहने पर टेबलेट Wi-Fi से अस्थायी रूप से डिस्कनेक्ट हो जाएगा"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"फ़ोन <xliff:g id="DEVICE_NAME">%1$s</xliff:g> से कनेक्ट रहते समय Wi-Fi से अस्थायी रूप से डिस्कनेक्ट हो जाएगा"</string> <string name="select_character" msgid="3365550120617701745">"वर्ण सम्मिलित करें"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS संदेश भेज रहा है"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"आपने अपने अनलॉक प्रतिमान को <xliff:g id="NUMBER_0">%d</xliff:g> बार गलत तरीके से आरेखित किया है. <xliff:g id="NUMBER_1">%d</xliff:g> और असफल प्रयासों के बाद, आपसे अपने फ़ोन को किसी ईमेल खाते का उपयोग करके अनलॉक करने के लिए कहा जाएगा."\n\n" <xliff:g id="NUMBER_2">%d</xliff:g> सेकंड में पुन: प्रयास करें."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"निकालें"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"वॉल्यूम को उपरोक्त अनुशंसित स्तर तक बढ़ाएं?"\n"लंबे समय तक अधिक वॉल्यूम पर सुनने से आपकी सुनने की क्षमता को क्षति पहुंच सकती है."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"पहुंच-योग्यता को सक्षम करने के लिए दो अंगुलियों से नीचे दबाए रखें."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"पहुंच-योग्यता सक्षम कर दी है."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"पहुंच-योग्यता रद्द की गई."</string> <string name="user_switched" msgid="3768006783166984410">"वर्तमान उपयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"स्वामी"</string> + <string name="error_message_title" msgid="4510373083082500195">"त्रुटि"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"यह एप्लिकेशन सीमित उपयोगकर्ताओं के खातों का समर्थन नहीं करता है"</string> + <string name="app_not_found" msgid="3429141853498927379">"इस कार्यवाही को प्रबंधित करने के लिए कोई एप्लिकेशन नहीं मिला"</string> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index c196b59..7b1eee3 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1143,7 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Prima:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Upišite potreban PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tabletno računalo privremeno će se isključiti s Wi-Fija dok je povezano s uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tablet će se privremeno isključiti s Wi-Fija dok je povezan s uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Telefon će se privremeno isključiti s Wi-Fija dok je povezan s uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Umetni znak"</string> <string name="sms_control_title" msgid="7296612781128917719">"Slanje SMS poruka"</string> @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Pristupačnost otkazana."</string> <string name="user_switched" msgid="3768006783166984410">"Trenutačni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Vlasnik"</string> + <string name="error_message_title" msgid="4510373083082500195">"Pogreška"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Aplikacija ne podržava račune za ograničene korisnike"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nije pronađena aplikacija za upravljanje ovom radnjom"</string> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index ff978a0..fc1d663 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Hozzáférés megszakítva."</string> <string name="user_switched" msgid="3768006783166984410">"<xliff:g id="NAME">%1$s</xliff:g> az aktuális felhasználó."</string> <string name="owner_name" msgid="2716755460376028154">"Tulajdonos"</string> + <string name="error_message_title" msgid="4510373083082500195">"Hiba"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ez az alkalmazás nem támogatja a korlátozott jogokkal rendelkező felhasználói fiókokat."</string> + <string name="app_not_found" msgid="3429141853498927379">"Nincs megfelelő alkalmazás a művelet elvégzésére."</string> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index fee8fc4..2a9f2fc 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -200,8 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Akses langsung ke mikrofon untuk merekam audio."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Akses langsung ke kamera untuk gambar atau tangkapan video."</string> - <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Layar pengunci"</string> - <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Kemampuan untuk memengaruhi perilaku layar pengunci di perangkat Anda."</string> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Layar terkunci"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Kemampuan untuk memengaruhi perilaku layar terkunci di perangkat Anda."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Informasi aplikasi Anda"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Kemampuan untuk memengaruhi perilaku aplikasi lain pada perangkat Anda."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Wallpaper"</string> @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Aksesibilitas dibatalkan."</string> <string name="user_switched" msgid="3768006783166984410">"Pengguna saat ini <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Pemilik"</string> + <string name="error_message_title" msgid="4510373083082500195">"Kesalahan"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Aplikasi ini tidak mendukung akun untuk pengguna terbatas"</string> + <string name="app_not_found" msgid="3429141853498927379">"Tidak ada aplikasi yang ditemukan untuk menangani tindakan ini"</string> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 0cd7322..2273459 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accessibilità annullata."</string> <string name="user_switched" msgid="3768006783166984410">"Utente corrente <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Proprietario"</string> + <string name="error_message_title" msgid="4510373083082500195">"Errore"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Questa applicazione non supporta account di utenti con limitazioni"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nessuna applicazione trovata in grado di gestire questa azione"</string> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 0139b29..8c7c74e 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"נגישות בוטלה."</string> <string name="user_switched" msgid="3768006783166984410">"המשתמש הנוכחי <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"בעלים"</string> + <string name="error_message_title" msgid="4510373083082500195">"שגיאה"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"היישום הזה לא תומך בחשבונות עבור משתמשים מוגבלים"</string> + <string name="app_not_found" msgid="3429141853498927379">"לא נמצא יישום שתומך בפעולה זו"</string> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index af64842..4ec756e 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"ユーザー補助をキャンセルしました。"</string> <string name="user_switched" msgid="3768006783166984410">"現在のユーザーは<xliff:g id="NAME">%1$s</xliff:g>です。"</string> <string name="owner_name" msgid="2716755460376028154">"所有者"</string> + <string name="error_message_title" msgid="4510373083082500195">"エラー"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"このアプリでは限定ユーザー用のアカウントはサポートしていません"</string> + <string name="app_not_found" msgid="3429141853498927379">"この操作を行うアプリが見つかりません"</string> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 5d663d5..e6010ac 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"접근성이 취소되었습니다."</string> <string name="user_switched" msgid="3768006783166984410">"현재 사용자는 <xliff:g id="NAME">%1$s</xliff:g>님입니다."</string> <string name="owner_name" msgid="2716755460376028154">"소유자"</string> + <string name="error_message_title" msgid="4510373083082500195">"오류"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"이 애플리케이션은 제한된 사용자를 위한 계정을 지원하지 않습니다."</string> + <string name="app_not_found" msgid="3429141853498927379">"이 작업을 처리하는 애플리케이션을 찾을 수 없습니다."</string> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 1a48fac..e8d7c26 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Tiesioginė prieiga prie mikrofono, kad būtų galima įrašyti garsą."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Fotoaparatas"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Tiesioginė prieiga prie fotoaparato, kad būtų galima fotografuoti vaizdus arba įrašyti vaizdo įrašus."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Užrakinti ekraną"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Galimybė paveikti užrakinimo ekrano veikimą įrenginyje."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Programų informacija"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Galimybė paveikti kitų įrenginio programų veikimą."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Ekrano fonas"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksto veiksmai"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Mažėja laisvos saugyklos vietos"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Kai kurios sistemos funkcijos gali neveikti"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ paleista"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu paleista"</string> <string name="ok" msgid="5970060430562524910">"Gerai"</string> <string name="cancel" msgid="6442560571259935130">"Atšaukti"</string> <string name="yes" msgid="5362982303337969312">"Gerai"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Skirta:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Įveskite reikiamą PIN kodą:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kodas:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Planšetinis kompiuteris bus laikinai atjungtas nuo „Wi-Fi“, kol jis prijungtas prie „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Telefonas bus laikinai atjungtas nuo „Wi-Fi“, kol bus prijungtas prie „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“"</string> <string name="select_character" msgid="3365550120617701745">"Įterpti simbolį"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS pranešimų siuntimas"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Netinkamai nupiešėte atrakinimo piešinį <xliff:g id="NUMBER_0">%d</xliff:g> k. Po dar <xliff:g id="NUMBER_1">%d</xliff:g> nesėkm. band. būsite paprašyti atrakinti telefoną naudodami „Google“ prisijungimo duomenis."\n\n" Bandykite dar kartą po <xliff:g id="NUMBER_2">%d</xliff:g> sek."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Pašalinti"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Padidinti garsumą viršijant saugų lygį?"\n"Ilgai klausantis dideliu garsumu gali sutrikti klausa."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Laikykite palietę dviem pirštais, kad įgalintumėte pritaikymo neįgaliesiems režimą."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Pritaikymas neįgaliesiems įgalintas."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Pritaikymo neįgaliesiems režimas atšauktas."</string> <string name="user_switched" msgid="3768006783166984410">"Dabartinis naudotojas: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Savininkas"</string> + <string name="error_message_title" msgid="4510373083082500195">"Klaida"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ši programa nepalaiko apribotų naudotojų paskyrų"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nerasta programa šiam veiksmui apdoroti"</string> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 5890f13..f134005 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Pieejamība ir atcelta."</string> <string name="user_switched" msgid="3768006783166984410">"Pašreizējais lietotājs: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Īpašnieks"</string> + <string name="error_message_title" msgid="4510373083082500195">"Kļūda"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Šajā lietojumprogrammā netiek atbalstīti ierobežotu lietotāju konti."</string> + <string name="app_not_found" msgid="3429141853498927379">"Netika atrasta neviena lietojumprogramma, kas var veikt šo darbību."</string> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index be04f9d..3091e7d 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Akses langsung ke mikrofon untuk merakam audio."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Akses langsung ke kamera untuk merakam imej atau video."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Kunci skrin"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Keupayaan untuk mempengaruhi kelakuan skrin kunci pada peranti anda."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Maklumat aplikasi anda"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Keupayaan untuk mempengaruhi tingkah laku aplikasi lain pada peranti anda."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Kertas dinding"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Tindakan teks"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ruang storan semakin berkurangan"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Beberapa fungsi sistem mungkin tidak berfungsi"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> berjalan"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang berjalan"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> <string name="cancel" msgid="6442560571259935130">"Batal"</string> <string name="yes" msgid="5362982303337969312">"OK"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kepada:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Taipkan PIN yang diperlukan:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Sambungan tablet ke Wi-Fi akan diputuskan buat sementara waktu semasa tablet bersambung ke <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Sambungan telefon ke Wi-Fi akan diputuskan buat sementara waktu semasa telefon bersambung ke <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Masukkan aksara"</string> <string name="sms_control_title" msgid="7296612781128917719">"Menghantar mesej SMS"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Anda telah tersilap lukis corak buka kunci sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Selepas <xliff:g id="NUMBER_1">%d</xliff:g> lagi percubaan yang tidak berjaya, anda akan diminta membuka kunci telefon anda menggunakan log masuk Google anda."\n\n" Cuba lagi dalam <xliff:g id="NUMBER_2">%d</xliff:g> saat."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Alih keluar"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Tingkatkan kelantangan melebihi aras yang dicadangkan?"\n"Mendengar pada kelantangan tinggi untuk tempoh yang panjang boleh merosakkan pendengaran anda."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Teruskan menahan dengan dua jari untuk mendayakan kebolehcapaian."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Kebolehcapaian didayakan."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Kebolehcapaian dibatalkan."</string> <string name="user_switched" msgid="3768006783166984410">"Pengguna semasa <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Pemilik"</string> + <string name="error_message_title" msgid="4510373083082500195">"Ralat"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Aplikasi ini tidak menyokong akaun untuk pengguna terhad"</string> + <string name="app_not_found" msgid="3429141853498927379">"Tidak menemui aplikasi untuk mengendalikan tindakan ini"</string> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 5fd9a42..9d39ac8 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Tilgjengelighetstjenesten ble avbrutt."</string> <string name="user_switched" msgid="3768006783166984410">"Gjeldende bruker: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Eier"</string> + <string name="error_message_title" msgid="4510373083082500195">"Feil"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Denne appen støtter ikke kontoer for brukere med begrensninger"</string> + <string name="app_not_found" msgid="3429141853498927379">"Finner ingen apper som kan utføre denne handlingen"</string> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index f5dd3be..b0a6d30 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Toegankelijkheid geannuleerd."</string> <string name="user_switched" msgid="3768006783166984410">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Eigenaar"</string> + <string name="error_message_title" msgid="4510373083082500195">"Fout"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Deze app ondersteunt geen accounts voor beperkte gebruikers"</string> + <string name="app_not_found" msgid="3429141853498927379">"Er is geen app gevonden om deze actie uit te voeren"</string> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index f6ffb20..0414be8 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -744,7 +744,7 @@ <string name="relationTypeDomesticPartner" msgid="6904807112121122133">"Partner życiowy"</string> <string name="relationTypeFather" msgid="5228034687082050725">"Ojciec"</string> <string name="relationTypeFriend" msgid="7313106762483391262">"Znajomy"</string> - <string name="relationTypeManager" msgid="6365677861610137895">"Kierownik"</string> + <string name="relationTypeManager" msgid="6365677861610137895">"Menedżer"</string> <string name="relationTypeMother" msgid="4578571352962758304">"Matka"</string> <string name="relationTypeParent" msgid="4755635567562925226">"Rodzic"</string> <string name="relationTypePartner" msgid="7266490285120262781">"Partner"</string> @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Ułatwienia dostępu zostały anulowane."</string> <string name="user_switched" msgid="3768006783166984410">"Bieżący użytkownik: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Właściciel"</string> + <string name="error_message_title" msgid="4510373083082500195">"Błąd"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ta aplikacja nie obsługuje kont użytkowników z ograniczeniami"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nie znaleziono aplikacji do obsługi tej akcji"</string> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 82a0893..5eba00c 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Acessibilidade cancelada."</string> <string name="user_switched" msgid="3768006783166984410">"<xliff:g id="NAME">%1$s</xliff:g> do utilizador atual."</string> <string name="owner_name" msgid="2716755460376028154">"Proprietário"</string> + <string name="error_message_title" msgid="4510373083082500195">"Erro"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Esta aplicação não suporta contas de utilizadores limitados"</string> + <string name="app_not_found" msgid="3429141853498927379">"Não foram encontradas aplicações para executar esta ação"</string> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 873c9db..6b0d18a 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Acesso direto ao microfone para gravação de áudio."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Câmera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Acesso direto à câmera para captura de imagens ou vídeo."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Tela de bloqueio"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Capacidade de afetar o comportamento da tela de bloqueio no dispositivo."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Informações sobre seus aplicativos"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Capacidade de afetar o comportamento de outros aplicativos no dispositivo."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Plano de fundo"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Ações de texto"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Pouco espaço de armazenamento"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Algumas funções do sistema podem não funcionar"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> em execução"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> está em execução"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> <string name="cancel" msgid="6442560571259935130">"Cancelar"</string> <string name="yes" msgid="5362982303337969312">"OK"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Digite o PIN obrigatório:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"O tablet desconectará temporariamente da rede Wi-Fi enquanto estiver conectado a <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"O telefone desconectará temporariamente da rede Wi-Fi enquanto estiver conectado a <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string> <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Você desenhou sua sequência de desbloqueio incorretamente <xliff:g id="NUMBER_0">%d</xliff:g> vezes. Se fizer mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas incorretas, será solicitado que você use o login do Google para desbloquear."\n\n" Tente novamente em <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Remover"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Aumentar o volume acima do nível recomendado?"\n"A audição em volume elevado por períodos longos pode prejudicar sua audição."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Mantenha pressionado com dois dedos para ativar a acessibilidade."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Acessibilidade ativada."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Acessibilidade cancelada."</string> <string name="user_switched" msgid="3768006783166984410">"Usuário atual <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Proprietário"</string> + <string name="error_message_title" msgid="4510373083082500195">"Erro"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"O aplicativo não suporta contas para usuários limitados"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nenhum aplicativo encontrado para executar a ação"</string> </resources> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index e934a08..99fd59d 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -2391,4 +2391,10 @@ <!-- no translation found for owner_name (2716755460376028154) --> <!-- no translation found for owner_name (3879126011135546571) --> <skip /> + <!-- no translation found for error_message_title (4510373083082500195) --> + <skip /> + <!-- no translation found for app_no_restricted_accounts (5322164210667258876) --> + <skip /> + <!-- no translation found for app_not_found (3429141853498927379) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index fdeeb93..b11cc2f 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Accesibilitatea a fost anulată"</string> <string name="user_switched" msgid="3768006783166984410">"Utilizator curent: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Proprietar"</string> + <string name="error_message_title" msgid="4510373083082500195">"Eroare"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Această aplicație nu acceptă conturile pentru utilizatori cu permisiuni limitate"</string> + <string name="app_not_found" msgid="3429141853498927379">"Nicio aplicație pentru gestionarea acestei acțiuni"</string> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index da1f312..3da7211 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1056,8 +1056,8 @@ <string name="no" msgid="5141531044935541497">"Отмена"</string> <string name="dialog_alert_title" msgid="2049658708609043103">"Внимание"</string> <string name="loading" msgid="7933681260296021180">"Загрузка…"</string> - <string name="capital_on" msgid="1544682755514494298">"ВКЛ"</string> - <string name="capital_off" msgid="6815870386972805832">"ВЫКЛ"</string> + <string name="capital_on" msgid="1544682755514494298">"I"</string> + <string name="capital_off" msgid="6815870386972805832">"O"</string> <string name="whichApplication" msgid="4533185947064773386">"Что использовать?"</string> <string name="alwaysUse" msgid="4583018368000610438">"По умолчанию для этого действия"</string> <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Удаляет настройки по умолчанию в меню \"Настройки > Приложения > Загруженные\"."</string> @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Специальные возможности не будут включены."</string> <string name="user_switched" msgid="3768006783166984410">"Выбран аккаунт пользователя <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Владелец"</string> + <string name="error_message_title" msgid="4510373083082500195">"Ошибка"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Приложение не поддерживает аккаунты с ограниченным доступом"</string> + <string name="app_not_found" msgid="3429141853498927379">"Невозможно обработать это действие"</string> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 94fdd7b..9ffe30d 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Zjednodušenie ovládania bolo zrušené."</string> <string name="user_switched" msgid="3768006783166984410">"Aktuálny používateľ je <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Vlastník"</string> + <string name="error_message_title" msgid="4510373083082500195">"Chyba"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Táto aplikácia nepodporuje účty v prípade používateľov s obmedzením"</string> + <string name="app_not_found" msgid="3429141853498927379">"Aplikácia potrebná na spracovanie tejto akcie sa nenašla"</string> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 1486341..ac1b6ad 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Neposreden dostop do mikrofona za snemanje zvoka."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Fotoaparat"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Neposreden dostop do fotoaparata za fotografiranje ali snemanje videoposnetkov."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Zaklepanje zaslona"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Lahko vpliva na delovanje zaklepanja zaslona v napravi."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Podatki o vaših aplikacijah"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Zmožnost vpliva na delovanje drugih aplikacij v napravi."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Slika za ozadje"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Besedilna dejanja"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Prostor za shranjevanje bo pošel"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Nekatere sistemske funkcije morda ne delujejo"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"Izvaja se aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"Trenutno se izvaja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="ok" msgid="5970060430562524910">"V redu"</string> <string name="cancel" msgid="6442560571259935130">"Prekliči"</string> <string name="yes" msgid="5362982303337969312">"V redu"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Za:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Vnesite zahtevano kodo PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Tablični računalnik bo začasno prekinil povezavo z Wi-Fi-jem, medtem ko je povezan z napravo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Telefon bo začasno prekinil povezavo z Wi-Fi-jem, ko je povezan z napravo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Vstavljanje znaka"</string> <string name="sms_control_title" msgid="7296612781128917719">"Pošiljanje sporočil SMS"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Vzorec za odklepanje ste <xliff:g id="NUMBER_0">%d</xliff:g>-krat napačno vnesli. Po nadaljnjih <xliff:g id="NUMBER_1">%d</xliff:g> neuspešnih poskusih boste pozvani, da odklenete telefon z Googlovimi podatki za prijavo."\n\n"Poskusite znova čez <xliff:g id="NUMBER_2">%d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Odstrani"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Želite povečati glasnost nad varno raven?"\n"Dolgotrajna izpostavljenost glasnemu predvajanju lahko poškoduje sluh."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Če želite omogočiti pripomočke za ljudi s posebnimi potrebami, na zaslonu pridržite z dvema prstoma."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Pripomočki za ljudi s posebnimi potrebami so omogočeni."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Omogočanje pripomočkov za ljudi s posebnimi potrebami preklicano."</string> <string name="user_switched" msgid="3768006783166984410">"Trenutni uporabnik <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Lastnik"</string> + <string name="error_message_title" msgid="4510373083082500195">"Napaka"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ta aplikacija ne podpira računov za uporabnike z omejitvami"</string> + <string name="app_not_found" msgid="3429141853498927379">"Najdena ni bila nobena aplikacija za izvedbo tega dejanja"</string> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 1ec58d4..7e8cf01 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Приступачност је отказана."</string> <string name="user_switched" msgid="3768006783166984410">"Актуелни корисник <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Власник"</string> + <string name="error_message_title" msgid="4510373083082500195">"Грешка"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ова апликација не подржава налоге за кориснике са ограничењем"</string> + <string name="app_not_found" msgid="3429141853498927379">"Није пронађена ниједна апликација која би могла да обави ову радњу"</string> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 6a51baf..5a54f71 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Byte till tillgänglighetsläge avbrutet."</string> <string name="user_switched" msgid="3768006783166984410">"Nuvarande användare: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Ägare"</string> + <string name="error_message_title" msgid="4510373083082500195">"Fel"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Appen har inte stöd för användarkonton med begränsningar"</string> + <string name="app_not_found" msgid="3429141853498927379">"Ingen app som kan hantera åtgärden hittades"</string> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index d1e4270..67b4608 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"Kufikia moja kwa moja kipokea sauti ili kurekodi sauti."</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Kufikia moja kwa moja kamera ya kunasa taswira au video."</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Funga skrini"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Uwezo wa kuathiri tabia ya skrini iliyofungwa kwenye kifaa chako."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Taarifa ya programu zako"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Uwezo wa kuathiri tabia ya programu nyingine kwenye kifaa chako."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Taswira"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"Vitendo vya maandishi"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Nafasi ya kuhafadhi inakwisha"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaendeshwa"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaendeshwa kwa sasa"</string> <string name="ok" msgid="5970060430562524910">"Sawa"</string> <string name="cancel" msgid="6442560571259935130">"Ghairi"</string> <string name="yes" msgid="5362982303337969312">"Sawa"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kwa:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Charaza PIN inayohitajika:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Kompyuta ndogo itaukata muunganisho kwa muda kutoka kwenye Wi-Fi inapokuwa imeunganishwa kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Simu itaukata muunganisho kwa muda kutoka kwenye Wi-Fi inapokuwa imeunganishwa kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"Ingiza kibambo"</string> <string name="sms_control_title" msgid="7296612781128917719">"Inatuma ujumbe wa SMS"</string> @@ -1355,7 +1350,7 @@ <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Imefanyika"</string> <string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"Modi ya mabadiliko"</string> <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Songa"</string> - <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Ingiza"</string> + <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string> <string name="activitychooserview_choose_application" msgid="2125168057199941199">"Chagua programu"</string> <string name="shareactionprovider_share_with" msgid="806688056141131819">"Shiriki na"</string> <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Shiriki na <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Umekosea kuchora mchoro wako wa kufungua mara <xliff:g id="NUMBER_0">%d</xliff:g>. Baada ya majaribio <xliff:g id="NUMBER_1">%d</xliff:g> yasiyofaulu, utaombwa kufungua simu yako kwa kutumia akaunti ya barua pepe."\n\n" Jaribu tena baada ya sekunde <xliff:g id="NUMBER_2">%d</xliff:g>."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Ondoa"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Iongeza sauti zaidi ya kiwango kinachopendekezwa?"\n"Kusikiliza kwa sauti ya juu kwa muda mrefu kunaweza kuharibu uwezo wako wa kusikia."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Endelea kushikilia chini kwa vidole vyako viwili ili kuwezesha ufikivu."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Ufikivu umewezeshwa."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Ufikivu umeghairiwa."</string> <string name="user_switched" msgid="3768006783166984410">"Mtumiaji wa sasa <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Mmiliki"</string> + <string name="error_message_title" msgid="4510373083082500195">"Hitilafu"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Programu hii haiwezi kutumiwa na akaunti za watumiaji waliowekewa vizuizi"</string> + <string name="app_not_found" msgid="3429141853498927379">"Hakuna programu iliyopatikana ili kushughulikia kitendo hiki"</string> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 4bca1b1..2d56e38 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -154,9 +154,9 @@ <string name="global_actions" product="default" msgid="2406416831541615258">"ตัวเลือกโทรศัพท์"</string> <string name="global_action_lock" msgid="2844945191792119712">"ล็อกหน้าจอ"</string> <string name="global_action_power_off" msgid="4471879440839879722">"ปิดเครื่อง"</string> - <string name="global_action_bug_report" msgid="7934010578922304799">"รายงานข้อบกพร่อง"</string> - <string name="bugreport_title" msgid="2667494803742548533">"ใช้รายงานข้อบกพร่อง"</string> - <string name="bugreport_message" msgid="398447048750350456">"การดำเนินการนี้จะรวบรวมข้อมูลเกี่ยวกับสถานะปัจจุบันของอุปกรณ์ของคุณ โดยจะส่งไปในรูปแบบข้อความอีเมล อาจใช้เวลาสักครู่ตั้งแต่เริ่มการสร้างรายงานข้อบกพร่องจนกระทั่งเสร็จสมบูรณ์ โปรดอดทนรอ"</string> + <string name="global_action_bug_report" msgid="7934010578922304799">"รายงานบั๊ก"</string> + <string name="bugreport_title" msgid="2667494803742548533">"ใช้รายงานบั๊ก"</string> + <string name="bugreport_message" msgid="398447048750350456">"การดำเนินการนี้จะรวบรวมข้อมูลเกี่ยวกับสถานะปัจจุบันของอุปกรณ์ของคุณ โดยจะส่งไปในรูปแบบข้อความอีเมล อาจใช้เวลาสักครู่ตั้งแต่เริ่มการสร้างรายงานบั๊กจนกระทั่งเสร็จสมบูรณ์ โปรดอดทนรอ"</string> <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"โหมดปิดเสียง"</string> <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"ปิดเสียงไว้"</string> <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"เปิดเสียงแล้ว"</string> @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"เข้าถึงไมโครโฟนเพื่อบันทึกเสียงโดยตรง"</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"กล้องถ่ายรูป"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"เข้าถึงกล้องถ่ายรูปเพื่อดูภาพและวิดีโอที่ถ่ายไว้โดยตรง"</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"ล็อกหน้าจอ"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"ความสามารถในการส่งผลกระทบต่อพฤติกรรมของหน้าจอล็อกบนอุปกรณ์"</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"ข้อมูลแอปพลิเคชันของคุณ"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"สามารถส่งผลต่อการทำงานของแอปพลิเคชันอื่นในอุปกรณ์ของคุณ"</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"วอลเปเปอร์"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"การทำงานของข้อความ"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"พื้นที่จัดเก็บเหลือน้อย"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"บางฟังก์ชันระบบอาจไม่ทำงาน"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังทำงาน"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังทำงานในขณะนี้"</string> <string name="ok" msgid="5970060430562524910">"ตกลง"</string> <string name="cancel" msgid="6442560571259935130">"ยกเลิก"</string> <string name="yes" msgid="5362982303337969312">"ตกลง"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"ถึง:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"พิมพ์ PIN ที่ต้องการ:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"แท็บเล็ตนี้จะยกเลิกการเชื่อมต่อกับ Wi-Fi ชั่วคราวในขณะที่เชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"โทรศัพท์จะยกเลิกการเชื่อมต่อกับ Wi-Fi ชั่วคราวในขณะที่เชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string> <string name="select_character" msgid="3365550120617701745">"ใส่อักขระ"</string> <string name="sms_control_title" msgid="7296612781128917719">"กำลังส่งข้อความ SMS"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"คุณวาดรูปแบบการปลดล็อกไม่ถูกต้อง <xliff:g id="NUMBER_0">%d</xliff:g> ครั้งแล้ว หากทำไม่สำเร็จอีก <xliff:g id="NUMBER_1">%d</xliff:g> ครั้ง ระบบจะขอให้คุณปลดล็อกโทรศัพท์โดยใช้ับัญชีอีเมล"\n\n" โปรดลองอีกครั้งในอีก <xliff:g id="NUMBER_2">%d</xliff:g> วินาที"</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"นำออก"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"ในกรณีที่ต้องการเพิ่มระดับเสียงจนเกินระดับที่แนะนำ"\n"การฟังเสียงดังเป็นเวลานานอาจทำให้การได้ยินของคุณบกพร่องได้"</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"ใช้สองนิ้วแตะค้างไว้เพื่อเปิดใช้งานการเข้าถึง"</string> <string name="accessibility_enabled" msgid="1381972048564547685">"เปิดใช้งานการเข้าถึงแล้ว"</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"ยกเลิกการเข้าถึงแล้ว"</string> <string name="user_switched" msgid="3768006783166984410">"ผู้ใช้ปัจจุบัน <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="owner_name" msgid="2716755460376028154">"เจ้าของ"</string> + <string name="error_message_title" msgid="4510373083082500195">"ข้อผิดพลาด"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"แอปพลิเคชันนี้ไม่สนับสนุนบัญชีของผู้ใช้บางรายที่ถูกจำกัด"</string> + <string name="app_not_found" msgid="3429141853498927379">"ไม่พบแอปพลิเคชันสำหรับการทำงานนี้"</string> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 5354a54..beeb6c0 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Nakansela ang pagiging naa-access."</string> <string name="user_switched" msgid="3768006783166984410">"Kasalukuyang user <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"May-ari"</string> + <string name="error_message_title" msgid="4510373083082500195">"Error"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Hindi sinusuportahan ng application na ito ang mga account para sa mga limitadong user"</string> + <string name="app_not_found" msgid="3429141853498927379">"Walang nakitang application na mangangasiwa sa pagkilos na ito"</string> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index c5ce4cd..c8d3ae4 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Erişilebilirlik iptal edildi."</string> <string name="user_switched" msgid="3768006783166984410">"Geçerli kullanıcı: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Sahibi"</string> + <string name="error_message_title" msgid="4510373083082500195">"Hata"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Bu uygulama, kısıtlı kullanıcı hesaplarını desteklemiyor"</string> + <string name="app_not_found" msgid="3429141853498927379">"Bu eylemi gerçekleştirecek bir uygulama bulunamadı"</string> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 7c8f192..6f505b1 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Доступність скасовано."</string> <string name="user_switched" msgid="3768006783166984410">"Поточний користувач: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Власник"</string> + <string name="error_message_title" msgid="4510373083082500195">"Помилка"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ця програма не підтримує облікові записи для обмежених користувачів"</string> + <string name="app_not_found" msgid="3429141853498927379">"Не знайдено програму для обробки цієї дії"</string> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index e9ccd36..4fe8d65 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -201,7 +201,7 @@ <string name="permgrouplab_camera" msgid="4820372495894586615">"Máy ảnh"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"Truy cập trực tiếp vào máy ảnh để chụp ảnh hoặc quay video."</string> <string name="permgrouplab_screenlock" msgid="8275500173330718168">"Khóa màn hình"</string> - <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Khả năng ảnh hưởng tới trạng thái màn hình khóa trên thiết bị của bạn."</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"Có thể ảnh hưởng đến thao tác của màn hình khóa trên thiết bị của bạn."</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"Thông tin về các ứng dụng của bạn"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"Khả năng ảnh hưởng tới hoạt động của các ứng dụng khác trên thiết bị của bạn."</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"Hình nền"</string> @@ -1470,10 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Bạn đã <xliff:g id="NUMBER_0">%d</xliff:g> lần vẽ không chính xác hình mở khóa của mình. Sau <xliff:g id="NUMBER_1">%d</xliff:g> lần thử không thành công nữa, bạn sẽ được yêu cầu mở khóa điện thoại bằng tài khoản email."\n\n" Vui lòng thử lại sau <xliff:g id="NUMBER_2">%d</xliff:g> giây."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Xóa"</string> - <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Tăng âm lượng trên mức được đề xuất?"\n"Nghe ở âm lượng cao trong thời gian dài có thể gây hại cho thính giác của bạn."</string> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"Tăng âm lượng trên mức được đề xuất?"\n"Nghe ở mức âm lượng cao trong thời gian dài có thể gây hại cho thính giác của bạn."</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"Tiếp tục giữ hai ngón tay để bật trợ năng."</string> <string name="accessibility_enabled" msgid="1381972048564547685">"Trợ năng đã được bật."</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Đã hủy trợ năng."</string> <string name="user_switched" msgid="3768006783166984410">"Người dùng hiện tại <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Chủ sở hữu"</string> + <string name="error_message_title" msgid="4510373083082500195">"Lỗi"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Ứng dụng này không hỗ trợ tài khoản cho người dùng giới hạn"</string> + <string name="app_not_found" msgid="3429141853498927379">"Không tìm thấy ứng dụng nào để xử lý tác vụ này"</string> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index c768ec8..af1a2ea 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -201,7 +201,7 @@ <string name="permgrouplab_camera" msgid="4820372495894586615">"相机"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"直接使用相机以拍摄图片或视频。"</string> <string name="permgrouplab_screenlock" msgid="8275500173330718168">"锁定屏幕"</string> - <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"能够影响设备的锁定屏幕的行为。"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"影响设备的锁定屏幕。"</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"您的应用信息"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"能够影响设备上其他应用的行为。"</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"壁纸"</string> @@ -1143,7 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"收件人:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"键入所需的 PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"平板电脑连接到<xliff:g id="DEVICE_NAME">%1$s</xliff:g>时会暂时断开与 Wi-Fi 的连接"</string> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"平板电脑连接到“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”时会暂时断开与 Wi-Fi 的连接"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"手机连接到<xliff:g id="DEVICE_NAME">%1$s</xliff:g>时会暂时断开与 Wi-Fi 的连接。"</string> <string name="select_character" msgid="3365550120617701745">"插入字符"</string> <string name="sms_control_title" msgid="7296612781128917719">"正在发送短信"</string> @@ -1470,10 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地绘制了解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐户解锁手机。"\n\n"请在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒后重试。"</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"删除"</string> - <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"将音量调高到推荐级别以上?"\n"长时间使用高音量可能会损伤听力。"</string> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"将音量调高到推荐级别以上?"\n"长时间聆听高音量可能会损伤听力。"</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"持续按住双指即可启用辅助功能。"</string> <string name="accessibility_enabled" msgid="1381972048564547685">"辅助功能已启用。"</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"已取消辅助功能。"</string> <string name="user_switched" msgid="3768006783166984410">"当前用户是<xliff:g id="NAME">%1$s</xliff:g>。"</string> <string name="owner_name" msgid="2716755460376028154">"机主"</string> + <string name="error_message_title" msgid="4510373083082500195">"错误"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"此应用不支持受限用户的帐户"</string> + <string name="app_not_found" msgid="3429141853498927379">"找不到可处理此操作的应用"</string> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 5cb2f9d..cea9369 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -200,10 +200,8 @@ <string name="permgroupdesc_microphone" msgid="7106618286905738408">"直接使用麥克風錄音。"</string> <string name="permgrouplab_camera" msgid="4820372495894586615">"相機"</string> <string name="permgroupdesc_camera" msgid="2933667372289567714">"直接使用相機拍照或錄影。"</string> - <!-- no translation found for permgrouplab_screenlock (8275500173330718168) --> - <skip /> - <!-- no translation found for permgroupdesc_screenlock (7067497128925499401) --> - <skip /> + <string name="permgrouplab_screenlock" msgid="8275500173330718168">"鎖定畫面"</string> + <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"可影響裝置的鎖定畫面運作方式。"</string> <string name="permgrouplab_appInfo" msgid="8028789762634147725">"您的應用程式資訊"</string> <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"影響裝置上其他應用程式的行為。"</string> <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"桌布"</string> @@ -1050,10 +1048,8 @@ <string name="editTextMenuTitle" msgid="4909135564941815494">"文字動作"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"儲存空間即將用盡"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"部分系統功能可能無法運作"</string> - <!-- no translation found for app_running_notification_title (4625479411505090209) --> - <skip /> - <!-- no translation found for app_running_notification_text (3368349329989620597) --> - <skip /> + <string name="app_running_notification_title" msgid="4625479411505090209">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在執行"</string> + <string name="app_running_notification_text" msgid="3368349329989620597">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前正在執行"</string> <string name="ok" msgid="5970060430562524910">"確定"</string> <string name="cancel" msgid="6442560571259935130">"取消"</string> <string name="yes" msgid="5362982303337969312">"確定"</string> @@ -1147,8 +1143,7 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"收件者:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"請輸入必要的 PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <!-- no translation found for wifi_p2p_frequency_conflict_message (8012981257742232475) --> - <skip /> + <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"平板電腦與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 連線期間將暫時中斷 Wi-Fi 連線"</string> <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"手機與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 連線期間將暫時中斷 WiFi 連線"</string> <string name="select_character" msgid="3365550120617701745">"插入字元"</string> <string name="sms_control_title" msgid="7296612781128917719">"傳送 SMS 簡訊"</string> @@ -1475,11 +1470,13 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"您的解鎖圖形已畫錯 <xliff:g id="NUMBER_0">%d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%d</xliff:g> 次仍未成功,系統就會要求您透過電子郵件帳戶解除手機的鎖定狀態。"\n\n"請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"移除"</string> - <!-- no translation found for safe_media_volume_warning (7324161939475478066) --> - <skip /> + <string name="safe_media_volume_warning" product="default" msgid="7324161939475478066">"要將音量調高到建議等級以上嗎?"\n"長時間聆聽偏高音量可能會損害您的聽力。"</string> <string name="continue_to_enable_accessibility" msgid="1626427372316070258">"持續用兩指按住即可啟用協助工具。"</string> <string name="accessibility_enabled" msgid="1381972048564547685">"協助工具已啟用。"</string> <string name="enable_accessibility_canceled" msgid="3833923257966635673">"協助工具已取消。"</string> <string name="user_switched" msgid="3768006783166984410">"目前的使用者是 <xliff:g id="NAME">%1$s</xliff:g>。"</string> <string name="owner_name" msgid="2716755460376028154">"擁有者"</string> + <string name="error_message_title" msgid="4510373083082500195">"錯誤"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"這個應用程式不支援受限的使用者帳戶。"</string> + <string name="app_not_found" msgid="3429141853498927379">"找不到支援此操作的應用程式"</string> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index d9d0913..8bf3aae 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1476,4 +1476,7 @@ <string name="enable_accessibility_canceled" msgid="3833923257966635673">"Ukufinyelela kukhanseliwe."</string> <string name="user_switched" msgid="3768006783166984410">"Umsebenzisi wamanje <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="owner_name" msgid="2716755460376028154">"Umnikazi"</string> + <string name="error_message_title" msgid="4510373083082500195">"Iphutha"</string> + <string name="app_no_restricted_accounts" msgid="5322164210667258876">"Lolu hlelo lokusebenza alusekeli ama-akhawunti wabasebenzisi abakhawulelwe"</string> + <string name="app_not_found" msgid="3429141853498927379">"Alukho uhlelo lokusebenza olutholakele lokuphatha lesi senzo"</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 757bbc8..ced0851 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1142,6 +1142,7 @@ <java-symbol type="xml" name="time_zones_by_country" /> <java-symbol type="xml" name="sms_short_codes" /> <java-symbol type="xml" name="audio_assets" /> + <java-symbol type="xml" name="global_keys" /> <java-symbol type="raw" name="accessibility_gestures" /> <java-symbol type="raw" name="incognito_mode_start_page" /> diff --git a/core/res/res/xml/global_keys.xml b/core/res/res/xml/global_keys.xml new file mode 100644 index 0000000..8fa6902 --- /dev/null +++ b/core/res/res/xml/global_keys.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, 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. +*/ +--> + +<!-- Mapping of keycodes to components which will be handled globally. + Modify this file to add global keys. + A global key will NOT go to the foreground application and instead only ever be sent via targeted + broadcast to the specified component. The action of the intent will be + android.intent.action.GLOBAL_BUTTON and the KeyEvent will be included in the intent as + android.intent.extra.KEY_EVENT. +--> + +<global_keys version="1"> + <!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. --> + <!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> --> +</global_keys> diff --git a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java index cc8c4a6..37495e1 100644 --- a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java +++ b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java @@ -18,64 +18,51 @@ package android.content.pm; import android.os.Parcel; import android.test.AndroidTestCase; -import android.util.Base64; -import java.util.jar.Attributes; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; public class ManifestDigestTest extends AndroidTestCase { - private static final byte[] DIGEST_1 = { + private static final byte[] MESSAGE_1 = { (byte) 0x00, (byte) 0xAA, (byte) 0x55, (byte) 0xFF }; - private static final String DIGEST_1_STR = Base64.encodeToString(DIGEST_1, Base64.DEFAULT); - - private static final byte[] DIGEST_2 = { - (byte) 0x0A, (byte) 0xA5, (byte) 0xF0, (byte) 0x5A - }; - - private static final String DIGEST_2_STR = Base64.encodeToString(DIGEST_2, Base64.DEFAULT); - - private static final Attributes.Name SHA1_DIGEST = new Attributes.Name("SHA1-Digest"); - - private static final Attributes.Name MD5_DIGEST = new Attributes.Name("MD5-Digest"); - - public void testManifestDigest_FromAttributes_Null() { + public void testManifestDigest_FromInputStream_Null() { assertNull("Attributes were null, so ManifestDigest.fromAttributes should return null", - ManifestDigest.fromAttributes(null)); + ManifestDigest.fromInputStream(null)); } - public void testManifestDigest_FromAttributes_NoAttributes() { - Attributes a = new Attributes(); + public void testManifestDigest_FromInputStream_ThrowsIoException() { + InputStream is = new InputStream() { + @Override + public int read() throws IOException { + throw new IOException(); + } + }; - assertNull("There were no attributes to extract, so ManifestDigest should be null", - ManifestDigest.fromAttributes(a)); + assertNull("InputStream threw exception, so ManifestDigest should be null", + ManifestDigest.fromInputStream(is)); } - public void testManifestDigest_FromAttributes_SHA1PreferredOverMD5() { - Attributes a = new Attributes(); - a.put(SHA1_DIGEST, DIGEST_1_STR); - - a.put(MD5_DIGEST, DIGEST_2_STR); - - ManifestDigest fromAttributes = ManifestDigest.fromAttributes(a); - - assertNotNull("A valid ManifestDigest should be returned", fromAttributes); + public void testManifestDigest_Equals() throws Exception { + InputStream is = new ByteArrayInputStream(MESSAGE_1); - ManifestDigest created = new ManifestDigest(DIGEST_1); + ManifestDigest expected = + new ManifestDigest(MessageDigest.getInstance("SHA-256").digest(MESSAGE_1)); - assertEquals("SHA-1 should be preferred over MD5: " + created.toString() + " vs. " - + fromAttributes.toString(), created, fromAttributes); + ManifestDigest actual = ManifestDigest.fromInputStream(is); + assertEquals(expected, actual); - assertEquals("Hash codes should be the same: " + created.toString() + " vs. " - + fromAttributes.toString(), created.hashCode(), fromAttributes - .hashCode()); + ManifestDigest unexpected = new ManifestDigest(new byte[0]); + assertFalse(unexpected.equals(actual)); } - public void testManifestDigest_Parcel() { - Attributes a = new Attributes(); - a.put(SHA1_DIGEST, DIGEST_1_STR); + public void testManifestDigest_Parcel() throws Exception { + InputStream is = new ByteArrayInputStream(MESSAGE_1); - ManifestDigest digest = ManifestDigest.fromAttributes(a); + ManifestDigest digest = ManifestDigest.fromInputStream(is); Parcel p = Parcel.obtain(); digest.writeToParcel(p, 0); diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java index 274ac6b..d6a7ee2 100644 --- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java +++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java @@ -33,14 +33,41 @@ public class LinkPropertiesTest extends TestCase { private static String GATEWAY2 = "69.78.8.1"; private static String NAME = "qmi0"; + public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) { + // Check implementation of equals(), element by element. + assertTrue(source.isIdenticalInterfaceName(target)); + assertTrue(target.isIdenticalInterfaceName(source)); + + assertTrue(source.isIdenticalAddresses(target)); + assertTrue(target.isIdenticalAddresses(source)); + + assertTrue(source.isIdenticalDnses(target)); + assertTrue(target.isIdenticalDnses(source)); + + assertTrue(source.isIdenticalRoutes(target)); + assertTrue(target.isIdenticalRoutes(source)); + + assertTrue(source.isIdenticalHttpProxy(target)); + assertTrue(target.isIdenticalHttpProxy(source)); + + assertTrue(source.isIdenticalStackedLinks(target)); + assertTrue(target.isIdenticalStackedLinks(source)); + + // Check result of equals(). + assertTrue(source.equals(target)); + assertTrue(target.equals(source)); + + // Check hashCode. + assertEquals(source.hashCode(), target.hashCode()); + } + @SmallTest public void testEqualsNull() { LinkProperties source = new LinkProperties(); LinkProperties target = new LinkProperties(); assertFalse(source == target); - assertTrue(source.equals(target)); - assertTrue(source.hashCode() == target.hashCode()); + assertLinkPropertiesEqual(source, target); } @SmallTest @@ -73,8 +100,7 @@ public class LinkPropertiesTest extends TestCase { target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); - assertTrue(source.equals(target)); - assertTrue(source.hashCode() == target.hashCode()); + assertLinkPropertiesEqual(source, target); target.clear(); // change Interface Name @@ -163,8 +189,7 @@ public class LinkPropertiesTest extends TestCase { target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - assertTrue(source.equals(target)); - assertTrue(source.hashCode() == target.hashCode()); + assertLinkPropertiesEqual(source, target); } catch (Exception e) { fail(); } @@ -191,8 +216,7 @@ public class LinkPropertiesTest extends TestCase { target.addLinkAddress(new LinkAddress( NetworkUtils.numericToInetAddress(ADDRV6), 128)); - assertTrue(source.equals(target)); - assertTrue(source.hashCode() == target.hashCode()); + assertLinkPropertiesEqual(source, target); } catch (Exception e) { fail(); } diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk index e02e95a..7f8d5f0 100644 --- a/data/fonts/Android.mk +++ b/data/fonts/Android.mk @@ -152,7 +152,7 @@ font_src_files += \ RobotoCondensed-Italic.ttf \ RobotoCondensed-BoldItalic.ttf \ DroidNaskh-Regular.ttf \ - DroidNaskh-Regular-SystemUI.ttf \ + DroidNaskhUI-Regular.ttf \ DroidSansDevanagari-Regular.ttf \ DroidSansHebrew-Regular.ttf \ DroidSansHebrew-Bold.ttf \ diff --git a/data/fonts/DroidNaskh-Regular-SystemUI.ttf b/data/fonts/DroidNaskhUI-Regular.ttf Binary files differindex c961de2..c961de2 100644 --- a/data/fonts/DroidNaskh-Regular-SystemUI.ttf +++ b/data/fonts/DroidNaskhUI-Regular.ttf diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index 744b15d..16d760c 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -36,7 +36,7 @@ </family> <family> <fileset> - <file variant="compact">DroidNaskh-Regular-SystemUI.ttf</file> + <file variant="compact">DroidNaskhUI-Regular.ttf</file> </fileset> </family> <family> diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk index 875795a..7c2f955 100644 --- a/data/fonts/fonts.mk +++ b/data/fonts/fonts.mk @@ -33,7 +33,7 @@ PRODUCT_PACKAGES := \ RobotoCondensed-Italic.ttf \ RobotoCondensed-BoldItalic.ttf \ DroidNaskh-Regular.ttf \ - DroidNaskh-Regular-SystemUI.ttf \ + DroidNaskhUI-Regular.ttf \ DroidSansDevanagari-Regular.ttf \ DroidSansHebrew-Regular.ttf \ DroidSansHebrew-Bold.ttf \ diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk index e403205..8b03bf7 100644 --- a/data/sounds/AllAudio.mk +++ b/data/sounds/AllAudio.mk @@ -1,5 +1,4 @@ -# -# Copyright (C) 2009 The Android Open Source Project +# Copyright 2013 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. @@ -12,13 +11,222 @@ # 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. -# -$(call inherit-product, frameworks/base/data/sounds/OriginalAudio.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage2.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage3.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage4.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage5.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage6.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage7.mk) -$(call inherit-product, frameworks/base/data/sounds/AudioPackage7alt.mk) +LOCAL_PATH := frameworks/base/data/sounds + +PRODUCT_COPY_FILES += \ + $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \ + $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \ + $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \ + $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \ + $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \ + $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \ + $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \ + $(LOCAL_PATH)/alarms/ogg/Barium.ogg:system/media/audio/alarms/Barium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \ + $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:system/media/audio/alarms/Fermium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:system/media/audio/alarms/Hassium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \ + $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \ + $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:system/media/audio/alarms/Neptunium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:system/media/audio/alarms/Nobelium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \ + $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ + $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \ + $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:system/media/audio/alarms/Scandium.ogg \ + $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \ + $(LOCAL_PATH)/notifications/Aldebaran.ogg:system/media/audio/notifications/Aldebaran.ogg \ + $(LOCAL_PATH)/notifications/Altair.ogg:system/media/audio/notifications/Altair.ogg \ + $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \ + $(LOCAL_PATH)/notifications/Antares.ogg:system/media/audio/notifications/Antares.ogg \ + $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:system/media/audio/notifications/Antimony.ogg \ + $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \ + $(LOCAL_PATH)/notifications/ogg/Argon.ogg:system/media/audio/notifications/Argon.ogg \ + $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \ + $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \ + $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:system/media/audio/notifications/Beryllium.ogg \ + $(LOCAL_PATH)/notifications/Betelgeuse.ogg:system/media/audio/notifications/Betelgeuse.ogg \ + $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg \ + $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \ + $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \ + $(LOCAL_PATH)/notifications/Castor.ogg:system/media/audio/notifications/Castor.ogg \ + $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \ + $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:system/media/audio/notifications/Cobalt.ogg \ + $(LOCAL_PATH)/notifications/Cricket.ogg:system/media/audio/notifications/Cricket.ogg \ + $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \ + $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \ + $(LOCAL_PATH)/notifications/Doink.ogg:system/media/audio/notifications/Doink.ogg \ + $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \ + $(LOCAL_PATH)/notifications/Drip.ogg:system/media/audio/notifications/Drip.ogg \ + $(LOCAL_PATH)/notifications/Electra.ogg:system/media/audio/notifications/Electra.ogg \ + $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \ + $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \ + $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \ + $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:system/media/audio/notifications/Fluorine.ogg \ + $(LOCAL_PATH)/notifications/Fomalhaut.ogg:system/media/audio/notifications/Fomalhaut.ogg \ + $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:system/media/audio/notifications/Gallium.ogg \ + $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \ + $(LOCAL_PATH)/notifications/ogg/Helium.ogg:system/media/audio/notifications/Helium.ogg \ + $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \ + $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \ + $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:system/media/audio/notifications/Iridium.ogg \ + $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:system/media/audio/notifications/Krypton.ogg \ + $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \ + $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \ + $(LOCAL_PATH)/notifications/Merope.ogg:system/media/audio/notifications/Merope.ogg \ + $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \ + $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \ + $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:system/media/audio/notifications/Palladium.ogg \ + $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:system/media/audio/notifications/Plastic_Pipe.ogg \ + $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \ + $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \ + $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \ + $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \ + $(LOCAL_PATH)/notifications/ogg/Radon.ogg:system/media/audio/notifications/Radon.ogg \ + $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:system/media/audio/notifications/Rubidium.ogg \ + $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:system/media/audio/notifications/Selenium.ogg \ + $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \ + $(LOCAL_PATH)/notifications/Sirrah.ogg:system/media/audio/notifications/Sirrah.ogg \ + $(LOCAL_PATH)/notifications/SpaceSeed.ogg:system/media/audio/notifications/SpaceSeed.ogg \ + $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \ + $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:system/media/audio/notifications/Strontium.ogg \ + $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \ + $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \ + $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \ + $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \ + $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:system/media/audio/notifications/Thallium.ogg \ + $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \ + $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \ + $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \ + $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \ + $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:system/media/audio/notifications/Xenon.ogg \ + $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:system/media/audio/notifications/Zirconium.ogg \ + $(LOCAL_PATH)/notifications/arcturus.ogg:system/media/audio/notifications/arcturus.ogg \ + $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \ + $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \ + $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \ + $(LOCAL_PATH)/notifications/regulus.ogg:system/media/audio/notifications/regulus.ogg \ + $(LOCAL_PATH)/notifications/sirius.ogg:system/media/audio/notifications/sirius.ogg \ + $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \ + $(LOCAL_PATH)/notifications/vega.ogg:system/media/audio/notifications/vega.ogg \ + $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:system/media/audio/ringtones/ANDROMEDA.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \ + $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:system/media/audio/ringtones/Atria.ogg \ + $(LOCAL_PATH)/ringtones/BOOTES.ogg:system/media/audio/ringtones/BOOTES.ogg \ + $(LOCAL_PATH)/newwavelabs/Backroad.ogg:system/media/audio/ringtones/Backroad.ogg \ + $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \ + $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \ + $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:system/media/audio/ringtones/Big_Easy.ogg \ + $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \ + $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:system/media/audio/ringtones/Bollywood.ogg \ + $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:system/media/audio/ringtones/BussaMove.ogg \ + $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:system/media/audio/ringtones/CANISMAJOR.ogg \ + $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:system/media/audio/ringtones/CASSIOPEIA.ogg \ + $(LOCAL_PATH)/newwavelabs/Cairo.ogg:system/media/audio/ringtones/Cairo.ogg \ + $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:system/media/audio/ringtones/Calypso_Steel.ogg \ + $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \ + $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:system/media/audio/ringtones/CaribbeanIce.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \ + $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:system/media/audio/ringtones/Champagne_Edition.ogg \ + $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:system/media/audio/ringtones/Club_Cubano.ogg \ + $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:system/media/audio/ringtones/CrayonRock.ogg \ + $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:system/media/audio/ringtones/CrazyDream.ogg \ + $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \ + $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:system/media/audio/ringtones/DancinFool.ogg \ + $(LOCAL_PATH)/newwavelabs/Ding.ogg:system/media/audio/ringtones/Ding.ogg \ + $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:system/media/audio/ringtones/DonMessWivIt.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \ + $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:system/media/audio/ringtones/DreamTheme.ogg \ + $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:system/media/audio/ringtones/Eastern_Sky.ogg \ + $(LOCAL_PATH)/newwavelabs/Enter_the_Nexus.ogg:system/media/audio/ringtones/Enter_the_Nexus.ogg \ + $(LOCAL_PATH)/ringtones/Eridani.ogg:system/media/audio/ringtones/Eridani.ogg \ + $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \ + $(LOCAL_PATH)/ringtones/FreeFlight.ogg:system/media/audio/ringtones/FreeFlight.ogg \ + $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:system/media/audio/ringtones/FriendlyGhost.ogg \ + $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:system/media/audio/ringtones/Funk_Yall.ogg \ + $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:system/media/audio/ringtones/GameOverGuitar.ogg \ + $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:system/media/audio/ringtones/Gimme_Mo_Town.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \ + $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:system/media/audio/ringtones/Glacial_Groove.ogg \ + $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \ + $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:system/media/audio/ringtones/HalfwayHome.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \ + $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:system/media/audio/ringtones/InsertCoin.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \ + $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \ + $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \ + $(LOCAL_PATH)/ringtones/Lyra.ogg:system/media/audio/ringtones/Lyra.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \ + $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \ + $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \ + $(LOCAL_PATH)/newwavelabs/Nairobi.ogg:system/media/audio/ringtones/Nairobi.ogg \ + $(LOCAL_PATH)/newwavelabs/Nassau.ogg:system/media/audio/ringtones/Nassau.ogg \ + $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \ + $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:system/media/audio/ringtones/No_Limits.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \ + $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \ + $(LOCAL_PATH)/ringtones/PERSEUS.ogg:system/media/audio/ringtones/PERSEUS.ogg \ + $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:system/media/audio/ringtones/Paradise_Island.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \ + $(LOCAL_PATH)/newwavelabs/Playa.ogg:system/media/audio/ringtones/Playa.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:system/media/audio/ringtones/Rasalas.ogg \ + $(LOCAL_PATH)/newwavelabs/Revelation.ogg:system/media/audio/ringtones/Revelation.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \ + $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \ + $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \ + $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \ + $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \ + $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:system/media/audio/ringtones/Road_Trip.ogg \ + $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:system/media/audio/ringtones/RomancingTheTone.ogg \ + $(LOCAL_PATH)/newwavelabs/Safari.ogg:system/media/audio/ringtones/Safari.ogg \ + $(LOCAL_PATH)/newwavelabs/Savannah.ogg:system/media/audio/ringtones/Savannah.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \ + $(LOCAL_PATH)/newwavelabs/Seville.ogg:system/media/audio/ringtones/Seville.ogg \ + $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:system/media/audio/ringtones/Shes_All_That.ogg \ + $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:system/media/audio/ringtones/SilkyWay.ogg \ + $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:system/media/audio/ringtones/SitarVsSitar.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \ + $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:system/media/audio/ringtones/SpringyJalopy.ogg \ + $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:system/media/audio/ringtones/Steppin_Out.ogg \ + $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \ + $(LOCAL_PATH)/ringtones/Testudo.ogg:system/media/audio/ringtones/Testudo.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \ + $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:system/media/audio/ringtones/Third_Eye.ogg \ + $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:system/media/audio/ringtones/Thunderfoot.ogg \ + $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \ + $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:system/media/audio/ringtones/URSAMINOR.ogg \ + $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \ + $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:system/media/audio/ringtones/VeryAlarmed.ogg \ + $(LOCAL_PATH)/ringtones/Vespa.ogg:system/media/audio/ringtones/Vespa.ogg \ + $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \ + $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg \ + $(LOCAL_PATH)/ringtones/hydra.ogg:system/media/audio/ringtones/hydra.ogg \ + $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \ + $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ + $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \ + $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \ + $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \ + $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \ + $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ + diff --git a/data/sounds/generate-all-audio.sh b/data/sounds/generate-all-audio.sh new file mode 100755 index 0000000..6f42f5a --- /dev/null +++ b/data/sounds/generate-all-audio.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# Copyright 2013 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. + +# This script regenerates AllAudio.mk based on the content of the other +# makefiles. + +# It needs to be run from its location in the source tree. + +cat > AllAudio.mk << EOF +# Copyright 2013 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 := frameworks/base/data/sounds + +PRODUCT_COPY_FILES += \\ +EOF + +cat OriginalAudio.mk AudioPackage*.mk | + grep \\\$\(LOCAL_PATH\).*: | + cut -d : -f 2 | + cut -d \ -f 1 | + sort -u | + while read DEST + do + echo -n \ \ \ \ >> AllAudio.mk + cat *.mk | + grep \\\$\(LOCAL_PATH\).*:$DEST | + tr -d \ \\t | + cut -d : -f 1 | + sort -u | + tail -n 1 | + tr -d \\n >> AllAudio.mk + echo :$DEST\ \\ >> AllAudio.mk + done +echo >> AllAudio.mk diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd index b2d50ce..e17a0fd 100644 --- a/docs/html/about/dashboards/index.jd +++ b/docs/html/about/dashboards/index.jd @@ -1,107 +1,65 @@ page.title=Dashboards -header.hide=1 @jd:body - - -<h2 id="Platform">Platform Versions</h2> - -<p>This page provides data about the relative number of active devices -running a given version of the Android platform. This can help you -understand the landscape of device distribution and decide how to prioritize -the development of your application features for the devices currently in -the hands of users. For information about how to target your application to devices based on -platform version, read about <a -href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API levels</a>.</p> - - -<h3 id="PlatformCurrent">Current Distribution</h3> - -<p>The following pie chart and table is based on the number of Android devices that have accessed -Google Play within a 14-day period ending on the data collection date noted below.</p> - -<div class="col-5" style="margin-left:0"> - - -<table> -<tr> - <th>Version</th> - <th>Codename</th> - <th>API</th> - <th>Distribution</th> -</tr> -<tr><td><a href="/about/versions/android-1.6.html">1.6</a></td><td>Donut</td> <td>4</td><td>0.2%</td></tr> -<tr><td><a href="/about/versions/android-2.1.html">2.1</a></td><td>Eclair</td> <td>7</td><td>1.9%</td></tr> -<tr><td><a href="/about/versions/android-2.2.html">2.2</a></td><td>Froyo</td> <td>8</td><td>7.5%</td></tr> -<tr><td><a href="/about/versions/android-2.3.html">2.3 - 2.3.2</a> - </td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.2%</td></tr> -<tr><td><a href="/about/versions/android-2.3.3.html">2.3.3 - 2.3.7 - </a></td><!-- Gingerbread --> <td>10</td><td>43.9%</td></tr> -<tr><td><a href="/about/versions/android-3.1.html">3.1</a></td> - <td rowspan="2">Honeycomb</td> <td>12</td><td>0.3%</td></tr> -<tr><td><a href="/about/versions/android-3.2.html">3.2</a></td> <!-- Honeycomb --><td>13</td><td>0.9%</td></tr> -<tr><td><a href="/about/versions/android-4.0.3.html">4.0.3 - 4.0.4</a></td> - <td>Ice Cream Sandwich</td><td>15</td><td>28.6%</td></tr> -<tr><td><a href="/about/versions/android-4.1.html">4.1</a></td> - <td rowspan="2">Jelly Bean</td><td>16</td><td>14.9%</td></tr> -<tr><td><a href="/about/versions/android-4.2.html">4.2</a></td><!--Jelly Bean--> <td>17</td><td>1.6%</td></tr> -</table> - +<style> +div.chart, +div.screens-chart { + display:none; +} +tr .total { + background-color:transparent; + border:0; + color:#666; +} +tr th.total { + font-weight:bold; +} +</style> + + + + +<div class="sidebox"> +<h2>Google Play Install Stats</h2> +<p>The Google Play Developer Console also provides <a +href="{@docRoot}distribute/googleplay/about/distribution.html#stats">detailed statistics</a> +about your users' devices. Those stats may help you prioritize the device profiles for which +you optimize your app.</p> </div> -<div class="col-8" style="margin-right:0"> -<img style="margin-left:30px" alt="" -src="//chart.apis.google.com/chart?&cht=p&chs=460x245&chf=bg,s,00000000&chd=t:2.1,7.5,44.1,1.2,28.6,16.5&chl=Eclair%20%26%20older|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=c4df9b,6fad0c" -/> - -</div><!-- end dashboard-panel --> - -<p style="clear:both"><em>Data collected during a 14-day period ending on March 4, 2013</em></p> -<!-- -<p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p> ---> - -<h3 id="PlatformHistorical">Historical Distribution</h3> - -<p>The following stacked line graph provides a history of the relative number of -active Android devices running different versions of the Android platform. It also provides a -valuable perspective of how many devices your application is compatible with, based on the -platform version.</p> - -<p>Notice that the platform versions are stacked on top of each other with the oldest active -version at the top. This format indicates the total percent of active devices that are compatible -with a given version of Android. For example, if you develop your application for -the version that is at the very top of the chart, then your application is -compatible with 100% of active devices (and all future versions), because all Android APIs are -forward compatible. Or, if you develop your application for a version lower on the chart, -then it is currently compatible with the percentage of devices indicated on the y-axis, where the -line for that version meets the y-axis on the right.</p> - -<p>Each dataset in the timeline is based on the number of Android devices that accessed -Google Play within a 14-day period ending on the date indicated on the x-axis.</p> - -<img alt="" height="250" width="660" -src="//chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chf=bg,s,00000000&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C02/15%7C03/01%7C1%3A%7C2012%7C%7C%7C%7C%7C%7C%7C%7C2013%7C%7C%7C%7C2013%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.3,99.4,99.5,99.5,99.5,99.6,100.0,100.0,100.0,100.0,100.0,100.0,100.0|95.6,95.8,96.1,96.3,96.4,96.7,96.9,97.2,97.4,97.4,97.6,97.7,97.9|81.4,82.3,83.2,83.8,84.7,85.6,86.4,87.0,88.2,88.8,89.4,89.9,90.3|23.7,25.5,27.4,28.7,31.1,33.0,35.4,36.8,40.3,42.0,43.6,45.1,46.0|21.5,23.5,25.5,26.8,29.4,31.4,33.8,35.2,38.8,40.7,42.3,43.9,44.8|1.1,1.4,1.8,2.1,3.2,4.8,6.5,7.5,9.9,11.7,13.3,14.8,16.1&chm=b,c3df9b,0,1,0|tFroyo,689326,1,0,15,,t::-5|b,b4db77,1,2,0|tGingerbread,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|b,96dd28,3,4,0|tIce%20Cream%20Sandwich,293f07,4,0,15,,t::-5|b,83c916,4,5,0|tJelly%20Bean,131d02,5,9,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Eclair|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" -/> -<p><em>Last historical dataset collected during a 14-day period ending on March 1, 2013</em></p> - - - - - - - - +<p>This page provides information about the relative number of devices that share a certain +characteristic, such as Android version or screen size. This information may +help you prioritize efforts for <a +href="{@docRoot}training/basics/supporting-devices/index.html">supporting different devices</a>.</p> +<p>Each snapshot of data represents all the devices that visited the Google Play Store in the +prior 14 days.</p> +<p class="note"><strong>Note:</strong> Beginning in April, 2013, these charts are now built +using data collected from each device when the user visits the Google Play Store. Previously, the +data was collected when the device simply checked-in to Google servers. We believe the new +data more accurately reflects those users who are most engaged in the Android and Google Play +ecosystem.</p> +<h2 id="Platform">Platform Versions</h2> +<p>This section provides data about the relative number of devices running a given version of +the Android platform.</p> +<p>For information about how to target your application to devices based on +platform version, read <a +href="{@docRoot}training/basics/supporting-devices/platforms.html">Supporting Different +Platform Versions</a>.</p> +<div id="version-chart"> +</div> +<p style="clear:both"><em>Data collected during a 14-day period ending on April 2, 2013. +<br/>Any versions with less than 0.1% distribution are not shown.</em> +</p> @@ -111,72 +69,22 @@ src="//chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chf=bg,s,000 <h2 id="Screens">Screen Sizes and Densities</h2> -<img alt="" style="float:right;" -src="//chart.googleapis.com/chart?cht=p&chs=400x250&chf=bg,s,00000000&chco=c4df9b,6fad0c&chl=Xlarge%7CLarge%7CNormal%7CSmall&chd=t%3A4.6,6.1,86.6,2.7" /> - - -<img alt="" style="float:right;clear:right" -src="//chart.googleapis.com/chart?cht=p&chs=400x250&chf=bg,s,00000000&chco=c4df9b,6fad0c&chl=ldpi%7Cmdpi%7Chdpi%7Cxhdpi&chd=t%3A2.2,18,51.1,28.7" /> - - -<p>This section provides data about the relative number of active devices that have a particular +<p>This section provides data about the relative number of devices that have a particular screen configuration, defined by a combination of screen size and density. To simplify the way that you design your user interfaces for different screen configurations, Android divides the range of -actual screen sizes and densities into:</p> - -<ul> -<li>A set of four generalized <strong>sizes</strong>: <em>small</em>, <em>normal</em>, -<em>large</em>, and <em>xlarge</em></em></li> -<li>A set of four generalized <strong>densities</strong>: <em>ldpi</em> (low), <em>mdpi</em> -(medium), <em>hdpi</em> (high), and <em>xhdpi</em> (extra high)</li> -</ul> +actual screen sizes and densities into several buckets as expressed by the table below.</p> <p>For information about how you can support multiple screen configurations in your -application, see <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple +application, read <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.</p> -<p class="note"><strong>Note:</strong> This data is based on the number -of Android devices that have accessed Google Play within a 7-day period -ending on the data collection date noted below.</p> - - -<table style="width:350px"> -<tr> -<th></th> -<th scope="col">ldpi</th> -<th scope="col">mdpi</th> -<th scope="col">hdpi</th> -<th scope="col">xhdpi</th> -</tr> -<tr><th scope="row">small</th> -<td>1.7%</td> <!-- small/ldpi --> -<td></td> <!-- small/mdpi --> -<td>1.0%</td> <!-- small/hdpi --> -<td></td> <!-- small/xhdpi --> -</tr> -<tr><th scope="row">normal</th> -<td>0.4%</td> <!-- normal/ldpi --> -<td>11%</td> <!-- normal/mdpi --> -<td>50.1%</td> <!-- normal/hdpi --> -<td>25.1%</td> <!-- normal/xhdpi --> -</tr> -<tr><th scope="row">large</th> -<td>0.1%</td> <!-- large/ldpi --> -<td>2.4%</td> <!-- large/mdpi --> -<td></td> <!-- large/hdpi --> -<td>3.6%</td> <!-- large/xhdpi --> -</tr> -<tr><th scope="row">xlarge</th> -<td></td> <!-- xlarge/ldpi --> -<td>4.6%</td> <!-- xlarge/mdpi --> -<td></td> <!-- xlarge/hdpi --> -<td></td> <!-- xlarge/xhdpi --> -</tr> -</table> -<p style="clear:both"><em>Data collected during a 7-day period ending on October 1, 2012</em></p> +<div id="screens-chart"> +</div> +<p style="clear:both"><em>Data collected during a 14-day period ending on April 2, 2013 +<br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p> @@ -187,14 +95,14 @@ ending on the data collection date noted below.</p> <h2 id="OpenGL">Open GL Version</h2> -<p>This section provides data about the relative number of active devices that support a particular +<p>This section provides data about the relative number of devices that support a particular version of OpenGL ES. Note that support for one particular version of OpenGL ES also implies support for any lower version (for example, support for version 2.0 also implies support for 1.1).</p> <img alt="" style="float:right" -src="//chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A9.2,90.8&chf=bg,s,00000000" /> +src="//chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A0.3,99.7&chf=bg,s,00000000" /> <p>To declare which version of OpenGL ES your application requires, you should use the {@code android:glEsVersion} attribute of the <a @@ -204,10 +112,6 @@ href="{@docRoot}guide/topics/manifest/supports-gl-texture-element.html">{@code <supports-gl-texture>}</a> element to declare the GL compression formats that your application uses.</p> -<p class="note"><strong>Note:</strong> This data is based on the number -of Android devices that have accessed Google Play within a 7-day period -ending on the data collection date noted below.</p> - <table style="width:350px"> <tr> @@ -216,14 +120,347 @@ ending on the data collection date noted below.</p> </tr> <tr> <td>1.1 only</th> -<td>9.2%</td> +<td>0.3%</td> </tr> <tr> <td>2.0 & 1.1</th> -<td>90.8%</td> +<td>99.7%</td> </tr> </table> -<p style="clear:both"><em>Data collected during a 7-day period ending on October 1, 2012</em></p> +<p style="clear:both"><em>Data collected during a 14-day period ending on April 2, 2013</em></p> + + + + + + + + + + + + + +<script> +var VERSION_DATA = +[ + { + "chart": "//chart.googleapis.com/chart?chf=bg%2Cs%2C00000000&chd=t%3A1.8%2C4.0%2C39.8%2C0.2%2C29.3%2C25.0&chl=Eclair%7CFroyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean&chs=500x250&cht=p&chco=c4df9b%2C6fad0c", + "data": [ + { + "api": 4, + "name": "Donut", + "perc": "0.1" + }, + { + "api": 7, + "name": "Eclair", + "perc": "1.7" + }, + { + "api": 8, + "name": "Froyo", + "perc": "4.0" + }, + { + "api": 9, + "name": "Gingerbread", + "perc": "0.1" + }, + { + "api": 10, + "name": "Gingerbread", + "perc": "39.7" + }, + { + "api": 13, + "name": "Honeycomb", + "perc": "0.2" + }, + { + "api": 15, + "name": "Ice Cream Sandwich", + "perc": "29.3" + }, + { + "api": 16, + "name": "Jelly Bean", + "perc": "23.0" + }, + { + "api": 17, + "name": "Jelly Bean", + "perc": "2.0" + } + ] + } +]; + + + + + +var SCREEN_DATA = +[ + { + "data": { + "Large": { + "hdpi": "0.5", + "ldpi": "0.7", + "mdpi": "2.7", + "tvdpi": "1.0", + "xhdpi": "0.8" + }, + "Normal": { + "hdpi": "37.9", + "ldpi": "0.1", + "mdpi": "16.1", + "xhdpi": "25.0", + "xxhdpi": "0.8" + }, + "Small": { + "ldpi": "9.5" + }, + "Xlarge": { + "hdpi": "0.1", + "ldpi": "0.1", + "mdpi": "4.6", + "xhdpi": "0.1" + } + }, + "densitychart": "//chart.googleapis.com/chart?chf=bg%2Cs%2C00000000&chd=t%3A10.4%2C23.4%2C1.0%2C38.5%2C25.9%2C0.8&chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chs=400x250&cht=p&chco=c4df9b%2C6fad0c", + "layoutchart": "//chart.googleapis.com/chart?chf=bg%2Cs%2C00000000&chd=t%3A4.9%2C5.7%2C79.9%2C9.5&chl=Xlarge%7CLarge%7CNormal%7CSmall&chs=400x250&cht=p&chco=c4df9b%2C6fad0c" + } +]; + + + +var VERSION_NAMES = +[ + {"api":0},{"api":1},{"api":2},{"api":3}, + { + "api":4, + "link":"<a href='/about/versions/android-1.6.html'>1.6</a>", + "codename":"Donut", + }, + { "api":5}, + { "api":6}, + { + "api":7, + "link":"<a href='/about/versions/android-2.1.html'>2.1</a>", + "codename":"Eclair", + }, + { + "api":8, + "link":"<a href='/about/versions/android-2.2.html'>2.2</a>", + "codename":"Froyo" + }, + { + "api":9, + "link":"<a href='/about/versions/android-2.3.html'>2.3 -<br>2.3.2</a>", + "codename":"Gingerbread" + }, + { + "api":10, + "link":"<a href='/about/versions/android-2.3.3.html'>2.3.3 -<br>2.3.7</a>", + "codename":"Gingerbread" + }, + { "api":11}, + { + "api":12, + "link":"<a href='/about/versions/android-3.1.html'>3.1</a>", + "codename":"Honeycomb" + }, + { + "api":13, + "link":"<a href='/about/versions/android-3.2.html'>3.2</a>", + "codename":"Honeycomb" + }, + { "api":14}, + { + "api":15, + "link":"<a href='/about/versions/android-4.0.html'>4.0.3 -<br>4.0.4</a>", + "codename":"Ice Cream Sandwich" + }, + { + "api":16, + "link":"<a href='/about/versions/android-4.1.html'>4.1.x</a>", + "codename":"Jelly Bean" + }, + { + "api":17, + "link":"<a href='/about/versions/android-4.2.html'>4.2.x</a>", + "codename":"Jelly Bean" + } +]; + + + + + + + + + + + + + + + + + + + +$(document).ready(function(){ + // for each set of data (each month) + $.each(VERSION_DATA, function(i, set) { + + // set up wrapper divs + var $div = $('<div class="chart"' + + ((i == 0) ? ' style="display:block"' : '') + + ' >'); + var $divtable = $('<div class="col-5" style="margin-left:0">'); + var $divchart = $('<div class="col-8" style="margin-right:0">'); + + // set up a new table + var $table = $("<table>"); + var $trh = $("<tr><th>Version</th>" + + "<th>Codename</th>" + + "<th>API</th>" + + "<th>Distribution</th></tr>"); + $table.append($trh); + + // loop each data set (each api level represented in stats) + $.each(set.data, function(i, data) { + // check if we need to rowspan the codename + var rowspan = 1; + // must not be first row + if (i > 0) { + // if this row's codename is the same as previous row codename + if (data.name == set.data[i-1].name) { + rowspan = 0; + // otherwise, as long as this is not the last row + } else if (i < (set.data.length - 1)) { + // increment rowspan for each subsequent row w/ same codename + while (data.name == set.data[i+rowspan].name) { + rowspan++; + // unless we've reached the last row + if ((i + rowspan) >= set.data.length) break; + } + } + } + + // create table row and get corresponding version info from VERSION_NAMES + var $tr = $("<tr>"); + $tr.append("<td>" + VERSION_NAMES[data.api].link + "</td>"); + if (rowspan > 0) { + $tr.append("<td rowspan='" + rowspan + "'>" + VERSION_NAMES[data.api].codename + "</td>"); + } + $tr.append("<td>" + data.api + "</td>"); + $tr.append("<td>" + data.perc + "%</td>"); + $table.append($tr); + }); + + // create chart image + var $chart = $('<img style="margin-left:30px" alt="" src="' + set.chart + '" />'); + + // stack up and insert the elements + $divtable.append($table); + $divchart.append($chart); + $div.append($divtable).append($divchart); + $("#version-chart").append($div); + }); + + + + var SCREEN_SIZES = ["Small","Normal","Large","Xlarge"]; + var SCREEN_DENSITIES = ["ldpi","mdpi","tvdpi","hdpi","xhdpi","xxhdpi"]; + + + // for each set of screens data (each month) + $.each(SCREEN_DATA, function(i, set) { + + // set up wrapper divs + var $div = $('<div class="screens-chart"' + + ((i == 0) ? ' style="display:block"' : '') + + ' >'); + + // set up a new table + var $table = $("<table>"); + var $trh = $("<tr><th></th></tr>"); + $.each(SCREEN_DENSITIES, function(i, density) { + $trh.append("<th scope='col'>" + density + "</th>"); + }); + $trh.append("<th scope='col' class='total'>Total</th>"); + $table.append($trh); + + // array to hold totals for each density + var densityTotals = new Array(SCREEN_DENSITIES.length); + $.each(densityTotals, function(i, total) { + densityTotals[i] = 0; // make them all zero to start + }); + + // loop through each screen size + $.each(SCREEN_SIZES, function(i, size) { + // if there are any devices of this size + if (typeof set.data[size] != "undefined") { + // create table row and insert data + var $tr = $("<tr>"); + $tr.append("<th scope='row'>" + size + "</th>"); + // variable to sum all densities for this size + var total = 0; + // loop through each density + $.each(SCREEN_DENSITIES, function(i, density) { + var num = typeof set.data[size][density] != "undefined" ? set.data[size][density] : 0; + $tr.append("<td>" + (num != 0 ? num + "%" : "") + "</td>"); + total += parseFloat(num); + densityTotals[i] += parseFloat(num); + }) + $tr.append("<td class='total'>" + total.toFixed(1) + "%</td>"); + $table.append($tr); + } + }); + + // create row of totals for each density + var $tr = $("<tr><th class='total'>Total</th></tr>"); + $.each(densityTotals, function(i, total) { + $tr.append("<td class='total'>" + total.toFixed(1) + "%</td>"); + }); + $table.append($tr); + + // create charts + var $sizechart = $('<img style="float:left;width:380px" alt="" src="' + + set.layoutchart + '" />'); + var $densitychart = $('<img style="float:left;width:380px" alt="" src="' + + set.densitychart + '" />'); + + // stack up and insert the elements + $div.append($table).append($sizechart).append($densitychart); + $("#screens-chart").append($div); + }); + + +}); + + + +function changeVersionDate() { + var date = $('#date-versions option:selected').val(); + + $(".chart").hide(); + $(".chart."+date+"").show(); +} + + +function changeScreensVersionDate() { + var date = $('#date-screens option:selected').val(); + + $(".screens-chart").hide(); + $(".screens-chart."+date+"").show(); +} + +</script> diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd index acb2538..71957be 100644 --- a/docs/html/about/versions/jelly-bean.jd +++ b/docs/html/about/versions/jelly-bean.jd @@ -904,7 +904,7 @@ style="font-weight:500;">App Widgets</span> can resize automatically to fit the <h3>Media codec access</h3> -<p>Android 4.1 provides low-level access to platform hardware and software codecs. Apps can query the system to discover what <strong>low-level media codecs</strong> are available on the device and then and use them in the ways they need. For example, you can now create multiple instances of a media codec, queue input buffers, and receive output buffers in return. In addition, the media codec framework supports protected content. Apps can query for an available codec that is able to play protected content with a DRM solution available on the the device.</p> +<p>Android 4.1 provides low-level access to platform hardware and software codecs. Apps can query the system to discover what <strong>low-level media codecs</strong> are available on the device and then and use them in the ways they need. For example, you can now create multiple instances of a media codec, queue input buffers, and receive output buffers in return. In addition, the media codec framework supports protected content. Apps can query for an available codec that is able to play protected content with a DRM solution available on the device.</p> <h3>USB Audio</h3> diff --git a/docs/html/design/patterns/widgets.jd b/docs/html/design/patterns/widgets.jd index a5979ce..f2b0f4a 100644 --- a/docs/html/design/patterns/widgets.jd +++ b/docs/html/design/patterns/widgets.jd @@ -16,7 +16,7 @@ page.tags="appwidget" <div class="layout-content-row"> <div class="layout-content-col span-6"> <h3>Collection widgets</h3> - <p>As the name implies, collection widgets specialize on displaying multitude elements of the same type, such as a collection of pictures from a gallery app, a collection of articles from a news app or a collection of emails/messages from a communication app. Collection widgets typically focus on two use cases: browsing the collection, and opening an element of the collection to its detail view for consumption. Collection widgets can scroll vertically.</p> + <p>As the name implies, collection widgets specialize in displaying multitude elements of the same type, such as a collection of pictures from a gallery app, a collection of articles from a news app or a collection of emails/messages from a communication app. Collection widgets typically focus on two use cases: browsing the collection, and opening an element of the collection to its detail view for consumption. Collection widgets can scroll vertically.</p> </div> <div class="layout-content-col span-3"> <img src="{@docRoot}design/media/widgets_collection_gmail.png"> @@ -137,4 +137,4 @@ A music player widget is primarily a control widget, but also keeps the user inf <li>Choose the right widget type for your purpose.</li> <li>For resizable widgets, plan how the content for your widget should adapt to different sizes.</li> <li>Make your widget orientation and device independent by ensuring that the layout is capable of stretching and contracting.</li> -</ul>
\ No newline at end of file +</ul> diff --git a/docs/html/distribute/googleplay/about/visibility.jd b/docs/html/distribute/googleplay/about/visibility.jd index 4957c0f..18f60e9 100644 --- a/docs/html/distribute/googleplay/about/visibility.jd +++ b/docs/html/distribute/googleplay/about/visibility.jd @@ -39,7 +39,7 @@ style="font-weight:500;">Growth in app consumption</span>: Users download more t </div> <div> -<p>Google Play is also a top destination for visitors from the the web. Anyone +<p>Google Play is also a top destination for visitors from the web. Anyone with a browser can explore everything that Google Play has to offer from its <a href="http://play.google.com/store">web site</a>. Android users can even buy and install the apps they want and Google Play pushes them automatically to their @@ -159,7 +159,7 @@ the editorial team will notice.</p> <h4>Featured and Staff Picks</h4> -<p>Each week the the Google Play editorial staff selects a new set of apps to +<p>Each week the Google Play editorial staff selects a new set of apps to promote in its popular <em>Featured</em> and <em>Staff Picks</em> collections. </p> diff --git a/docs/html/google/play/billing/billing_integrate.jd b/docs/html/google/play/billing/billing_integrate.jd index 3365cfc..57227a8 100644 --- a/docs/html/google/play/billing/billing_integrate.jd +++ b/docs/html/google/play/billing/billing_integrate.jd @@ -19,6 +19,7 @@ parent.link=index.html <li><a href="#Subs">Implementing Subscriptions</a><li> </ol> </li> + <li><a href="#billing-security">Securing Your App</a> </ol> <h2>Reference</h2> <ol> @@ -361,6 +362,34 @@ Bundle activeSubs = mService.getPurchases(3, "com.example.myapp", the user. Once a subscription expires without renewal, it will no longer appear in the returned {@code Bundle}.</p> +<h2 id="billing-security">Securing Your Application</h2> + +<p>To help ensure the integrity of the transaction information that is sent to +your application, Google Play signs the JSON string that contains the response +data for a purchase order. Google Play uses the private key that is associated +with your application in the Developer Console to create this signature. The +Developer Console generates an RSA key pair for each application.<p> + +<p class="note"><strong>Note:</strong>To find the public key portion of this key +pair, open your application's details in the Developer Console, then click on +<strong>Services & APIs</strong>, and look at the field titled +<strong>Your License Key for This Application</strong>.</p> + +<p>The Base64-encoded RSA public key generated by Google Play is in binary +encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public +key that is used with Google Play licensing.</p> + +<p>When your application receives this signed response you can +use the public key portion of your RSA key pair to verify the signature. +By performing signature verification you can detect responses that have +been tampered with or that have been spoofed. You can perform this signature +verification step in your application; however, if your application connects +to a secure remote server then we recommend that you perform the signature +verification on that server.</p> + +<p>For more information about best practices for security and design, see <a +href="{@docRoot}google/play/billing/billing_best_practices.html">Security and Design</a>.</p> + diff --git a/docs/html/google/play/billing/billing_reference.jd b/docs/html/google/play/billing/billing_reference.jd index 1410e65..e168d70 100644 --- a/docs/html/google/play/billing/billing_reference.jd +++ b/docs/html/google/play/billing/billing_reference.jd @@ -143,7 +143,9 @@ does not include tax.</td> </tr> <tr> <td>{@code INAPP_DATA_SIGNATURE}</td> - <td>String containing the signature of the purchase data that was signed with the private key of the developer.</td> + <td>String containing the signature of the purchase data that was signed +with the private key of the developer. The data signature uses the +RSASSA-PKCS1-v1_5 scheme.</td> </tr> </table> </p> diff --git a/docs/html/google/play/licensing/adding-licensing.jd b/docs/html/google/play/licensing/adding-licensing.jd index 3f2460f..93561f6 100644 --- a/docs/html/google/play/licensing/adding-licensing.jd +++ b/docs/html/google/play/licensing/adding-licensing.jd @@ -853,37 +853,39 @@ sample application calls <code>checkAccess()</code> from a <h3 id="account-key">Embed your public key for licensing</h3> -<p>For each publisher account, the Google Play service automatically -generates a 2048-bit RSA public/private key pair that is used exclusively for -licensing. The key pair is uniquely associated with the publisher account and is -shared across all applications that are published through the account. Although -associated with a publisher account, the key pair is <em>not</em> the same as -the key that you use to sign your applications (or derived from it).</p> +<p>For each application, the Google Play service automatically +generates a 2048-bit RSA public/private key pair that is used for +licensing and in-app billing. The key pair is uniquely associated with the +application. Although associated with the application, the key pair is +<em>not</em> the same as the key that you use to sign your applications (or derived from it).</p> <p>The Google Play Developer Console exposes the public key for licensing to any -developer signed in to the publisher account, but it keeps the private key +developer signed in to the Developer Console, but it keeps the private key hidden from all users in a secure location. When an application requests a license check for an application published in your account, the licensing server -signs the license response using the private key of your account's key pair. +signs the license response using the private key of your application's key pair. When the LVL receives the response, it uses the public key provided by the application to verify the signature of the license response. </p> -<p>To add licensing to an application, you must obtain your publisher account's +<p>To add licensing to an application, you must obtain your application's public key for licensing and copy it into your application. Here's how to find -your account's public key for licensing:</p> +your application's public key for licensing:</p> <ol> <li>Go to the Google Play <a href="http://play.google.com/apps/publish">Developer Console</a> and sign in. Make sure that you sign in to the account from which the application you are licensing is published (or will be published). </li> -<li>In the account home page, locate the "Edit profile" link and click it. </li> -<li>In the Edit Profile page, locate the "Licensing" pane, shown below. Your -public key for licensing is given in the "Public key" text box. </li> +<li>In the application details page, locate the <strong>Services & APIs</strong> +link and click it. </li> +<li>In the <strong>Services & APIs</strong> page, locate the +<strong>Licensing & In-App Billing</strong> section. Your public key for +licensing is given in the +<strong>Your License Key For This Application</strong> field. </li> </ol> <p>To add the public key to your application, simply copy/paste the key string -from the text box into your application as the value of the String variable +from the field into your application as the value of the String variable <code>BASE64_PUBLIC_KEY</code>. When you are copying, make sure that you have selected the entire key string, without omitting any characters. </p> @@ -965,16 +967,6 @@ application that they have legitimately purchased on another device.</li> </ul> </div> - - - - - - - - - - <h2 id="app-obfuscation">Obfuscating Your Code</h2> <p>To ensure the security of your application, particularly for a paid diff --git a/docs/html/google/play/licensing/index.jd b/docs/html/google/play/licensing/index.jd index a13be10..6632fc0 100644 --- a/docs/html/google/play/licensing/index.jd +++ b/docs/html/google/play/licensing/index.jd @@ -16,7 +16,7 @@ restrict use of the application to a specific device, in addition to any other c <p>The licensing service is a secure means of controlling access to your applications. When an application checks the licensing status, the Google Play server signs the licensing status -response using a key pair that is uniquely associated with the publisher account. Your application +response using a key pair that is uniquely associated with the application. Your application stores the public key in its compiled <code>.apk</code> file and uses it to verify the licensing status response.</p> diff --git a/docs/html/google/play/licensing/licensing-reference.jd b/docs/html/google/play/licensing/licensing-reference.jd index 4240097..7bfa61a 100644 --- a/docs/html/google/play/licensing/licensing-reference.jd +++ b/docs/html/google/play/licensing/licensing-reference.jd @@ -186,7 +186,7 @@ licensing server, possibly because of network availability problems. </td> </tr> <tr> <td>{@code ERROR_SERVER_FAILURE}</td> -<td>Server error — the server could not load the publisher account's key +<td>Server error — the server could not load the application's key pair for licensing.</td> <td>No</td> <td></td> diff --git a/docs/html/google/play/licensing/overview.jd b/docs/html/google/play/licensing/overview.jd index 2434a4c..4e1a9c9 100644 --- a/docs/html/google/play/licensing/overview.jd +++ b/docs/html/google/play/licensing/overview.jd @@ -38,13 +38,13 @@ the licensing server and receives the result. The Google Play application sends the result to your application, which can allow or disallow further use of the application as needed.</p> -<p class="note"><strong>Note:</strong> If a paid application has been uploaded to Google Play but -saved only as a draft application (the app is unpublished), the licensing server considers all users -to be licensed users of the application (because it's not even possible to purchase the app). -This exception is necessary in order for you to perform testing of your licensing +<p class="note"><strong>Note:</strong> If a paid application has been uploaded +to Google Play, but saved only as a draft application (the app is +unpublished), the licensing server considers all users to be licensed users of +the application (because it's not even possible to purchase the app). This +exception is necessary in order for you to perform testing of your licensing implementation.</p> - <div class="figure" style="width:469px"> <img src="{@docRoot}images/licensing_arch.png" alt=""/> <p class="img-caption"><strong>Figure 1.</strong> Your application initiates a @@ -102,10 +102,11 @@ response data using an RSA key pair that is shared exclusively between the Googl server and you.</p> <p>The licensing service generates a single licensing key pair for each -publisher account and exposes the public key in your account's profile page. You must copy the -public key from the web site and embed it in your application source code. The server retains the -private key internally and uses it to sign license responses for the applications you -publish with that account.</p> +application and exposes the public key in your application's +<strong>Services & APIs</strong> page in the Developer Console. You must copy +the public key from the Developer Console and embed it in your application +source code. The server retains the private key internally and uses it to sign +license responses for the applications you publish with that account.</p> <p>When your application receives a signed response, it uses the embedded public key to verify the data. The use of public key cryptography in the licensing @@ -221,7 +222,7 @@ gives you wider distribution potential for your applications. </p> <p>Licensing lets you move to a license-based model that is enforceable on all devices that have access to Google Play. Access is not bound to the characteristics of the host device, but to your -publisher account on Google Play (through the app's public key) and the +application on Google Play (through the app's public key) and the licensing policy that you define. Your application can be installed and managed on any device on any storage, including SD card.</p> diff --git a/docs/html/guide/components/fragments.jd b/docs/html/guide/components/fragments.jd index 7747b31..32c9f99 100644 --- a/docs/html/guide/components/fragments.jd +++ b/docs/html/guide/components/fragments.jd @@ -172,7 +172,7 @@ the user might not come back).</dd> <p>Most applications should implement at least these three methods for every fragment, but there are several other callback methods you should also use to handle various stages of the -fragment lifecycle. All the lifecycle callback methods are discussed more later, in the section +fragment lifecycle. All the lifecycle callback methods are discussed in more detail in the section about <a href="#Lifecycle">Handling the Fragment Lifecycle</a>.</p> diff --git a/docs/html/guide/components/fundamentals.jd b/docs/html/guide/components/fundamentals.jd index 2c33a26..ce50022 100644 --- a/docs/html/guide/components/fundamentals.jd +++ b/docs/html/guide/components/fundamentals.jd @@ -345,7 +345,7 @@ receivers can be either declared in the manifest or created dynamically in code {@link android.content.BroadcastReceiver} objects) and registered with the system by calling {@link android.content.Context#registerReceiver registerReceiver()}.</p> -<p>For more about how to structure the manifest file for your application, see the <a +<p>For more about how to structure the manifest file for your application, see <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">The AndroidManifest.xml File</a> documentation. </p> diff --git a/docs/html/guide/components/tasks-and-back-stack.jd b/docs/html/guide/components/tasks-and-back-stack.jd index ecaba8d..a21bf34 100644 --- a/docs/html/guide/components/tasks-and-back-stack.jd +++ b/docs/html/guide/components/tasks-and-back-stack.jd @@ -231,7 +231,7 @@ activities except for the root activity when the user leaves the task.</p> <activity>}</a> manifest element and with flags in the intent that you pass to {@link android.app.Activity#startActivity startActivity()}.</p> -<p>In this regard, the the principal <a +<p>In this regard, the principal <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> attributes you can use are:</p> @@ -319,7 +319,7 @@ each instance can belong to different tasks, and one task can have multiple inst routes the intent to that instance through a call to its {@link android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance of the activity. The activity can be instantiated multiple times, each instance can -belong to different tasks, and one task can have multiple instances (but only if the the +belong to different tasks, and one task can have multiple instances (but only if the activity at the top of the back stack is <em>not</em> an existing instance of the activity). <p>For example, suppose a task's back stack consists of root activity A with activities B, C, and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D. diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_action_bar.jd b/docs/html/guide/practices/ui_guidelines/icon_design_action_bar.jd index c12b789..9f835a7 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design_action_bar.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design_action_bar.jd @@ -96,7 +96,7 @@ finished Action Bar icon dimensions for each generalized screen density.</p> </th> </tr> <tr> - <th style="background-color:#f3f3f3;font-weight:normal"> + <th> Action Bar Icon Size </th> <td> diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_dialog.jd b/docs/html/guide/practices/ui_guidelines/icon_design_dialog.jd index e02cdfc..a2c1459 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design_dialog.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design_dialog.jd @@ -51,36 +51,46 @@ for suggestions on how to work with multiple sets of icons.</p> <p class="table-caption"><strong>Table 1.</strong> Summary of finished dialog icon dimensions for each of the three generalized screen densities.</p> - <table> - <tbody> - <tr> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>Low density screen <em>(ldpi)</em></nobr> - </th> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>Medium density screen <em>(mdpi)</em></nobr> - </th> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>High density screen <em>(hdpi)</em><nobr> - </th> - </tr> - - <tr> - <td> - 24 x 24 px - </td> - <td> - 32 x 32 px - </td> - <td> - 48 x 48 px - </td> - </tr> - - </tbody> - </table> - - +<table> + <tbody> + <tr> + <th></th> + <th> + <code>ldpi</code> (120 dpi)<br> + <small style="font-weight: normal">(Low density screen)</small> + </th> + <th> + <code>mdpi</code> (160 dpi)<br> + <small style="font-weight: normal">(Medium density screen)</small> + </th> + <th> + <code>hdpi</code> (240 dpi)<br> + <small style="font-weight: normal">(High density screen)</small> + </th> + <th> + <code>xhdpi</code> (320 dpi)<br> + <small style="font-weight: normal">(Extra-high density screen)</small> + </th> + </tr> + <tr> + <th style="background-color:#f3f3f3;font-weight:normal"> + Dialog Icon Size + </th> + <td> + 24 x 24 px + </td> + <td> + 32 x 32 px + </td> + <td> + 48 x 48 px + </td> + <td> + 64 x 64 px + </td> + </tr> + </tbody> +</table> <p><strong>Final art must be exported as a transparent PNG file. Do not include a background color</strong>.</p> diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_launcher.jd b/docs/html/guide/practices/ui_guidelines/icon_design_launcher.jd index 200c135..4ec56b1 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design_launcher.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design_launcher.jd @@ -213,7 +213,7 @@ finished launcher icon dimensions for each generalized screen density.</p> </th> </tr> <tr> - <th style="background-color:#f3f3f3;font-weight:normal"> + <th> Launcher Icon Size </th> <td> diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_list.jd b/docs/html/guide/practices/ui_guidelines/icon_design_list.jd index 2fbce87..38ceb85 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design_list.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design_list.jd @@ -53,36 +53,46 @@ for suggestions on how to work with multiple sets of icons.</p> <p class="table-caption"><strong>Table 1.</strong> Summary of finished list view icon dimensions for each of the three generalized screen densities.</p> - <table> - <tbody> - <tr> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>Low density screen <em>(ldpi)</em></nobr> - </th> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>Medium density screen <em>(mdpi)</em></nobr> - </th> - <th style="background-color:#f3f3f3;font-weight:normal"> - <nobr>High density screen <em>(hdpi)</em><nobr> - </th> - </tr> - - <tr> - <td> - 24 x 24 px - </td> - <td> - 32 x 32 px - </td> - <td> - 48 x 48 px - </td> - </tr> - - </tbody> - </table> - - +<table> + <tbody> + <tr> + <th></th> + <th> + <code>ldpi</code> (120 dpi)<br> + <small style="font-weight: normal">(Low density screen)</small> + </th> + <th> + <code>mdpi</code> (160 dpi)<br> + <small style="font-weight: normal">(Medium density screen)</small> + </th> + <th> + <code>hdpi</code> (240 dpi)<br> + <small style="font-weight: normal">(High density screen)</small> + </th> + <th> + <code>xhdpi</code> (320 dpi)<br> + <small style="font-weight: normal">(Extra-high density screen)</small> + </th> + </tr> + <tr> + <th style="background-color:#f3f3f3;font-weight:normal"> + List View Icon Size + </th> + <td> + 24 x 24 px + </td> + <td> + 32 x 32 px + </td> + <td> + 48 x 48 px + </td> + <td> + 64 x 64 px + </td> + </tr> + </tbody> +</table> <p><strong>Final art must be exported as a transparent PNG file. Do not include a background color</strong>.</p> diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_status_bar.jd b/docs/html/guide/practices/ui_guidelines/icon_design_status_bar.jd index 8c15777..4cd4db3 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design_status_bar.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design_status_bar.jd @@ -155,7 +155,7 @@ finished icon dimensions for each generalized screen density.</p> </th> </tr> <tr> - <th style="background-color:#f3f3f3;font-weight:normal"> + <th> Status Bar Icon Size<br><small>(Android 3.0 and Later)</small> </th> <td> diff --git a/docs/html/guide/topics/admin/device-admin.jd b/docs/html/guide/topics/admin/device-admin.jd index 4a325db..f917576 100644 --- a/docs/html/guide/topics/admin/device-admin.jd +++ b/docs/html/guide/topics/admin/device-admin.jd @@ -339,7 +339,7 @@ a label.</li> </code> is a permission that a {@link android.app.admin.DeviceAdminReceiver} subclass must have, to ensure that only the system can interact with the receiver (no application can be granted this permission). This prevents other applications from abusing your device admin app.</li> -<li><code>android.app.action.DEVICE_ADMIN_ENABLED</code> is the the primary +<li><code>android.app.action.DEVICE_ADMIN_ENABLED</code> is the primary action that a {@link android.app.admin.DeviceAdminReceiver} subclass must handle to be allowed to manage a device. This is set to the receiver when the user enables the device admin app. Your code typically handles this in diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd index 6e6fa28..93d6c6f 100644 --- a/docs/html/guide/topics/appwidgets/index.jd +++ b/docs/html/guide/topics/appwidgets/index.jd @@ -1119,7 +1119,7 @@ initialize an array of <code>WidgetItem</code> objects. When your app widget is active, the system accesses these objects using their index position in the array and the text they contain is displayed </p> -<p>Here is an excerpt from the the <a +<p>Here is an excerpt from the <a href="{@docRoot}resources/samples/StackWidget/index.html">StackView Widget</a> sample's {@link android.widget.RemoteViewsService.RemoteViewsFactory diff --git a/docs/html/guide/topics/connectivity/wifip2p.jd b/docs/html/guide/topics/connectivity/wifip2p.jd index bbf30fd..efb3ac7 100644 --- a/docs/html/guide/topics/connectivity/wifip2p.jd +++ b/docs/html/guide/topics/connectivity/wifip2p.jd @@ -433,7 +433,7 @@ if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { <p>The {@link android.net.wifi.p2p.WifiP2pManager#requestPeers requestPeers()} method is also asynchronous and can notify your activity when a list of peers is available with {@link - android.net.wifi.p2p.WifiP2pManager.PeerListListener#onPeersAvailable onPeersAvailable()}, which is defined in the + android.net.wifi.p2p.WifiP2pManager.PeerListListener#onPeersAvailable onPeersAvailable()}, which is defined in the {@link android.net.wifi.p2p.WifiP2pManager.PeerListListener} interface. The {@link android.net.wifi.p2p.WifiP2pManager.PeerListListener#onPeersAvailable onPeersAvailable()} method provides you with an {@link android.net.wifi.p2p.WifiP2pDeviceList}, which you can iterate diff --git a/docs/html/guide/topics/graphics/opengl.jd b/docs/html/guide/topics/graphics/opengl.jd index 6114a4a..5630e63 100644 --- a/docs/html/guide/topics/graphics/opengl.jd +++ b/docs/html/guide/topics/graphics/opengl.jd @@ -444,7 +444,7 @@ investigate other texture compression formats available on your target devices.< <p>Beyond the ETC1 format, Android devices have varied support for texture compression based on their GPU chipsets and OpenGL implementations. You should investigate texture compression support on -the the devices you are are targeting to determine what compression types your application should +the devices you are are targeting to determine what compression types your application should support. In order to determine what texture formats are supported on a given device, you must <a href="#gl-extension-query">query the device</a> and review the <em>OpenGL extension names</em>, which identify what texture compression formats (and other OpenGL features) are supported by the diff --git a/docs/html/guide/topics/graphics/prop-animation.jd b/docs/html/guide/topics/graphics/prop-animation.jd index b733624..49d7bb8 100644 --- a/docs/html/guide/topics/graphics/prop-animation.jd +++ b/docs/html/guide/topics/graphics/prop-animation.jd @@ -479,7 +479,7 @@ ObjectAnimator.ofFloat(targetObject, "propName", 1f) </li> <li>Depending on what property or object you are animating, you might need to call the {@link - android.view.View#invalidate invalidate()} method on a View force the screen to redraw itself with the + android.view.View#invalidate invalidate()} method on a View to force the screen to redraw itself with the updated animated values. You do this in the {@link android.animation.ValueAnimator.AnimatorUpdateListener#onAnimationUpdate onAnimationUpdate()} callback. For example, animating the color property of a Drawable object only cause updates to the @@ -825,7 +825,7 @@ rotationAnim.setDuration(5000ms); <h2 id="views">Animating Views</h2> - <p>The property animation system allow streamlined animation of View objects and offerse + <p>The property animation system allow streamlined animation of View objects and offers a few advantages over the view animation system. The view animation system transformed View objects by changing the way that they were drawn. This was handled in the container of each View, because the View itself had no properties to manipulate. diff --git a/docs/html/guide/topics/location/index.jd b/docs/html/guide/topics/location/index.jd index 3217196..c4e8829 100644 --- a/docs/html/guide/topics/location/index.jd +++ b/docs/html/guide/topics/location/index.jd @@ -59,7 +59,9 @@ href="{@docRoot}guide/topics/location/strategies.html">Location Strategies</a> g <h2 id="maps">Google Maps Android API</h2> -<p>With the Google Maps Android API, you can add maps to your app that are based on Google +<p>With the +<a href="http://developers.google.com/maps/documentation/android/">Google Maps Android API</a>, +you can add maps to your app that are based on Google Maps data. The API automatically handles access to Google Maps servers, data downloading, map display, and touch gestures on the map. You can also use API calls to add markers, polygons and overlays, and to change the user's view of a particular map area.</p> @@ -85,6 +87,6 @@ with the Google Play Store running Android 2.2 or higher, through <p>To integrate Google Maps into your app, you need to install the Google Play services libraries for your Android SDK. For more details, read about <a -href="{@docRoot}google/play-services/index.html">Google Play services</a>.</p> +href="{@docRoot}google/play-services/maps.html">Google Play services</a>.</p> diff --git a/docs/html/guide/topics/manifest/activity-alias-element.jd b/docs/html/guide/topics/manifest/activity-alias-element.jd index ba2c154..d3df08b 100644 --- a/docs/html/guide/topics/manifest/activity-alias-element.jd +++ b/docs/html/guide/topics/manifest/activity-alias-element.jd @@ -89,7 +89,7 @@ See the <code><a href="{@docRoot}guide/topics/manifest/activity-element.html">&l <dt><a name="label"></a>{@code android:label}</dt> <dd>A user-readable label for the alias when presented to users through the alias. -See the the <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> element's +See the <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> element's <code><a href="{@docRoot}guide/topics/manifest/activity-element.html#label">label</a></code> attribute for more information. </p></dd> @@ -132,4 +132,4 @@ the alias in the manifest. <dt>see also:</dt> <dd><code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code></dd> -</dl>
\ No newline at end of file +</dl> diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index 2aedaec..c9f505f 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -217,7 +217,7 @@ separated by '{@code |}' — for example, "{@code locale|navigation|orientat </tr><tr> <td>"{@code uiMode}"</td> <td>The user interface mode has changed — this can be caused when the user places the -device into a desk/car dock or when the the night mode changes. See {@link +device into a desk/car dock or when the night mode changes. See {@link android.app.UiModeManager}. <em>Added in API level 8</em>.</td> </tr><tr> diff --git a/docs/html/guide/topics/manifest/meta-data-element.jd b/docs/html/guide/topics/manifest/meta-data-element.jd index 85a871d..56a214c 100644 --- a/docs/html/guide/topics/manifest/meta-data-element.jd +++ b/docs/html/guide/topics/manifest/meta-data-element.jd @@ -80,7 +80,7 @@ to the item. The ID can be retrieved from the meta-data Bundle by the </tr><tr> <td>Color value, in the form "{@code #rgb}", "{@code #argb}", "{@code #rrggbb}", or "{@code #aarrggbb}"</td> - <td>{@link android.os.Bundle#getString(String) getString()}</td> + <td>{@link android.os.Bundle#getInt(String) getInt()}</td> </tr><tr> <td>Float value, such as "{@code 1.23}"</td> <td>{@link android.os.Bundle#getFloat(String) getFloat()}</td> diff --git a/docs/html/guide/topics/providers/calendar-provider.jd b/docs/html/guide/topics/providers/calendar-provider.jd index 5adc68c..3cd4511 100644 --- a/docs/html/guide/topics/providers/calendar-provider.jd +++ b/docs/html/guide/topics/providers/calendar-provider.jd @@ -816,7 +816,7 @@ zone. <tr> <td>{@link android.provider.CalendarContract.Instances#END_MINUTE}</td> - <td>The end minute of the instance measured from midnight in the the + <td>The end minute of the instance measured from midnight in the Calendar's time zone.</td> </tr> diff --git a/docs/html/guide/topics/resources/accessing-resources.jd b/docs/html/guide/topics/resources/accessing-resources.jd index 0673b6f..8f99653 100644 --- a/docs/html/guide/topics/resources/accessing-resources.jd +++ b/docs/html/guide/topics/resources/accessing-resources.jd @@ -50,7 +50,7 @@ the {@code aapt} tool automatically generates.</p> <p>When your application is compiled, {@code aapt} generates the {@code R} class, which contains resource IDs for all the resources in your {@code res/} directory. For each type of resource, there is an {@code R} subclass (for example, -{@code R.drawable} for all drawable resources) and for each resource of that type, there is a static +{@code R.drawable} for all drawable resources), and for each resource of that type, there is a static integer (for example, {@code R.drawable.icon}). This integer is the resource ID that you can use to retrieve your resource.</p> @@ -68,7 +68,7 @@ resource is a simple value (such as a string).</li> <p>There are two ways you can access a resource:</p> <ul> - <li><strong>In code:</strong> Using an static integer from a sub-class of your {@code R} + <li><strong>In code:</strong> Using a static integer from a sub-class of your {@code R} class, such as: <pre class="classic no-pretty-print">R.string.hello</pre> <p>{@code string} is the resource type and {@code hello} is the resource name. There are many @@ -264,11 +264,13 @@ reference a system resource, you would need to include the package name. For exa android:text="@string/hello" /> </pre> -<p class="note"><strong>Note:</strong> You should use string resources at all times, so that your -application can be localized for other languages. For information about creating alternative +<p class="note"><strong>Note:</strong> You should use string resources at +all times, so that your application can be localized for other languages. +For information about creating alternative resources (such as localized strings), see <a href="providing-resources.html#AlternativeResources">Providing Alternative -Resources</a>.</p> +Resources</a>. For a complete guide to localizing your application for other languages, +see <a href="localization.html">Localization</a>.</p> <p>You can even use resources in XML to create aliases. For example, you can create a drawable resource that is an alias for another drawable resource:</p> diff --git a/docs/html/guide/topics/resources/animation-resource.jd b/docs/html/guide/topics/resources/animation-resource.jd index ef64f07..e5cac88 100644 --- a/docs/html/guide/topics/resources/animation-resource.jd +++ b/docs/html/guide/topics/resources/animation-resource.jd @@ -593,7 +593,7 @@ attribute, the value of which is a reference to an interpolator resource.</p> <p>All interpolators available in Android are subclasses of the {@link android.view.animation.Interpolator} class. For each interpolator class, Android includes a public resource you can reference in order to apply the interpolator to an animation -using the the {@code android:interpolator} attribute. +using the {@code android:interpolator} attribute. The following table specifies the resource to use for each interpolator:</p> <table> diff --git a/docs/html/guide/topics/resources/layout-resource.jd b/docs/html/guide/topics/resources/layout-resource.jd index 380ab15..366ddc8 100644 --- a/docs/html/guide/topics/resources/layout-resource.jd +++ b/docs/html/guide/topics/resources/layout-resource.jd @@ -135,7 +135,7 @@ a reference of all available attributes, </dd> <dt id="requestfocus-element"><code><requestFocus></code></dt> <dd>Any element representing a {@link android.view.View} object can include this empty element, - which gives it's parent initial focus on the screen. You can have only one of these + which gives its parent initial focus on the screen. You can have only one of these elements per file.</dd> <dt id="include-element"><code><include></code></dt> diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd index b311b7f..5097cc4 100644 --- a/docs/html/guide/topics/resources/providing-resources.jd +++ b/docs/html/guide/topics/resources/providing-resources.jd @@ -376,7 +376,7 @@ res/ screen area. Specifically, the device's smallestWidth is the shortest of the screen's available height and width (you may also think of it as the "smallest possible width" for the screen). You can use this qualifier to ensure that, regardless of the screen's current orientation, your -application's has at least {@code <N>} dps of width available for it UI.</p> +application has at least {@code <N>} dps of width available for its UI.</p> <p>For example, if your layout requires that its smallest dimension of screen area be at least 600 dp at all times, then you can use this qualifer to create the layout resources, {@code res/layout-sw600dp/}. The system will use these resources only when the smallest dimension of diff --git a/docs/html/guide/topics/search/search-dialog.jd b/docs/html/guide/topics/search/search-dialog.jd index b9a26d6..e24681a 100644 --- a/docs/html/guide/topics/search/search-dialog.jd +++ b/docs/html/guide/topics/search/search-dialog.jd @@ -722,6 +722,7 @@ public boolean onCreateOptionsMenu(Menu menu) { // Get the SearchView and set the searchable configuration SearchManager searchManager = (SearchManager) {@link android.app.Activity#getSystemService getSystemService}(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); + // Assumes current activity is the searchable activity searchView.setSearchableInfo(searchManager.getSearchableInfo({@link android.app.Activity#getComponentName()})); searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd index 678a512..db09e7d 100644 --- a/docs/html/guide/topics/ui/actionbar.jd +++ b/docs/html/guide/topics/ui/actionbar.jd @@ -674,7 +674,7 @@ action view still appears in the action bar when the user selects the item. You view collapsible by adding {@code "collapseActionView"} to the {@code android:showAsAction} attribute, as shown in the XML above.</p> -<p>Because the system will expand the action view when the user selects the item, so you +<p>Because the system will expand the action view when the user selects the item, you <em>do not</em> need to respond to the item in the {@link android.app.Activity#onOptionsItemSelected onOptionsItemSelected} callback. The system still calls {@link android.app.Activity#onOptionsItemSelected onOptionsItemSelected()} when the user selects it, diff --git a/docs/html/guide/topics/ui/drag-drop.jd b/docs/html/guide/topics/ui/drag-drop.jd index cacdf5c..884a1b2 100644 --- a/docs/html/guide/topics/ui/drag-drop.jd +++ b/docs/html/guide/topics/ui/drag-drop.jd @@ -750,7 +750,7 @@ imageView.setOnLongClickListener(new View.OnLongClickListener() { A{@link android.view.DragEvent#ACTION_DRAG_EXITED} event, it receives a new {@link android.view.DragEvent#ACTION_DRAG_LOCATION} event every time the touch point moves. The {@link android.view.DragEvent#getX()} and {@link android.view.DragEvent#getY()} methods - return the the X and Y coordinates of the touch point. + return the X and Y coordinates of the touch point. </li> <li> {@link android.view.DragEvent#ACTION_DRAG_EXITED}: This event is sent to a listener that @@ -995,4 +995,4 @@ protected class myDragEventListener implements View.OnDragEventListener { }; }; }; -</pre>
\ No newline at end of file +</pre> diff --git a/docs/html/guide/topics/ui/menus.jd b/docs/html/guide/topics/ui/menus.jd index 01d373e..dfcea52 100644 --- a/docs/html/guide/topics/ui/menus.jd +++ b/docs/html/guide/topics/ui/menus.jd @@ -834,7 +834,7 @@ android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}</l </ul> <p>You can create a group by nesting {@code <item>} elements inside a {@code <group>} -element in your menu resource or by specifying a group ID with the the {@link +element in your menu resource or by specifying a group ID with the {@link android.view.Menu#add(int,int,int,int) add()} method.</p> <p>Here's an example menu resource that includes a group:</p> diff --git a/docs/html/guide/topics/ui/notifiers/toasts.jd b/docs/html/guide/topics/ui/notifiers/toasts.jd index 92c146a..e5d4a0a 100644 --- a/docs/html/guide/topics/ui/notifiers/toasts.jd +++ b/docs/html/guide/topics/ui/notifiers/toasts.jd @@ -105,7 +105,7 @@ with the following XML (saved as <em>toast_layout.xml</em>):</p> </LinearLayout> </pre> -<p>Notice that the ID of the LinearLayout element is "toast_layout". You must use this +<p>Notice that the ID of the LinearLayout element is "toast_layout_root". You must use this ID to inflate the layout from the XML, as shown here:</p> <pre> diff --git a/docs/html/images/home/io-extended-2013.png b/docs/html/images/home/io-extended-2013.png Binary files differnew file mode 100644 index 0000000..93989d4 --- /dev/null +++ b/docs/html/images/home/io-extended-2013.png diff --git a/docs/html/index.jd b/docs/html/index.jd index ec0469c..29d6a8f 100644 --- a/docs/html/index.jd +++ b/docs/html/index.jd @@ -14,16 +14,16 @@ page.metaDescription=The official site for Android developers. Provides the Andr <ul> <li class="item carousel-home"> <div class="content-left col-10"> - <img src="{@docRoot}images/home/io-logo-2013.png" style="margin:40px 0 0"> + <img src="{@docRoot}images/home/io-extended-2013.png" style="margin:90px 0 0"> </div> <div class="content-right col-5"> - <h1>Google I/O 2013</h1> - <p>Android will be at Google I/O on May 15-17, 2013, with sessions covering a variety of topics - such as design, performance, and how to extend your app with the latest Android features.</p> - <p>For more information about event details and planned sessions, - stay tuned to <a - href="http://google.com/+GoogleDevelopers">+Google Developers</a>.</p> - <p><a href="https://developers.google.com/events/io/" class="button">Learn more</a></p> + <h1>Google I/O Extended</h1> + <p>Android will be at Google I/O on May 15-17, 2013, with sessions covering topics + such as design, performance, and how to enhance your app with the latest Android features.</p> + <p>Even if you can't make it there, you can experience the excitement and innovation of + Google I/O remotely with Google I/O Extended.</p> + <p><a href="https://developers.google.com/events/io/io-extended/?utm_source=site&utm_medium=emb&utm_campaign=extended-android-site" + >Organize or attend an event near you »</a></p> </div> </li> <li class="item carousel-home"> diff --git a/docs/html/tools/debugging/ddms.jd b/docs/html/tools/debugging/ddms.jd index 3d6324b..f641aad 100644 --- a/docs/html/tools/debugging/ddms.jd +++ b/docs/html/tools/debugging/ddms.jd @@ -54,7 +54,7 @@ parent.link=index.html <p>When DDMS starts, it connects to <a href="{@docRoot}tools/help/adb.html">adb</a>. When a device is connected, a VM monitoring service is created between <code>adb</code> and DDMS, which notifies DDMS when a VM on the device is started or terminated. Once a VM - is running, DDMS retrieves the the VM's process ID (pid), via <code>adb</code>, and opens a connection to the + is running, DDMS retrieves the VM's process ID (pid), via <code>adb</code>, and opens a connection to the VM's debugger, through the adb daemon (adbd) on the device. DDMS can now talk to the VM using a custom wire protocol.</p> diff --git a/docs/html/tools/device.jd b/docs/html/tools/device.jd index 9bdaf47..c7827b2 100644 --- a/docs/html/tools/device.jd +++ b/docs/html/tools/device.jd @@ -30,7 +30,7 @@ device directly from Eclipse or from the command line with ADB. If you don't yet have a device, check with the service providers in your area to determine which Android-powered devices are available.</p> -<p>If you want a SIM-unlocked phone, then you might consider the Google Nexus S. To find a place +<p>If you want a SIM-unlocked phone, then you might consider a Nexus phone. To find a place to purchase the Nexus S and other Android-powered devices, visit <a href="http://www.google.com/phone/detail/nexus-s">google.com/phone</a>.</p> diff --git a/docs/html/tools/devices/emulator.jd b/docs/html/tools/devices/emulator.jd index bae3985..fda233d 100644 --- a/docs/html/tools/devices/emulator.jd +++ b/docs/html/tools/devices/emulator.jd @@ -898,7 +898,7 @@ to a specified guest port on the router, while the router directs traffic to/from that port to the emulated device's host port. </p> <p>To set up the network redirection, you create a mapping of host and guest -ports/addresses on the the emulator instance. There are two ways to set up +ports/addresses on the emulator instance. There are two ways to set up network redirection: using emulator console commands and using the ADB tool, as described below. </p> diff --git a/docs/html/tools/projects/index.jd b/docs/html/tools/projects/index.jd index 6a49ac9..439d3be 100644 --- a/docs/html/tools/projects/index.jd +++ b/docs/html/tools/projects/index.jd @@ -68,12 +68,12 @@ page.title=Managing Projects <code>src<em>/your/package/namespace/ActivityName</em>.java</code>. All other source code files (such as <code>.java</code> or <code>.aidl</code> files) go here as well.</dd> - <dt><code>bin</code></dt> + <dt><code>bin/</code></dt> <dd>Output directory of the build. This is where you can find the final <code>.apk</code> file and other compiled resources.</dd> - <dt><code>jni</code></dt> + <dt><code>jni/</code></dt> <dd>Contains native code sources developed using the Android NDK. For more information, see the <a href="{@docRoot}tools/sdk/ndk/index.html">Android NDK documentation</a>.</dd> @@ -88,7 +88,7 @@ page.title=Managing Projects <dd>This is empty. You can use it to store raw asset files. Files that you save here are compiled into an <code>.apk</code> file as-is, and the original filename is preserved. You can navigate this directory in the same way as a typical file system using URIs and read files as a stream of - bytes using the the {@link android.content.res.AssetManager}. For example, this is a good + bytes using the {@link android.content.res.AssetManager}. For example, this is a good location for textures and game data.</dd> <dt><code>res/</code></dt> @@ -114,7 +114,7 @@ page.title=Managing Projects <dt><code>drawable/</code></dt> <dd>For bitmap files (PNG, JPEG, or GIF), 9-Patch image files, and XML files that describe - Drawable shapes or a Drawable objects that contain multiple states (normal, pressed, or + Drawable shapes or Drawable objects that contain multiple states (normal, pressed, or focused). See the <a href= "{@docRoot}guide/topics/resources/drawable-resource.html">Drawable</a> resource type.</dd> @@ -251,7 +251,7 @@ used.</dd> code and resources as a standard Android project, stored in the same way. For example, source code in the library project can access its own resources through its <code>R</code> class.</p> - <p>However, a library project differs from an standard Android application project in that you + <p>However, a library project differs from a standard Android application project in that you cannot compile it directly to its own <code>.apk</code> and run it on an Android device. Similarly, you cannot export the library project to a self-contained JAR file, as you would do for a true library. Instead, you must compile the library indirectly, by referencing the diff --git a/docs/html/tools/testing/activity_test.jd b/docs/html/tools/testing/activity_test.jd index 8288249..096aea5 100644 --- a/docs/html/tools/testing/activity_test.jd +++ b/docs/html/tools/testing/activity_test.jd @@ -537,7 +537,7 @@ import android.widget.Spinner; import android.widget.SpinnerAdapter; </pre> <p> - You now have the the complete <code>setUp()</code> method. + You now have the complete <code>setUp()</code> method. </p> <h3 id="AddPreConditionsTest">Adding an initial conditions test</h3> <p> @@ -1266,7 +1266,7 @@ $ adb install Spinner/bin/SpinnerActivity-debug.apk </li> <li> Follow the tutorial, starting with the section <a href="#CreateTestCaseClass">Creating the Test Case Class</a>. When you are prompted to - run the sample application, go the the Launcher screen in your device or emulator and select SpinnerActivity. + run the sample application, go to the Launcher screen in your device or emulator and select SpinnerActivity. When you are prompted to run the test application, return here to continue with the following instructions. </li> <li> diff --git a/docs/html/tools/testing/activity_testing.jd b/docs/html/tools/testing/activity_testing.jd index 7190b98..88ac9b2 100644 --- a/docs/html/tools/testing/activity_testing.jd +++ b/docs/html/tools/testing/activity_testing.jd @@ -77,7 +77,7 @@ parent.link=index.html </div> </div> <p> - Activity testing is particularly dependent on the the Android instrumentation framework. + Activity testing is particularly dependent on the Android instrumentation framework. Unlike other components, activities have a complex lifecycle based on callback methods; these can't be invoked directly except by instrumentation. Also, the only way to send events to the user interface from a program is through instrumentation. @@ -322,7 +322,7 @@ parent.link=index.html the published application. </p> <p> - To add the the permission, add the element + To add the permission, add the element <code><uses-permission android:name="android.permission.DISABLE_KEYGUARD"/></code> as a child of the <code><manifest></code> element. To disable the KeyGuard, add the following code to the <code>onCreate()</code> method of activities you intend to test: diff --git a/docs/html/tools/testing/testing_android.jd b/docs/html/tools/testing/testing_android.jd index acf5ec2..10843e8 100644 --- a/docs/html/tools/testing/testing_android.jd +++ b/docs/html/tools/testing/testing_android.jd @@ -111,14 +111,14 @@ parent.link=index.html </li> <li> The SDK tools for building and tests are available in Eclipse with ADT, and also in - command-line form for use with other IDES. These tools get information from the project of + command-line form for use with other IDEs. These tools get information from the project of the application under test and use this information to automatically create the build files, manifest file, and directory structure for the test package. </li> <li> The SDK also provides <a href="{@docRoot}tools/help/monkeyrunner_concepts.html">monkeyrunner</a>, an API - testing devices with Python programs, and <a + for testing devices with Python programs, and <a href="{@docRoot}tools/help/monkey.html">UI/Application Exerciser Monkey</a>, a command-line tool for stress-testing UIs by sending pseudo-random events to a device. </li> diff --git a/docs/html/tools/workflow/index.jd b/docs/html/tools/workflow/index.jd index 5ae06e6..784b212 100644 --- a/docs/html/tools/workflow/index.jd +++ b/docs/html/tools/workflow/index.jd @@ -34,7 +34,7 @@ development steps encompass four development phases, which include:</p> </li> <li><strong>Development</strong> <p>During this phase you set up and develop your Android project, which contains all of the - source code and resource files for your application. For more informations, see + source code and resource files for your application. For more information, see <a href="{@docRoot}tools/projects/index.html">Create an Android project</a>.</p> </li> <li><strong>Debugging and Testing</strong> diff --git a/docs/html/training/accessibility/service.jd b/docs/html/training/accessibility/service.jd index 373ddbb..953c558 100644 --- a/docs/html/training/accessibility/service.jd +++ b/docs/html/training/accessibility/service.jd @@ -204,7 +204,7 @@ public void onAccessibilityEvent(AccessibilityEvent event) { <p>This step is optional, but highly useful. One of the new features in Android 4.0 (API Level 14) is the ability for an {@link android.accessibilityservice.AccessibilityService} to query the view -hierarchy, collecting information about the the UI component that generated an event, and +hierarchy, collecting information about the UI component that generated an event, and its parent and children. In order to do this, make sure that you set the following line in your XML configuration:</p> <pre> diff --git a/docs/html/training/animation/cardflip.jd b/docs/html/training/animation/cardflip.jd index 1477f9f..48fbbd8 100644 --- a/docs/html/training/animation/cardflip.jd +++ b/docs/html/training/animation/cardflip.jd @@ -70,7 +70,7 @@ trainingnavtop=true <code>animator/card_flip_right_out.xml</code> </li> <li> - <code>animator/card_flip_right_in.xml</code> + <code>animator/card_flip_left_in.xml</code> </li> <li> <code>animator/card_flip_left_out.xml</code> @@ -372,4 +372,4 @@ private void flipCard() { // Commit the transaction. .commit(); } -</pre>
\ No newline at end of file +</pre> diff --git a/docs/html/training/basics/fragments/fragment-ui.jd b/docs/html/training/basics/fragments/fragment-ui.jd index d648938..db3119b 100644 --- a/docs/html/training/basics/fragments/fragment-ui.jd +++ b/docs/html/training/basics/fragments/fragment-ui.jd @@ -41,7 +41,7 @@ tablet which has a wider screen size to display more information to the user.</p <img src="{@docRoot}images/training/basics/fragments-screen-mock.png" alt="" /> <p class="img-caption"><strong>Figure 1.</strong> Two fragments, displayed in different -configurations for the same activity on different screen sizes. On a large screen, both fragment +configurations for the same activity on different screen sizes. On a large screen, both fragments fit side by side, but on a handset device, only one fragment fits at a time so the fragments must replace each other as the user navigates.</p> diff --git a/docs/html/training/basics/network-ops/connecting.jd b/docs/html/training/basics/network-ops/connecting.jd index ac8d993..50a9e1b 100644 --- a/docs/html/training/basics/network-ops/connecting.jd +++ b/docs/html/training/basics/network-ops/connecting.jd @@ -136,7 +136,7 @@ public class HttpExampleActivity extends Activity { getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { - new DownloadWebpageText().execute(stringUrl); + new DownloadWebpageTask().execute(stringUrl); } else { textView.setText("No network connection available."); } @@ -147,7 +147,7 @@ public class HttpExampleActivity extends Activity { // has been established, the AsyncTask downloads the contents of the webpage as // an InputStream. Finally, the InputStream is converted into a string, which is // displayed in the UI by the AsyncTask's onPostExecute method. - private class DownloadWebpageText extends AsyncTask<String, Void, String> { + private class DownloadWebpageTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... urls) { diff --git a/docs/html/training/basics/network-ops/managing.jd b/docs/html/training/basics/network-ops/managing.jd index 0f3d495..990b8cb 100644 --- a/docs/html/training/basics/network-ops/managing.jd +++ b/docs/html/training/basics/network-ops/managing.jd @@ -269,7 +269,7 @@ returns to the main activity:</p> // When the user changes the preferences selection, // onSharedPreferenceChanged() restarts the main activity as a new - // task. Sets the the refreshDisplay flag to "true" to indicate that + // task. Sets the refreshDisplay flag to "true" to indicate that // the main activity should update its display. // The main activity queries the PreferenceManager to get the latest settings. diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd index bd1537a..3e3aa14 100644 --- a/docs/html/training/gestures/scroll.jd +++ b/docs/html/training/gestures/scroll.jd @@ -56,7 +56,7 @@ scrolling animation in response to a touch event. They are similar, but {@link android.widget.OverScroller} includes methods for indicating to users that they've reached the content edges after a pan or fling gesture. The {@code InteractiveChart} sample -uses the the {@link android.widget.EdgeEffect} class +uses the {@link android.widget.EdgeEffect} class (actually the {@link android.support.v4.widget.EdgeEffectCompat} class) to display a "glow" effect when users reach the content edges.</p> diff --git a/docs/html/training/in-app-billing/list-iab-products.jd b/docs/html/training/in-app-billing/list-iab-products.jd index 36ff34a..c423fc1 100644 --- a/docs/html/training/in-app-billing/list-iab-products.jd +++ b/docs/html/training/in-app-billing/list-iab-products.jd @@ -54,7 +54,7 @@ next.link=purchase-iab-products.html <li>The {@code List} argument consists of one or more product IDs (also called SKUs) for the products that you want to query.</li> <li>Finally, the {@code QueryInventoryFinishedListener} argument specifies a listener is notified when the query operation has completed and handles the query response.</li> </ul> -If you use the the convenience classes provided in the sample, the classes will handle background thread management for In-app Billing requests, so you can safely make queries from the main thread of your application. +If you use the convenience classes provided in the sample, the classes will handle background thread management for In-app Billing requests, so you can safely make queries from the main thread of your application. </p> <p>The following code shows how you can retrieve the details for two products with IDs {@code SKU_APPLE} and {@code SKU_BANANA} that you previously defined in the Developer Console.</p> diff --git a/docs/html/training/monitoring-device-state/battery-monitoring.jd b/docs/html/training/monitoring-device-state/battery-monitoring.jd index c963a18..a202566 100644 --- a/docs/html/training/monitoring-device-state/battery-monitoring.jd +++ b/docs/html/training/monitoring-device-state/battery-monitoring.jd @@ -65,9 +65,9 @@ boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; // How are we charging? -int chargePlug = battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); -boolean usbCharge = chargePlug == BATTERY_PLUGGED_USB; -boolean acCharge = chargePlug == BATTERY_PLUGGED_AC;</pre> +int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); +boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB; +boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;</pre> <p>Typically you should maximize the rate of your background updates in the case where the device is connected to an AC charger, reduce the rate if the charge is over USB, and lower it @@ -105,8 +105,8 @@ the current charging state and method as described in the previous step.</p> status == BatteryManager.BATTERY_STATUS_FULL; int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - boolean usbCharge = chargePlug == BATTERY_PLUGGED_USB; - boolean acCharge = chargePlug == BATTERY_PLUGGED_AC; + boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB; + boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC; } }</pre> @@ -119,8 +119,8 @@ the rate of your background updates if the battery charge is below a certain lev <p>You can find the current battery charge by extracting the current battery level and scale from the battery status intent as shown here:</p> -<pre>int level = battery.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); -int scale = battery.getIntExtra(BatteryManager.EXTRA_SCALE, -1); +<pre>int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); +int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); float batteryPct = level / (float)scale;</pre> diff --git a/docs/html/training/monitoring-device-state/docking-monitoring.jd b/docs/html/training/monitoring-device-state/docking-monitoring.jd index 3787a55..5c8bfd6 100644 --- a/docs/html/training/monitoring-device-state/docking-monitoring.jd +++ b/docs/html/training/monitoring-device-state/docking-monitoring.jd @@ -79,7 +79,7 @@ boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK || <h2 id="MonitorDockState">Monitor for Changes in the Dock State or Type</h2> -<p>Whenever the the device is docked or undocked, the {@link +<p>Whenever the device is docked or undocked, the {@link android.content.Intent#ACTION_DOCK_EVENT} action is broadcast. To monitor changes in the device's dock-state, simply register a broadcast receiver in your application manifest as shown in the snippet below:</p> diff --git a/docs/html/training/multiple-threads/define-runnable.jd b/docs/html/training/multiple-threads/define-runnable.jd index 17640a9..40853d3 100644 --- a/docs/html/training/multiple-threads/define-runnable.jd +++ b/docs/html/training/multiple-threads/define-runnable.jd @@ -98,7 +98,7 @@ class PhotoDecodeRunnable implements Runnable { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); ... /* - * Stores the current Thread in the the PhotoTask instance, + * Stores the current Thread in the PhotoTask instance, * so that the instance * can interrupt the Thread. */ diff --git a/graphics/java/android/renderscript/ScriptIntrinsicBlur.java b/graphics/java/android/renderscript/ScriptIntrinsicBlur.java index 7ffd1e7..2848f64 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicBlur.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicBlur.java @@ -46,7 +46,7 @@ public final class ScriptIntrinsicBlur extends ScriptIntrinsic { * @return ScriptIntrinsicBlur */ public static ScriptIntrinsicBlur create(RenderScript rs, Element e) { - if ((e != Element.U8_4(rs)) && e != (Element.U8(rs))) { + if ((!e.isCompatible(Element.U8_4(rs))) && (!e.isCompatible(Element.U8(rs)))) { throw new RSIllegalArgumentException("Unsuported element type."); } int id = rs.nScriptIntrinsicCreate(5, e.getID(rs)); diff --git a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java index b219978..f7e844e 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java @@ -47,7 +47,7 @@ public final class ScriptIntrinsicColorMatrix extends ScriptIntrinsic { * @return ScriptIntrinsicColorMatrix */ public static ScriptIntrinsicColorMatrix create(RenderScript rs, Element e) { - if (e != Element.U8_4(rs)) { + if (!e.isCompatible(Element.U8_4(rs))) { throw new RSIllegalArgumentException("Unsuported element type."); } int id = rs.nScriptIntrinsicCreate(2, e.getID(rs)); diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java index b40ea84..d54df96 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java @@ -48,7 +48,7 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { */ public static ScriptIntrinsicConvolve3x3 create(RenderScript rs, Element e) { float f[] = { 0, 0, 0, 0, 1, 0, 0, 0, 0}; - if (e != Element.U8_4(rs)) { + if (!e.isCompatible(Element.U8_4(rs))) { throw new RSIllegalArgumentException("Unsuported element type."); } int id = rs.nScriptIntrinsicCreate(1, e.getID(rs)); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index d7119fff..c99dff0 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -356,6 +356,30 @@ public final class KeyChain { } } + /** + * Returns {@code true} if the current device's {@code KeyChain} supports a + * specific {@code PrivateKey} type indicated by {@code algorithm} (e.g., + * "RSA"). + */ + public static boolean isKeyAlgorithmSupported(String algorithm) { + return "RSA".equals(algorithm); + } + + /** + * Returns {@code true} if the current device's {@code KeyChain} binds any + * {@code PrivateKey} of the given {@code algorithm} to the device once + * imported or generated. This can be used to tell if there is special + * hardware support that can be used to bind keys to the device in a way + * that makes it non-exportable. + */ + public static boolean isBoundKeyAlgorithm(String algorithm) { + if (!isKeyAlgorithmSupported(algorithm)) { + return false; + } + + return KeyStore.getInstance().isHardwareBacked(); + } + private static X509Certificate toCertificate(byte[] bytes) { if (bytes == null) { throw new IllegalArgumentException("bytes == null"); diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 2037472..852f0bb 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -305,6 +305,15 @@ public class KeyStore { } } + public boolean clearUid(int uid) { + try { + return mBinder.clear_uid(uid) == NO_ERROR; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + public int getLastError() { return mError; } diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 5ff92be..fe51bf9 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -34,6 +34,9 @@ namespace android { namespace uirenderer { +// Depth of the save stack at the beginning of batch playback at flush time +#define FLUSH_SAVE_STACK_DEPTH 2 + ///////////////////////////////////////////////////////////////////////////////// // Operation Batches ///////////////////////////////////////////////////////////////////////////////// @@ -270,7 +273,7 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop(); - storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + 1); + storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + FLUSH_SAVE_STACK_DEPTH); } void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { @@ -386,6 +389,8 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers(); renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + // NOTE: depth of the save stack at this point, before playback, should be reflected in + // FLUSH_SAVE_STACK_DEPTH, so that save/restores match up correctly status |= replayBatchList(mBatches, renderer, dirty); renderer.restoreToCount(1); diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 3e450da..653f315 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -52,8 +52,6 @@ public: kOpBatch_Count, // Add other batch ids before this }; - void clear(); - bool isEmpty() { return mBatches.isEmpty(); } /** @@ -80,6 +78,8 @@ private: */ void resetBatchingState(); + void clear(); + void storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op); void storeRestoreToCountBarrier(OpenGLRenderer& renderer, StateOp* op, int newSaveCount); diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index d985ad0..36c95f9 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -352,7 +352,9 @@ void DisplayList::outputViewProperties(const int level) { } } if (mAlpha < 1) { - if (mCaching || !mHasOverlappingRendering) { + if (mCaching) { + ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mAlpha); + } else if (!mHasOverlappingRendering) { ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha); } else { int flags = SkCanvas::kHasAlphaLayer_SaveFlag; @@ -400,7 +402,9 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler, } } if (mAlpha < 1) { - if (mCaching || !mHasOverlappingRendering) { + if (mCaching) { + renderer.setOverrideLayerAlpha(mAlpha); + } else if (!mHasOverlappingRendering) { renderer.scaleAlpha(mAlpha); } else { // TODO: should be able to store the size of a DL at record time and not @@ -513,6 +517,7 @@ void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo); handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT); renderer.restoreToCount(restoreTo); + renderer.setOverrideLayerAlpha(1.0f); } }; // namespace uirenderer diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 9c3d058..a5dee9f 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -165,7 +165,11 @@ public: return DeferredDisplayList::kOpBatch_None; } - float strokeWidthOutset() { return mPaint->getStrokeWidth() * 0.5f; } + float strokeWidthOutset() { + float width = mPaint->getStrokeWidth(); + if (width == 0) return 0.5f; // account for hairline + return width * 0.5f; + } protected: SkPaint* getPaint(OpenGLRenderer& renderer) { diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index edc90fb..51aec8d 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -64,10 +64,32 @@ Extensions::Extensions(): Singleton<Extensions>() { mHas4BitStencil = hasExtension("GL_OES_stencil4"); mExtensions = strdup(buffer); + + const char* version = (const char*) glGetString(GL_VERSION); + mVersion = strdup(version); + + // Section 6.1.5 of the OpenGL ES specification indicates the GL version + // string strictly follows this format: + // + // OpenGL<space>ES<space><version number><space><vendor-specific information> + // + // In addition section 6.1.5 describes the version number thusly: + // + // "The version number is either of the form major number.minor number or + // major number.minor number.release number, where the numbers all have one + // or more digits. The release number and vendor specific information are + // optional." + + if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) !=2) { + // If we cannot parse the version number, assume OpenGL ES 2.0 + mVersionMajor = 2; + mVersionMinor = 0; + } } Extensions::~Extensions() { free(mExtensions); + free(mVersion); } /////////////////////////////////////////////////////////////////////////////// @@ -80,6 +102,7 @@ bool Extensions::hasExtension(const char* extension) const { } void Extensions::dump() const { + ALOGD("%s", mVersion); ALOGD("Supported extensions:\n%s", mExtensions); } diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index a069a6a..54a3987 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -45,6 +45,9 @@ public: inline bool has1BitStencil() const { return mHas1BitStencil; } inline bool has4BitStencil() const { return mHas4BitStencil; } + inline int getMajorGlVersion() const { return mVersionMajor; } + inline int getMinorGlVersion() const { return mVersionMinor; } + bool hasExtension(const char* extension) const; void dump() const; @@ -55,6 +58,7 @@ private: SortedVector<String8> mExtensionList; char* mExtensions; + char* mVersion; bool mHasNPot; bool mHasFramebufferFetch; @@ -64,6 +68,9 @@ private: bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; + + int mVersionMajor; + int mVersionMinor; }; // class Extensions }; // namespace uirenderer diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 26c7e5d..44dc731 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -31,6 +31,7 @@ #include "Caches.h" #include "Debug.h" +#include "Extensions.h" #include "FontRenderer.h" #include "Rect.h" @@ -375,34 +376,60 @@ void FontRenderer::checkTextureUpdate() { Caches& caches = Caches::getInstance(); GLuint lastTextureId = 0; + + // OpenGL ES 3.0+ lets us specify the row length for unpack operations such + // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. + // With OpenGL ES 2.0 we have to upload entire stripes instead. + const bool hasUnpackRowLength = Extensions::getInstance().getMajorGlVersion() >= 3; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // Iterate over all the cache textures and see which ones need to be updated for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture->isDirty() && cacheTexture->getTexture()) { - // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer - // of data. So expand the dirty rect to the encompassing horizontal stripe. const Rect* dirtyRect = cacheTexture->getDirtyRect(); - uint32_t x = 0; + uint32_t x = hasUnpackRowLength ? dirtyRect->left : 0; uint32_t y = dirtyRect->top; uint32_t width = cacheTexture->getWidth(); uint32_t height = dirtyRect->getHeight(); - void* textureData = cacheTexture->getTexture() + y * width; + void* textureData = cacheTexture->getTexture() + y * width + x; if (cacheTexture->getTextureId() != lastTextureId) { lastTextureId = cacheTexture->getTextureId(); caches.activeTexture(0); glBindTexture(GL_TEXTURE_2D, lastTextureId); + + // The unpack row length only needs to be specified when a new + // texture is bound + if (hasUnpackRowLength) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, width); + } + } + + // If we can upload a sub-rectangle, use the dirty rect width + // instead of the width of the entire texture + if (hasUnpackRowLength) { + width = dirtyRect->getWidth(); } + #if DEBUG_FONT_RENDERER ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", i, x, y, width, height); #endif + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, textureData); + cacheTexture->setDirty(false); } } + // Reset to default unpack row length to avoid affecting texture + // uploads in other parts of the renderer + if (hasUnpackRowLength) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + mUploadTexture = false; } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 63bb73f..a718294 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -90,7 +90,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { if (fbo) { Caches::getInstance().activeTexture(0); bindTexture(); - allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + allocateTexture(); if (glGetError() != GL_NO_ERROR) { setSize(oldWidth, oldHeight); @@ -167,7 +167,6 @@ void Layer::defer() { displayList->defer(deferredState, 0); deferredUpdateScheduled = false; - displayList = NULL; } void Layer::flush() { @@ -182,7 +181,7 @@ void Layer::flush() { renderer = NULL; dirtyRect.setEmpty(); - deferredList->clear(); + displayList = NULL; } } diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 27e0cf1..715dfa4 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -255,13 +255,14 @@ struct Layer { texture.id = 0; } - inline void allocateTexture(GLenum format, GLenum storage) { + inline void allocateTexture() { #if DEBUG_LAYERS ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); #endif if (texture.id) { - glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, - format, storage, NULL); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); } } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 8451048..3e55fff 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -256,7 +256,7 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque // Initialize the texture if needed if (layer->isEmpty()) { layer->setEmpty(false); - layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + layer->allocateTexture(); // This should only happen if we run out of memory if (glGetError() != GL_NO_ERROR) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 4a5785c..3730017 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -114,6 +114,7 @@ OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) { mDrawModifiers.mShader = NULL; mDrawModifiers.mColorFilter = NULL; + mDrawModifiers.mOverrideLayerAlpha = 1.0f; mDrawModifiers.mHasShadow = false; mDrawModifiers.mHasDrawFilter = false; @@ -570,8 +571,8 @@ void OpenGLRenderer::updateLayers() { startMark("Defer Layer Updates"); } - // Note: it is very important to update the layers in reverse order - for (int i = count - 1; i >= 0; i--) { + // Note: it is very important to update the layers in order + for (int i = 0; i < count; i++) { Layer* layer = mLayerUpdates.itemAt(i); updateLayer(layer, false); if (CC_UNLIKELY(mCaches.drawDeferDisabled)) { @@ -593,8 +594,8 @@ void OpenGLRenderer::flushLayers() { startMark("Apply Layer Updates"); char layerName[12]; - // Note: it is very important to update the layers in reverse order - for (int i = count - 1; i >= 0; i--) { + // Note: it is very important to update the layers in order + for (int i = 0; i < count; i++) { sprintf(layerName, "Layer #%d", i); startMark(layerName); @@ -921,7 +922,7 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui // Initialize the texture if needed if (layer->isEmpty()) { - layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + layer->allocateTexture(); layer->setEmpty(false); } @@ -1074,7 +1075,7 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) layer->setFilter(GL_LINEAR, true); } - float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha; + float alpha = getLayerAlpha(layer); bool blend = layer->isBlend() || alpha < 1.0f; drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(), layer->getTexture(), alpha, layer->getMode(), blend, @@ -1112,7 +1113,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { rects = safeRegion.getArray(&count); } - const float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha; + const float alpha = getLayerAlpha(layer); const float texX = 1.0f / float(layer->getWidth()); const float texY = 1.0f / float(layer->getHeight()); const float height = rect.getHeight(); @@ -2237,7 +2238,7 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const float left, float top, float right, float bottom, SkPaint* paint) { int alpha; SkXfermode::Mode mode; - getAlphaAndModeDirect(paint, &alpha, &mode); + getAlphaAndMode(paint, &alpha, &mode); return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors, left, top, right, bottom, alpha, mode); @@ -2990,7 +2991,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { if (layer->region.isRect()) { composeLayerRect(layer, layer->regionRect); } else if (layer->mesh) { - const float a = layer->getAlpha() / 255.0f * mSnapshot->alpha; + const float a = getLayerAlpha(layer); setupDraw(); setupDrawWithTexture(); setupDrawColor(a, a, a, a); @@ -3446,10 +3447,24 @@ void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, flo TextureVertex::setUV(v++, u2, v2); } -void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) { +void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const { getAlphaAndModeDirect(paint, alpha, mode); + if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) { + // if drawing a layer, ignore the paint's alpha + *alpha = mDrawModifiers.mOverrideLayerAlpha; + } *alpha *= mSnapshot->alpha; } +float OpenGLRenderer::getLayerAlpha(Layer* layer) const { + float alpha; + if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) { + alpha = mDrawModifiers.mOverrideLayerAlpha; + } else { + alpha = layer->getAlpha() / 255.0f; + } + return alpha * mSnapshot->alpha; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 04a47fc..dd7a5a2 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -51,6 +51,7 @@ namespace uirenderer { struct DrawModifiers { SkiaShader* mShader; SkiaColorFilter* mColorFilter; + float mOverrideLayerAlpha; // Drop shadow bool mHasShadow; @@ -275,6 +276,9 @@ public: virtual void resetPaintFilter(); virtual void setupPaintFilter(int clearBits, int setBits); + // If this value is set to < 1.0, it overrides alpha set on layer (see drawBitmap, drawLayer) + void setOverrideLayerAlpha(float alpha) { mDrawModifiers.mOverrideLayerAlpha = alpha; } + SkPaint* filterPaint(SkPaint* paint); bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags); @@ -283,7 +287,6 @@ public: const DrawModifiers& getDrawModifiers() { return mDrawModifiers; } void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; } - // TODO: what does this mean? no perspective? no rotate? ANDROID_API bool isCurrentTransformSimple() { return mSnapshot->transform->isSimple(); } @@ -325,7 +328,8 @@ public: /** * Gets the alpha and xfermode out of a paint object. If the paint is null * alpha will be 255 and the xfermode will be SRC_OVER. This method does - * not multiply the paint's alpha by the current snapshot's alpha. + * not multiply the paint's alpha by the current snapshot's alpha, and does + * not replace the alpha with the overrideLayerAlpha * * @param paint The paint to extract values from * @param alpha Where to store the resulting alpha @@ -450,13 +454,21 @@ protected: /** * Gets the alpha and xfermode out of a paint object. If the paint is null - * alpha will be 255 and the xfermode will be SRC_OVER. + * alpha will be 255 and the xfermode will be SRC_OVER. Accounts for both + * snapshot alpha, and overrideLayerAlpha * * @param paint The paint to extract values from * @param alpha Where to store the resulting alpha * @param mode Where to store the resulting xfermode */ - inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode); + inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const; + + /** + * Gets the alpha from a layer, accounting for snapshot alpha and overrideLayerAlpha + * + * @param layer The layer from which the alpha is extracted + */ + inline float getLayerAlpha(Layer* layer) const; /** * Safely retrieves the mode from the specified xfermode. If the specified diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index 395bbf6..0879b1b 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -61,6 +61,7 @@ void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint bool forceExpand) { if (forceExpand || paint->getStyle() != SkPaint::kFill_Style) { float outset = paint->getStrokeWidth() * 0.5f; + if (outset == 0) outset = 0.5f; // account for hairline bounds.outset(outset, outset); } } diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 1096642..577f463 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -15,10 +15,9 @@ */ #include <SkGlyph.h> -#include <utils/Log.h> -#include "Debug.h" #include "CacheTexture.h" +#include "../Debug.h" namespace android { namespace uirenderer { diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index 5742941..e7fb474 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -17,7 +17,7 @@ #ifndef ANDROID_HWUI_CACHE_TEXTURE_H #define ANDROID_HWUI_CACHE_TEXTURE_H -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #include <SkScalerContext.h> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 6f284f8..b80a166 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2127,7 +2127,7 @@ public class AudioManager { mediaButtonIntent.setComponent(eventReceiver); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); - unregisterMediaButtonIntent(pi, eventReceiver); + unregisterMediaButtonIntent(pi); } /** @@ -2139,16 +2139,16 @@ public class AudioManager { if (eventReceiver == null) { return; } - unregisterMediaButtonIntent(eventReceiver, null); + unregisterMediaButtonIntent(eventReceiver); } /** * @hide */ - public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { + public void unregisterMediaButtonIntent(PendingIntent pi) { IAudioService service = getService(); try { - service.unregisterMediaButtonIntent(pi, eventReceiver); + service.unregisterMediaButtonIntent(pi); } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e); } @@ -2274,6 +2274,26 @@ public class AudioManager { } /** + * @hide + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if (timeMs < 0) { + return; + } + IAudioService service = getService(); + try { + service.setRemoteControlClientPlaybackPosition(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", " + + timeMs + ")", e); + } + } + + /** * @hide * Reload audio settings. This method is called by Settings backup * agent when audio settings are restored and causes the AudioService diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 9ded922..fd71d79 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -167,12 +167,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30; private static final int MSG_UNLOAD_SOUND_EFFECTS = 31; private static final int MSG_RCC_NEW_PLAYBACK_STATE = 32; - - - // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be - // persisted - private static final int PERSIST_CURRENT = 0x1; - private static final int PERSIST_LAST_AUDIBLE = 0x2; + private static final int MSG_RCC_SEEK_REQUEST = 33; private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; // Timeout for connection to bluetooth headset service @@ -582,14 +577,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int streamType = 0; streamType < numStreamTypes; streamType++) { if (streamType != mStreamVolumeAlias[streamType]) { mStreamStates[streamType]. - setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], - false /*lastAudible*/); - mStreamStates[streamType]. - setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], - true /*lastAudible*/); + setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]); } // apply stream volume - if (mStreamStates[streamType].muteCount() == 0) { + if (!mStreamStates[streamType].isMuted()) { mStreamStates[streamType].applyAllVolumes(); } } @@ -633,10 +624,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; if (updateVolumes) { - mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], - false /*lastAudible*/); - mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], - true /*lastAudible*/); + mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, @@ -836,14 +824,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { final int device = getDeviceForStream(streamTypeAlias); - // get last audible index if stream is muted, current index otherwise - int aliasIndex = streamState.getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); + int aliasIndex = streamState.getIndex(device); boolean adjustVolume = true; - int step; - int index; - int oldIndex; // reset any pending volume command synchronized (mSafeMediaVolumeState) { @@ -872,64 +855,40 @@ public class AudioService extends IAudioService.Stub implements OnFinished { step = rescaleIndex(10, streamType, streamTypeAlias); } - if ((direction == AudioManager.ADJUST_RAISE) && - !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { - index = mStreamStates[streamType].getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); - oldIndex = index; - mVolumePanel.postDisplaySafeVolumeWarning(flags); - } else { - // If either the client forces allowing ringer modes for this adjustment, - // or the stream type is one that is affected by ringer modes - if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || - (streamTypeAlias == getMasterStreamType())) { - int ringerMode = getRingerMode(); - // do not vibrate if already in vibrate mode - if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { - flags &= ~AudioManager.FLAG_VIBRATE; - } - // Check if the ringer mode changes with this volume adjustment. If - // it does, it will handle adjusting the volume, so we won't below - adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); - if ((streamTypeAlias == getMasterStreamType()) && - (mRingerMode == AudioManager.RINGER_MODE_SILENT)) { - streamState.setLastAudibleIndex(0, device); - } - } - - // If stream is muted, adjust last audible index only - oldIndex = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); - - if (streamState.muteCount() != 0) { - if (adjustVolume) { - // Post a persist volume msg - // no need to persist volume on all streams sharing the same alias - streamState.adjustLastAudibleIndex(direction * step, device); - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } - index = mStreamStates[streamType].getIndex(device, true /* lastAudible */); - } else { - if (adjustVolume && streamState.adjustIndex(direction * step, device)) { - // Post message to set system volume (it in turn will post a message - // to persist). Do not change volume if stream is muted. - sendMsg(mAudioHandler, - MSG_SET_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); - } - index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + // If either the client forces allowing ringer modes for this adjustment, + // or the stream type is one that is affected by ringer modes + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (streamTypeAlias == getMasterStreamType())) { + int ringerMode = getRingerMode(); + // do not vibrate if already in vibrate mode + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + flags &= ~AudioManager.FLAG_VIBRATE; } + // Check if the ringer mode changes with this volume adjustment. If + // it does, it will handle adjusting the volume, so we won't below + adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); } + + int oldIndex = mStreamStates[streamType].getIndex(device); + + if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { + if ((direction == AudioManager.ADJUST_RAISE) && + !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { + Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); + mVolumePanel.postDisplaySafeVolumeWarning(flags); + } else if (streamState.adjustIndex(direction * step, device)) { + // Post message to set system volume (it in turn will post a message + // to persist). Do not change volume if stream is muted. + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + } + int index = mStreamStates[streamType].getIndex(device); sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -969,6 +928,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { }; private void onSetStreamVolume(int streamType, int index, int flags, int device) { + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false); // setting volume on master stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (mStreamVolumeAlias[streamType] == getMasterStreamType())) { @@ -976,18 +936,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (index == 0) { newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE : AudioManager.RINGER_MODE_SILENT; - setStreamVolumeInt(mStreamVolumeAlias[streamType], - index, - device, - false, - true); } else { newRingerMode = AudioManager.RINGER_MODE_NORMAL; } setRingerMode(newRingerMode); } - - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, true); } /** @see AudioManager#setStreamVolume(int, int, int) */ @@ -1006,9 +959,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // reset any pending volume command mPendingVolumeCommand = null; - // get last audible index if stream is muted, current index otherwise - oldIndex = streamState.getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); + oldIndex = streamState.getIndex(device); index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); @@ -1034,9 +985,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamType, index, flags, device); } else { onSetStreamVolume(streamType, index, flags, device); - // get last audible index if stream is muted, current index otherwise - index = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + index = mStreamStates[streamType].getIndex(device); } } sendVolumeUpdate(streamType, oldIndex, index, flags); @@ -1198,41 +1147,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * @param device the device whose volume must be changed * @param force If true, set the volume even if the desired volume is same * as the current volume. - * @param lastAudible If true, stores new index as last audible one */ private void setStreamVolumeInt(int streamType, int index, int device, - boolean force, - boolean lastAudible) { + boolean force) { VolumeStreamState streamState = mStreamStates[streamType]; - // If stream is muted, set last audible index only - if (streamState.muteCount() != 0) { - // Do not allow last audible index to be 0 - if (index != 0) { - streamState.setLastAudibleIndex(index, device); - // Post a persist volume msg - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } - } else { - if (streamState.setIndex(index, device, lastAudible) || force) { - // Post message to set system volume (it in turn will post a message - // to persist). - sendMsg(mAudioHandler, - MSG_SET_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); - } + if (streamState.setIndex(index, device) || force) { + // Post message to set system volume (it in turn will post a message + // to persist). + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); } } @@ -1244,7 +1175,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int stream = 0; stream < mStreamStates.length; stream++) { if (!isStreamAffectedByMute(stream) || stream == streamType) continue; - // Bring back last audible volume mStreamStates[stream].mute(cb, state); } } @@ -1262,7 +1192,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** get stream mute state. */ public boolean isStreamMute(int streamType) { - return (mStreamStates[streamType].muteCount() != 0); + return mStreamStates[streamType].isMuted(); } /** @see AudioManager#setMasterMute(boolean, int) */ @@ -1289,8 +1219,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - int index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + int index = mStreamStates[streamType].getIndex(device); + // by convention getStreamVolume() returns 0 when a stream is muted. + if (mStreamStates[streamType].isMuted()) { + index = 0; + } if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && (device & mFixedVolumeDevices) != 0) { index = mStreamStates[streamType].getMaxIndex(); @@ -1347,7 +1281,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getLastAudibleStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - return (mStreamStates[streamType].getIndex(device, true /* lastAudible */) + 5) / 10; + return (mStreamStates[streamType].getIndex(device) + 5) / 10; } /** Get last audible master volume before it was muted. */ @@ -1412,7 +1346,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (mVoiceCapable && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) { synchronized (mStreamStates[streamType]) { - Set set = mStreamStates[streamType].mLastAudibleIndex.entrySet(); + Set set = mStreamStates[streamType].mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); @@ -1661,8 +1595,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamType = AudioManager.STREAM_MUSIC; } int device = getDeviceForStream(streamType); - int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device, false); - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, false); + int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true); updateStreamVolumeAlias(true /*updateVolumes*/); } @@ -1896,7 +1830,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamState.readSettings(); // unmute stream that was muted but is not affect by mute anymore - if (streamState.muteCount() != 0 && ((!isStreamAffectedByMute(streamType) && + if (streamState.isMuted() && ((!isStreamAffectedByMute(streamType) && !isStreamMutedByRingerMode(streamType)) || mUseFixedVolume)) { int size = streamState.mDeathHandlers.size(); for (int i = 0; i < size; i++) { @@ -2396,8 +2330,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 0, null, MUSIC_ACTIVE_POLL_PERIOD_MS); - int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, - false /*lastAudible*/); + int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && (index > mSafeMediaVolumeIndex)) { // Approximate cumulative active music time @@ -2742,18 +2675,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private final int mStreamType; private String mVolumeIndexSettingName; - private String mLastAudibleVolumeIndexSettingName; private int mIndexMax; private final ConcurrentHashMap<Integer, Integer> mIndex = new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4); - private final ConcurrentHashMap<Integer, Integer> mLastAudibleIndex = - new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4); private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo clients death private VolumeStreamState(String settingName, int streamType) { mVolumeIndexSettingName = settingName; - mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE; mStreamType = streamType; mIndexMax = MAX_STREAM_VOLUME[streamType]; @@ -2766,10 +2695,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { readSettings(); } - public String getSettingNameForDevice(boolean lastAudible, int device) { - String name = lastAudible ? - mLastAudibleVolumeIndexSettingName : - mVolumeIndexSettingName; + public String getSettingNameForDevice(int device) { + String name = mVolumeIndexSettingName; String suffix = AudioSystem.getDeviceName(device); if (suffix.isEmpty()) { return name; @@ -2781,14 +2708,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); - mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); return; } // do not read system stream volume from settings: this stream is always aliased // to another stream type and its volume is never persisted. Values in settings can // only be stale values - // on first call to readSettings() at init time, muteCount() is always 0 so we will - // always create entries for default device if ((mStreamType == AudioSystem.STREAM_SYSTEM) || (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { int index = 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; @@ -2797,10 +2721,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { index = mIndexMax; } } - if (muteCount() == 0) { - mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); - } - mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); return; } @@ -2814,7 +2735,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { remainingDevices &= ~device; // retrieve current volume for device - String name = getSettingNameForDevice(false /* lastAudible */, device); + String name = getSettingNameForDevice(device); // if no volume stored for current stream and device, use default volume if default // device, continue otherwise int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? @@ -2828,72 +2749,33 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // ignore settings for fixed volume devices: volume should always be at max or 0 if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { - if ((muteCount()) == 0 && (index != 0)) { - mIndex.put(device, mIndexMax); - } else { - mIndex.put(device, 0); - } - mLastAudibleIndex.put(device, mIndexMax); - continue; - } - - // retrieve last audible volume for device - name = getSettingNameForDevice(true /* lastAudible */, device); - // use stored last audible index if present, otherwise use current index if not 0 - // or default index - defaultIndex = (index > 0) ? - index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - int lastAudibleIndex = Settings.System.getIntForUser( - mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); - - // a last audible index of 0 should never be stored for ring and notification - // streams on phones (voice capable devices). - if ((lastAudibleIndex == 0) && mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { - lastAudibleIndex = AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - // Correct the data base - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - this, - PERSIST_DELAY); - } - mLastAudibleIndex.put(device, getValidIndex(10 * lastAudibleIndex)); - // the initial index should never be 0 for ring and notification streams on phones - // (voice capable devices) if not in silent or vibrate mode. - if ((index == 0) && (mRingerMode == AudioManager.RINGER_MODE_NORMAL) && - mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { - index = lastAudibleIndex; - // Correct the data base - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_CURRENT, - device, - this, - PERSIST_DELAY); - } - if (muteCount() == 0) { + mIndex.put(device, (index != 0) ? mIndexMax : 0); + } else { mIndex.put(device, getValidIndex(10 * index)); } } } public void applyDeviceVolume(int device) { - AudioSystem.setStreamVolumeIndex(mStreamType, - (getIndex(device, false /* lastAudible */) + 5)/10, - device); + int index; + if (isMuted()) { + index = 0; + } else { + index = (getIndex(device) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); } public synchronized void applyAllVolumes() { // apply default volume first: by convention this will reset all // devices volumes in audio policy manager to the supplied value - AudioSystem.setStreamVolumeIndex(mStreamType, - (getIndex(AudioSystem.DEVICE_OUT_DEFAULT, false /* lastAudible */) + 5)/10, - AudioSystem.DEVICE_OUT_DEFAULT); + int index; + if (isMuted()) { + index = 0; + } else { + index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); // then apply device specific volumes Set set = mIndex.entrySet(); Iterator i = set.iterator(); @@ -2901,22 +2783,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Map.Entry entry = (Map.Entry)i.next(); int device = ((Integer)entry.getKey()).intValue(); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { - AudioSystem.setStreamVolumeIndex(mStreamType, - ((Integer)entry.getValue() + 5)/10, - device); + if (isMuted()) { + index = 0; + } else { + index = ((Integer)entry.getValue() + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); } } } public boolean adjustIndex(int deltaIndex, int device) { - return setIndex(getIndex(device, - false /* lastAudible */) + deltaIndex, - device, - true /* lastAudible */); + return setIndex(getIndex(device) + deltaIndex, + device); } - public synchronized boolean setIndex(int index, int device, boolean lastAudible) { - int oldIndex = getIndex(device, false /* lastAudible */); + public synchronized boolean setIndex(int index, int device) { + int oldIndex = getIndex(device); index = getValidIndex(index); synchronized (mCameraSoundForced) { if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { @@ -2926,9 +2809,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mIndex.put(device, index); if (oldIndex != index) { - if (lastAudible) { - mLastAudibleIndex.put(device, index); - } // Apply change to all streams using this one as alias // if changing volume of current device, also change volume of current // device on aliased stream @@ -2939,12 +2819,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mStreamVolumeAlias[streamType] == mStreamType) { int scaledIndex = rescaleIndex(index, mStreamType, streamType); mStreamStates[streamType].setIndex(scaledIndex, - device, - lastAudible); + device); if (currentDevice) { mStreamStates[streamType].setIndex(scaledIndex, - getDeviceForStream(streamType), - lastAudible); + getDeviceForStream(streamType)); } } } @@ -2954,63 +2832,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - public synchronized int getIndex(int device, boolean lastAudible) { - ConcurrentHashMap <Integer, Integer> indexes; - if (lastAudible) { - indexes = mLastAudibleIndex; - } else { - indexes = mIndex; - } - Integer index = indexes.get(device); + public synchronized int getIndex(int device) { + Integer index = mIndex.get(device); if (index == null) { // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT - index = indexes.get(AudioSystem.DEVICE_OUT_DEFAULT); + index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT); } return index.intValue(); } - public synchronized void setLastAudibleIndex(int index, int device) { - // Apply change to all streams using this one as alias - // if changing volume of current device, also change volume of current - // device on aliased stream - boolean currentDevice = (device == getDeviceForStream(mStreamType)); - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { - if (streamType != mStreamType && - mStreamVolumeAlias[streamType] == mStreamType) { - int scaledIndex = rescaleIndex(index, mStreamType, streamType); - mStreamStates[streamType].setLastAudibleIndex(scaledIndex, device); - if (currentDevice) { - mStreamStates[streamType].setLastAudibleIndex(scaledIndex, - getDeviceForStream(streamType)); - } - } - } - mLastAudibleIndex.put(device, getValidIndex(index)); - } - - public synchronized void adjustLastAudibleIndex(int deltaIndex, int device) { - setLastAudibleIndex(getIndex(device, - true /* lastAudible */) + deltaIndex, - device); - } - public int getMaxIndex() { return mIndexMax; } - // only called by setAllIndexes() which is already synchronized - public ConcurrentHashMap <Integer, Integer> getAllIndexes(boolean lastAudible) { - if (lastAudible) { - return mLastAudibleIndex; - } else { - return mIndex; - } - } - - public synchronized void setAllIndexes(VolumeStreamState srcStream, boolean lastAudible) { - ConcurrentHashMap <Integer, Integer> indexes = srcStream.getAllIndexes(lastAudible); - Set set = indexes.entrySet(); + public synchronized void setAllIndexes(VolumeStreamState srcStream) { + Set set = srcStream.mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); @@ -3018,11 +2854,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int index = ((Integer)entry.getValue()).intValue(); index = rescaleIndex(index, srcStream.getStreamType(), mStreamType); - if (lastAudible) { - setLastAudibleIndex(index, device); - } else { - setIndex(index, device, false /* lastAudible */); - } + setIndex(index, device); } } @@ -3033,12 +2865,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Map.Entry entry = (Map.Entry)i.next(); entry.setValue(mIndexMax); } - set = mLastAudibleIndex.entrySet(); - i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - entry.setValue(mIndexMax); - } } public synchronized void mute(IBinder cb, boolean state) { @@ -3074,6 +2900,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // must be called while synchronized on parent VolumeStreamState public void mute(boolean state) { + boolean updateVolume = false; if (state) { if (mMuteCount == 0) { // Register for client death notification @@ -3082,22 +2909,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (mICallback != null) { mICallback.linkToDeath(this, 0); } - mDeathHandlers.add(this); + VolumeStreamState.this.mDeathHandlers.add(this); // If the stream is not yet muted by any client, set level to 0 - if (muteCount() == 0) { - Set set = mIndex.entrySet(); - Iterator i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - int device = ((Integer)entry.getKey()).intValue(); - setIndex(0, device, false /* lastAudible */); - } - sendMsg(mAudioHandler, - MSG_SET_ALL_VOLUMES, - SENDMSG_QUEUE, - 0, - 0, - VolumeStreamState.this, 0); + if (!VolumeStreamState.this.isMuted()) { + updateVolume = true; } } catch (RemoteException e) { // Client has died! @@ -3115,37 +2930,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMuteCount--; if (mMuteCount == 0) { // Unregister from client death notification - mDeathHandlers.remove(this); + VolumeStreamState.this.mDeathHandlers.remove(this); // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.unlinkToDeath(this, 0); } - if (muteCount() == 0) { - // If the stream is not muted any more, restore its volume if - // ringer mode allows it - if (!isStreamAffectedByRingerMode(mStreamType) || - mRingerMode == AudioManager.RINGER_MODE_NORMAL) { - Set set = mIndex.entrySet(); - Iterator i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - int device = ((Integer)entry.getKey()).intValue(); - setIndex(getIndex(device, - true /* lastAudible */), - device, - false /* lastAudible */); - } - sendMsg(mAudioHandler, - MSG_SET_ALL_VOLUMES, - SENDMSG_QUEUE, - 0, - 0, - VolumeStreamState.this, 0); - } + if (!VolumeStreamState.this.isMuted()) { + updateVolume = true; } } } } + if (updateVolume) { + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + VolumeStreamState.this, 0); + } } public void binderDied() { @@ -3167,6 +2970,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return count; } + private synchronized boolean isMuted() { + return muteCount() != 0; + } + // only called by mute() which is already synchronized private VolumeDeathHandler getDeathHandler(IBinder cb, boolean state) { VolumeDeathHandler handler; @@ -3199,14 +3006,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); } - pw.print("\n Last audible: "); - set = mLastAudibleIndex.entrySet(); - i = set.iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry)i.next(); - pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) - + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); - } } } @@ -3254,8 +3053,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, - PERSIST_CURRENT|PERSIST_LAST_AUDIBLE, device, + 0, streamState, PERSIST_DELAY); @@ -3276,24 +3075,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - private void persistVolume(VolumeStreamState streamState, - int persistType, - int device) { + private void persistVolume(VolumeStreamState streamState, int device) { if (mUseFixedVolume) { return; } - if ((persistType & PERSIST_CURRENT) != 0) { - System.putIntForUser(mContentResolver, - streamState.getSettingNameForDevice(false /* lastAudible */, device), - (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10, - UserHandle.USER_CURRENT); - } - if ((persistType & PERSIST_LAST_AUDIBLE) != 0) { - System.putIntForUser(mContentResolver, - streamState.getSettingNameForDevice(true /* lastAudible */, device), - (streamState.getIndex(device, true /* lastAudible */) + 5) / 10, - UserHandle.USER_CURRENT); - } + System.putIntForUser(mContentResolver, + streamState.getSettingNameForDevice(device), + (streamState.getIndex(device) + 5)/ 10, + UserHandle.USER_CURRENT); } private void persistRingerMode(int ringerMode) { @@ -3545,7 +3334,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_PERSIST_VOLUME: - persistVolume((VolumeStreamState) msg.obj, msg.arg1, msg.arg2); + persistVolume((VolumeStreamState) msg.obj, msg.arg1); break; case MSG_PERSIST_MASTER_VOLUME: @@ -4919,6 +4708,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack + */ private final Object mCurrentRcLock = new Object(); /** * The one remote control client which will receive a request for display information. @@ -5100,7 +4892,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRemoteVolumeObs = null; } - /** precondition: mediaIntent != null, eventReceiver != null */ + /** precondition: mediaIntent != null */ public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) { mMediaIntent = mediaIntent; mReceiverComponent = eventReceiver; @@ -5190,6 +4982,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } } synchronized (mMainRemote) { pw.println("\nRemote Volume State:"); @@ -5273,6 +5068,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); if ((null != receiverName) && !receiverName.isEmpty()) { ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + if (eventReceiver == null) { + // an invalid name was persisted + return; + } // construct a PendingIntent targeted to the restored component name // for the media button and register it Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); @@ -5288,7 +5087,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * Helper function: * Set the new remote control receiver at the top of the RC focus stack. * Called synchronized on mAudioFocusLock, then mRCStack - * precondition: mediaIntent != null, target != null + * precondition: mediaIntent != null */ private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target) { // already at top of stack? @@ -5317,8 +5116,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRCStack.push(rcse); // rcse is never null // post message to persist the default media button receiver - mAudioHandler.sendMessage( mAudioHandler.obtainMessage( - MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + if (target != null) { + mAudioHandler.sendMessage( mAudioHandler.obtainMessage( + MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + } } /** @@ -5618,7 +5419,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) - * precondition: mediaIntent != null, target != null + * precondition: mediaIntent != null */ public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) { Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); @@ -5636,7 +5437,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) * precondition: mediaIntent != null, eventReceiver != null */ - public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) + public void unregisterMediaButtonIntent(PendingIntent mediaIntent) { Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); @@ -6018,6 +5819,29 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_QUEUE, generationId /* arg1 */, + 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + } + + public void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + + ", timeMs=" + timeMs + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { + // tell the current client to seek to the requested location + try { + mCurrentRcClient.seekTo(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } + } + } + } + public void setPlaybackInfoForRcc(int rccId, int what, int value) { sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); @@ -6391,10 +6215,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } else { - s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], - false /*lastAudible*/); - s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], - true /*lastAudible*/); + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]); mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); } @@ -6540,7 +6361,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void enforceSafeMediaVolume() { VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - boolean lastAudible = (streamState.muteCount() != 0); int devices = mSafeMediaVolumeDevices; int i = 0; @@ -6549,27 +6369,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if ((device & devices) == 0) { continue; } - int index = streamState.getIndex(device, lastAudible); + int index = streamState.getIndex(device); if (index > mSafeMediaVolumeIndex) { - if (lastAudible) { - streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device); - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } else { - streamState.setIndex(mSafeMediaVolumeIndex, device, true); - sendMsg(mAudioHandler, - MSG_SET_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); - } + streamState.setIndex(mSafeMediaVolumeIndex, device); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); } devices &= ~device; } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index cd50de4..399eb7b 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -791,7 +791,7 @@ public class AudioTrack * {@link #ERROR_INVALID_OPERATION} */ public int setPlaybackRate(int sampleRateInHz) { - if (mState == STATE_UNINITIALIZED) { + if (mState != STATE_INITIALIZED) { return ERROR_INVALID_OPERATION; } if (sampleRateInHz <= 0) { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index e21b26b..25aae8f 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -121,16 +121,11 @@ interface IAudioService { void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent); void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c); - oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c); + oneway void unregisterMediaButtonIntent(in PendingIntent pi); oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c); oneway void unregisterMediaButtonEventReceiverForCalls(); - int registerRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient, in String callingPackageName); - oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient); - /** * Register an IRemoteControlDisplay. * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient @@ -157,9 +152,29 @@ interface IAudioService { * display doesn't need to receive artwork. */ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); + /** + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + void setRemoteControlClientPlaybackPosition(int generationId, long timeMs); + + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)} + */ + int registerRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient, in String callingPackageName); + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)} + */ + oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); - void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); + void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); int getRemoteStreamMaxVolume(); int getRemoteStreamVolume(); oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 5600263..e4cee06 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -47,4 +47,5 @@ oneway interface IRemoteControlClient void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); + void seekTo(int clientGeneration, long timeMs); }
\ No newline at end of file diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl index 095cf80..c70889c 100644 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ b/media/java/android/media/IRemoteControlDisplay.aidl @@ -43,7 +43,16 @@ oneway interface IRemoteControlDisplay void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed); - void setTransportControlFlags(int generationId, int transportControlFlags); + /** + * Sets the transport control flags and playback position capabilities of a client. + * @param generationId the current generation ID as known by this client + * @param transportControlFlags bitmask of the transport controls this client supports, see + * {@link RemoteControlClient#setTransportControlFlags(int)} + * @param posCapabilities a bit mask for playback position capabilities, see + * {@link RemoteControlClient#MEDIA_POSITION_READABLE} and + * {@link RemoteControlClient#MEDIA_POSITION_WRITABLE} + */ + void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities); void setMetadata(int generationId, in Bundle metadata); diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 4561d3f..4eb0c56 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -25,11 +25,12 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Bundle; +import android.os.Parcel; import android.util.Log; /** - * MediaDrm class can be used in conjunction with {@link android.media.MediaCrypto} - * to obtain licenses for decoding encrypted media data. + * MediaDrm can be used in conjunction with {@link android.media.MediaCrypto} + * to obtain keys for decrypting protected media data. * * Crypto schemes are assigned 16 byte UUIDs, * the method {@link #isCryptoSchemeSupported} can be used to query if a given @@ -131,9 +132,11 @@ public final class MediaDrm { void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data); } - /* Do not change these values without updating their counterparts - * in include/media/mediadrm.h! - */ + public static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = 1; + public static final int MEDIA_DRM_EVENT_KEY_REQUIRED = 2; + public static final int MEDIA_DRM_EVENT_KEY_EXPIRED = 3; + public static final int MEDIA_DRM_EVENT_VENDOR_DEFINED = 4; + private static final int DRM_EVENT = 200; private class EventHandler extends Handler @@ -157,10 +160,18 @@ public final class MediaDrm { Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")"); if (mOnEventListener != null) { - Bundle bundle = msg.getData(); - byte[] sessionId = bundle.getByteArray("sessionId"); - byte[] data = bundle.getByteArray("data"); - mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); + if (msg.obj != null && msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + byte[] sessionId = parcel.createByteArray(); + if (sessionId.length == 0) { + sessionId = null; + } + byte[] data = parcel.createByteArray(); + if (data.length == 0) { + data = null; + } + mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); + } } return; @@ -179,14 +190,14 @@ public final class MediaDrm { * the cookie passed to native_setup().) */ private static void postEventFromNative(Object mediadrm_ref, - int what, int arg1, int arg2, Object obj) + int eventType, int extra, Object obj) { MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get(); if (md == null) { return; } if (md.mEventHandler != null) { - Message m = md.mEventHandler.obtainMessage(what, arg1, arg2, obj); + Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj); md.mEventHandler.sendMessage(m); } } @@ -197,68 +208,88 @@ public final class MediaDrm { public native byte[] openSession() throws MediaDrmException; /** - * Close a session on the MediaDrm object. + * Close a session on the MediaDrm object that was previously opened + * with {@link #openSession}. */ public native void closeSession(byte[] sessionId) throws MediaDrmException; - public static final int MEDIA_DRM_LICENSE_TYPE_STREAMING = 1; - public static final int MEDIA_DRM_LICENSE_TYPE_OFFLINE = 2; + public static final int MEDIA_DRM_KEY_TYPE_STREAMING = 1; + public static final int MEDIA_DRM_KEY_TYPE_OFFLINE = 2; - public final class LicenseRequest { - public LicenseRequest() {} + public final class KeyRequest { + public KeyRequest() {} public byte[] data; public String defaultUrl; }; /** - * A license request/response exchange occurs between the app and a License - * Server to obtain the keys required to decrypt the content. getLicenseRequest() - * is used to obtain an opaque license request byte array that is delivered to the - * license server. The opaque license request byte array is returned in - * LicenseReqeust.data. The recommended URL to deliver the license request to is - * returned in LicenseRequest.defaultUrl + * A key request/response exchange occurs between the app and a license + * server to obtain the keys to decrypt encrypted content. getKeyRequest() + * is used to obtain an opaque key request byte array that is delivered to the + * license server. The opaque key request byte array is returned in + * KeyRequest.data. The recommended URL to deliver the key request to is + * returned in KeyRequest.defaultUrl. + * + * After the app has received the key request response from the server, + * it should deliver to the response to the DRM engine plugin using the method + * {@link #provideKeyResponse}. * * @param sessonId the session ID for the drm session * @param init container-specific data, its meaning is interpreted based on the * mime type provided in the mimeType parameter. It could contain, for example, * the content ID, key ID or other data obtained from the content metadata that is - * required in generating the license request. + * required in generating the key request. * @param mimeType identifies the mime type of the content - * @param licenseType specifes if the license is for streaming or offline content - * @param optionalParameters are included in the license server request message to + * @param keyType specifes if the request is for streaming or offline content + * @param optionalParameters are included in the key request message to * allow a client application to provide additional message parameters to the server. */ - public native LicenseRequest getLicenseRequest( byte[] sessionId, byte[] init, - String mimeType, int licenseType, - HashMap<String, String> optionalParameters ) + public native KeyRequest getKeyRequest(byte[] sessionId, byte[] init, + String mimeType, int keyType, + HashMap<String, String> optionalParameters) throws MediaDrmException; /** - * After a license response is received by the app, it is provided to the DRM plugin - * using provideLicenseResponse. + * A key response is received from the license server by the app, then it is + * provided to the DRM engine plugin using provideKeyResponse. The byte array + * returned is a keySetId that can be used to later restore the keys to a new + * session with the method {@link restoreKeys}, enabling offline key use. * * @param sessionId the session ID for the DRM session * @param response the byte array response from the server */ - public native void provideLicenseResponse( byte[] sessionId, byte[] response ) + public native byte[] provideKeyResponse(byte[] sessionId, byte[] response) throws MediaDrmException; /** - * Remove the keys associated with a license for a session + * Restore persisted offline keys into a new session. keySetId identifies the + * keys to load, obtained from a prior call to {@link provideKeyResponse}. + * * @param sessionId the session ID for the DRM session + * @param keySetId identifies the saved key set to restore */ - public native void removeLicense( byte[] sessionId ) throws MediaDrmException; + public native void restoreKeys(byte[] sessionId, byte[] keySetId) + throws MediaDrmException; /** - * Request an informative description of the license for the session. The status is + * Remove the persisted keys associated with an offline license. Keys are persisted + * when {@link provideKeyResponse} is called with keys obtained from the method + * {@link getKeyRequest} using keyType = MEDIA_DRM_KEY_TYPE_OFFLINE. + * + * @param keySetId identifies the saved key set to remove + */ + public native void removeKeys(byte[] keySetId) throws MediaDrmException; + + /** + * Request an informative description of the key status for the session. The status is * in the form of {name, value} pairs. Since DRM license policies vary by vendor, * the specific status field names are determined by each DRM vendor. Refer to your * DRM provider documentation for definitions of the field names for a particular - * DrmEngine. + * DRM engine plugin. * * @param sessionId the session ID for the DRM session */ - public native HashMap<String, String> queryLicenseStatus( byte[] sessionId ) + public native HashMap<String, String> queryKeyStatus(byte[] sessionId) throws MediaDrmException; public final class ProvisionRequest { @@ -269,22 +300,23 @@ public final class MediaDrm { /** * A provision request/response exchange occurs between the app and a provisioning - * server to retrieve a device certificate. getProvisionRequest is used to obtain - * an opaque license request byte array that is delivered to the provisioning server. - * The opaque provision request byte array is returned in ProvisionRequest.data - * The recommended URL to deliver the license request to is returned in - * ProvisionRequest.defaultUrl. + * server to retrieve a device certificate. If provisionining is required, the + * MEDIA_DRM_EVENT_PROVISION_REQUIRED event will be sent to the event handler. + * getProvisionRequest is used to obtain the opaque provision request byte array that + * should be delivered to the provisioning server. The provision request byte array + * is returned in ProvisionRequest.data. The recommended URL to deliver the provision + * request to is returned in ProvisionRequest.defaultUrl. */ public native ProvisionRequest getProvisionRequest() throws MediaDrmException; /** * After a provision response is received by the app, it is provided to the DRM - * plugin using this method. + * engine plugin using this method. * * @param response the opaque provisioning response byte array to provide to the - * DrmEngine. + * DRM engine plugin. */ - public native void provideProvisionResponse( byte[] response ) + public native void provideProvisionResponse(byte[] response) throws MediaDrmException; /** @@ -314,38 +346,140 @@ public final class MediaDrm { * * @param ssRelease the server response indicating which secure stops to release */ - public native void releaseSecureStops( byte[] ssRelease ) + public native void releaseSecureStops(byte[] ssRelease) throws MediaDrmException; /** - * Read a Drm plugin property value, given the property name string. There are several - * forms of property access functions, depending on the data type returned. + * Read a DRM engine plugin property value, given the property name string. There are + * several forms of property access functions, depending on the data type returned. * * Standard fields names are: - * vendor String - identifies the maker of the plugin - * version String - identifies the version of the plugin - * description String - describes the plugin + * vendor String - identifies the maker of the DRM engine plugin + * version String - identifies the version of the DRM engine plugin + * description String - describes the DRM engine plugin * deviceUniqueId byte[] - The device unique identifier is established during device - * provisioning and provides a means of uniquely identifying - * each device + * provisioning and provides a means of uniquely identifying + * each device + * algorithms String - a comma-separate list of cipher and mac algorithms supported + * by CryptoSession. The list may be empty if the DRM engine + * plugin does not support CryptoSession operations. */ - public native String getPropertyString( String propertyName ) + public native String getPropertyString(String propertyName) throws MediaDrmException; - public native byte[] getPropertyByteArray( String propertyName ) + public native byte[] getPropertyByteArray(String propertyName) throws MediaDrmException; /** - * Write a Drm plugin property value. There are several forms of property setting - * functions, depending on the data type being set. + * Write a DRM engine plugin property value. There are several forms of + * property setting functions, depending on the data type being set. */ - public native void setPropertyString( String propertyName, String value ) + public native void setPropertyString(String propertyName, String value) throws MediaDrmException; - public native void setPropertyByteArray( String propertyName, byte[] value ) + public native void setPropertyByteArray(String propertyName, byte[] value) throws MediaDrmException; + /** + * In addition to supporting decryption of DASH Common Encrypted Media, the + * MediaDrm APIs provide the ability to securely deliver session keys from + * an operator's session key server to a client device, based on the factory-installed + * root of trust, and provide the ability to do encrypt, decrypt, sign and verify + * with the session key on arbitrary user data. + * + * The CryptoSession class implements generic encrypt/decrypt/sign/verify methods + * based on the established session keys. These keys are exchanged using the + * getKeyRequest/provideKeyResponse methods. + * + * Applications of this capability could include securing various types of + * purchased or private content, such as applications, books and other media, + * photos or media delivery protocols. + * + * Operators can create session key servers that are functionally similar to a + * license key server, except that instead of receiving license key requests and + * providing encrypted content keys which are used specifically to decrypt A/V media + * content, the session key server receives session key requests and provides + * encrypted session keys which can be used for general purpose crypto operations. + */ + + private static final native void setCipherAlgorithmNative(MediaDrm drm, byte[] sessionId, + String algorithm); + + private static final native void setMacAlgorithmNative(MediaDrm drm, byte[] sessionId, + String algorithm); + + private static final native byte[] encryptNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] input, byte[] iv); + + private static final native byte[] decryptNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] input, byte[] iv); + + private static final native byte[] signNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] message); + + private static final native boolean verifyNative(MediaDrm drm, byte[] sessionId, + byte[] keyId, byte[] message, + byte[] signature); + + public final class CryptoSession { + private MediaDrm mDrm; + private byte[] mSessionId; + + /** + * Construct a CryptoSession which can be used to encrypt, decrypt, + * sign and verify messages or data using the session keys established + * for the session using methods {@link getKeyRequest} and + * {@link provideKeyResponse} using a session key server. + * + * @param sessionId the session ID for the session containing keys + * to be used for encrypt, decrypt, sign and/or verify + * + * @param cipherAlgorithm the algorithm to use for encryption and + * decryption ciphers. The algorithm string conforms to JCA Standard + * Names for Cipher Transforms and is case insensitive. For example + * "AES/CBC/PKCS5Padding". + * + * @param macAlgorithm the algorithm to use for sign and verify + * The algorithm string conforms to JCA Standard Names for Mac + * Algorithms and is case insensitive. For example "HmacSHA256". + * + * The list of supported algorithms for a DRM engine plugin can be obtained + * using the method {@link getPropertyString("algorithms")} + */ + + public CryptoSession(MediaDrm drm, byte[] sessionId, + String cipherAlgorithm, String macAlgorithm) + throws MediaDrmException { + mSessionId = sessionId; + mDrm = drm; + setCipherAlgorithmNative(drm, sessionId, cipherAlgorithm); + setMacAlgorithmNative(drm, sessionId, macAlgorithm); + } + + public byte[] encrypt(byte[] keyid, byte[] input, byte[] iv) { + return encryptNative(mDrm, mSessionId, keyid, input, iv); + } + + public byte[] decrypt(byte[] keyid, byte[] input, byte[] iv) { + return decryptNative(mDrm, mSessionId, keyid, input, iv); + } + + public byte[] sign(byte[] keyid, byte[] message) { + return signNative(mDrm, mSessionId, keyid, message); + } + public boolean verify(byte[] keyid, byte[] message, byte[] signature) { + return verifyNative(mDrm, mSessionId, keyid, message, signature); + } + }; + + public CryptoSession getCryptoSession(byte[] sessionId, + String cipherAlgorithm, + String macAlgorithm) + throws MediaDrmException { + return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm); + } + @Override protected void finalize() { native_finalize(); diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index cc59d02..376bb2d 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -367,7 +367,7 @@ public class MediaMetadataRetriever * counterparts in include/media/mediametadataretriever.h! */ /** - * The metadata key to retrieve the numberic string describing the + * The metadata key to retrieve the numeric string describing the * order of the audio data source on its original recording. */ public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index f186000..e076ef0 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -278,11 +278,14 @@ public class RemoteControlClient public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; /** * @hide - * (to be un-hidden and added in javadoc of setTransportControlFlags(int)) + * TODO un-hide and add in javadoc of setTransportControlFlags(int) * Flag indicating a RemoteControlClient can receive changes in the media playback position - * through the {@link #OnPlaybackPositionUpdateListener} interface. - * + * through the {@link #OnPlaybackPositionUpdateListener} interface. This flag must be set + * in order for components that display the RemoteControlClient information, to display and + * let the user control media playback position. * @see #setTransportControlFlags(int) + * @see #setPlaybackPositionProvider(PlaybackPositionProvider) + * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; @@ -605,7 +608,7 @@ public class RemoteControlClient /** * @hide - * (to be un-hidden) + * TODO un-hide * Sets the current playback state and the matching media position for the current playback * speed. * @param state The current playback state, one of the following values: @@ -630,11 +633,6 @@ public class RemoteControlClient */ public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { synchronized(mCacheLock) { - if (timeInMs != PLAYBACK_POSITION_INVALID) { - mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; - } else { - mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; - } if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) || (mPlaybackSpeed != playbackSpeed)) { // store locally @@ -670,19 +668,20 @@ public class RemoteControlClient mTransportControlFlags = transportControlFlags; // send to remote control display if conditions are met - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); } } /** * @hide - * (to be un-hidden) + * TODO un-hide * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE */ public interface OnPlaybackPositionUpdateListener { /** - * Called on the listener to notify it that the playback head should be set at the given + * Called on the implementer to notify it that the playback head should be set at the given * position. If the position can be changed from its current value, the implementor of * the interface should also update the playback position using * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new @@ -694,8 +693,25 @@ public class RemoteControlClient /** * @hide - * (to be un-hidden) - * Sets the listener RemoteControlClient calls whenever the media playback position is requested + * TODO un-hide + * Interface definition for a callback to be invoked when the media playback position is + * queried. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE + */ + public interface PlaybackPositionProvider { + /** + * Called on the implementer of the interface to query the current playback position. + * @return a negative value if the current playback position (or the last valid playback + * position) is not known, or a zero or positive value expressed in ms indicating the + * current position, or the last valid known position. + */ + long getPlaybackPosition(); + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called whenever the media playback position is requested * to be updated. * Notifications will be received in the same thread as the one in which RemoteControlClient * was created. @@ -703,16 +719,41 @@ public class RemoteControlClient */ public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { synchronized(mCacheLock) { - if ((mPositionUpdateListener == null) && (l != null)) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; - // tell RCDs and AudioService this RCC accepts position updates - // TODO implement - } else if ((mPositionUpdateListener != null) && (l == null)) { + } else { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; - // tell RCDs and AudioService this RCC doesn't handle position updates - // TODO implement } mPositionUpdateListener = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } + } + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called whenever the media current playback position is needed. + * Queries will be received in the same thread as the one in which RemoteControlClient + * was created. + * @param l + */ + public void setPlaybackPositionProvider(PlaybackPositionProvider l) { + synchronized(mCacheLock) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { + mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; + } else { + mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; + } + mPositionProvider = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } } } @@ -896,6 +937,10 @@ public class RemoteControlClient */ private OnPlaybackPositionUpdateListener mPositionUpdateListener; /** + * Provider registered by user of RemoteControlClient to provide the current playback position. + */ + private PlaybackPositionProvider mPositionProvider; + /** * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; @@ -957,14 +1002,14 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { - public void onInformationRequested(int clientGeneration, int infoFlags) { + public void onInformationRequested(int generationId, int infoFlags) { // only post messages, we can't block here if (mEventHandler != null) { // signal new client mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); mEventHandler.dispatchMessage( mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, - /*arg1*/ clientGeneration, /*arg2, ignored*/ 0)); + /*arg1*/ generationId, /*arg2, ignored*/ 0)); // send the information mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); mEventHandler.removeMessages(MSG_REQUEST_METADATA); @@ -1011,6 +1056,16 @@ public class RemoteControlClient MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); } } + + public void seekTo(int generationId, long timeMs) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.removeMessages(MSG_SEEK_TO); + mEventHandler.dispatchMessage(mEventHandler.obtainMessage( + MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */, + new Long(timeMs))); + } + } }; /** @@ -1051,6 +1106,7 @@ public class RemoteControlClient private final static int MSG_PLUG_DISPLAY = 7; private final static int MSG_UNPLUG_DISPLAY = 8; private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; + private final static int MSG_SEEK_TO = 10; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1072,7 +1128,7 @@ public class RemoteControlClient break; case MSG_REQUEST_TRANSPORTCONTROL: synchronized (mCacheLock) { - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); } break; case MSG_REQUEST_ARTWORK: @@ -1095,6 +1151,8 @@ public class RemoteControlClient case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); break; + case MSG_SEEK_TO: + onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1136,14 +1194,14 @@ public class RemoteControlClient } } - private void sendTransportControlFlags_syncCacheLock() { + private void sendTransportControlInfo_syncCacheLock() { if (mCurrentClientGenId == mInternalClientGenId) { final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { - di.mRcDisplay.setTransportControlFlags(mInternalClientGenId, - mTransportControlFlags); + di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + mTransportControlFlags, mPlaybackPositionCapabilities); } catch (RemoteException e) { Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, e); @@ -1325,6 +1383,14 @@ public class RemoteControlClient } } + private void onSeekTo(int generationId, long timeMs) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { + mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); + } + } + } + //=========================================================== // Internal utilities diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index ea12803..632334b 100644 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -96,7 +96,8 @@ public class MtpDatabase { Files.FileColumns.FORMAT, // 2 Files.FileColumns.PARENT, // 3 Files.FileColumns.DATA, // 4 - Files.FileColumns.DATE_MODIFIED, // 5 + Files.FileColumns.DATE_ADDED, // 5 + Files.FileColumns.DATE_MODIFIED, // 6 }; private static final String ID_WHERE = Files.FileColumns._ID + "=?"; private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; @@ -840,7 +841,7 @@ public class MtpDatabase { } private boolean getObjectInfo(int handle, int[] outStorageFormatParent, - char[] outName, long[] outModified) { + char[] outName, long[] outCreatedModified) { Cursor c = null; try { c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION, @@ -861,7 +862,12 @@ public class MtpDatabase { path.getChars(start, end, outName, 0); outName[end - start] = 0; - outModified[0] = c.getLong(5); + outCreatedModified[0] = c.getLong(5); + outCreatedModified[1] = c.getLong(6); + // use modification date as creation date if date added is not set + if (outCreatedModified[0] == 0) { + outCreatedModified[0] = outCreatedModified[1]; + } return true; } } catch (RemoteException e) { diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 9938f76..c32ba9d 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -21,10 +21,12 @@ #include "android_media_MediaDrm.h" #include "android_runtime/AndroidRuntime.h" +#include "android_os_Parcel.h" #include "jni.h" #include "JNIHelp.h" #include <binder/IServiceManager.h> +#include <binder/Parcel.h> #include <media/IDrm.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ADebug.h> @@ -43,6 +45,15 @@ namespace android { var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! var, "Unable to find method " fieldName); +#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +#define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method " fieldName); + + struct RequestFields { jfieldID data; jfieldID defaultUrl; @@ -74,9 +85,17 @@ struct EntryFields { jmethodID getValue; }; +struct EventTypes { + int kEventProvisionRequired; + int kEventKeyRequired; + int kEventKeyExpired; + int kEventVendorDefined; +} gEventTypes; + struct fields_t { jfieldID context; - RequestFields licenseRequest; + jmethodID post_event; + RequestFields keyRequest; RequestFields provisionRequest; ArrayListFields arraylist; HashmapFields hashmap; @@ -87,6 +106,88 @@ struct fields_t { static fields_t gFields; +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIDrmListener: public DrmListener +{ +public: + JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIDrmListener(); + virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj = NULL); +private: + JNIDrmListener(); + jclass mClass; // Reference to MediaDrm class + jobject mObject; // Weak ref to MediaDrm Java object to call on +}; + +JNIDrmListener::JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + // Hold onto the MediaDrm class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + ALOGE("Can't find android/media/MediaDrm"); + jniThrowException(env, "java/lang/Exception", NULL); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the MediaDrm object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIDrmListener::~JNIDrmListener() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, + const Parcel *obj) +{ + jint jeventType; + + // translate DrmPlugin event types into their java equivalents + switch(eventType) { + case DrmPlugin::kDrmPluginEventProvisionRequired: + jeventType = gEventTypes.kEventProvisionRequired; + break; + case DrmPlugin::kDrmPluginEventKeyNeeded: + jeventType = gEventTypes.kEventKeyRequired; + break; + case DrmPlugin::kDrmPluginEventKeyExpired: + jeventType = gEventTypes.kEventKeyExpired; + break; + case DrmPlugin::kDrmPluginEventVendorDefined: + jeventType = gEventTypes.kEventVendorDefined; + break; + default: + ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType); + return; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (obj && obj->dataSize() > 0) { + jobject jParcel = createJavaParcelObject(env); + if (jParcel != NULL) { + Parcel* nativeParcel = parcelForJavaObject(env, jParcel); + nativeParcel->setData(obj->data(), obj->dataSize()); + env->CallStaticVoidMethod(mClass, gFields.post_event, mObject, + jeventType, extra, jParcel); + } + } + + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + LOGW_EX(env); + env->ExceptionClear(); + } +} + + static bool throwExceptionAsNecessary( JNIEnv *env, status_t err, const char *msg = NULL) { @@ -109,6 +210,9 @@ JDrm::JDrm( JNIEnv *env, jobject thiz, const uint8_t uuid[16]) { mObject = env->NewWeakGlobalRef(thiz); mDrm = MakeDrm(uuid); + if (mDrm != NULL) { + mDrm->setListener(this); + } } JDrm::~JDrm() { @@ -160,6 +264,25 @@ sp<IDrm> JDrm::MakeDrm(const uint8_t uuid[16]) { return drm; } +status_t JDrm::setListener(const sp<DrmListener>& listener) { + Mutex::Autolock lock(mLock); + mListener = listener; + return OK; +} + +void JDrm::notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) { + sp<DrmListener> listener; + mLock.lock(); + listener = mListener; + mLock.unlock(); + + if (listener != NULL) { + Mutex::Autolock lock(mNotifyLock); + listener->notify(eventType, extra, obj); + } +} + + // static bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) { sp<IDrm> drm = MakeDrm(); @@ -194,16 +317,16 @@ static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector) } static String8 JStringToString8(JNIEnv *env, jstring const &jstr) { - jboolean isCopy; String8 result; - const char *s = env->GetStringUTFChars(jstr, &isCopy); + const char *s = env->GetStringUTFChars(jstr, NULL); if (s) { result = s; env->ReleaseStringUTFChars(jstr, s); } return result; } + /* import java.util.HashMap; import java.util.Set; @@ -321,17 +444,32 @@ static bool CheckSession(JNIEnv *env, const sp<IDrm> &drm, jbyteArray const &jse } static void android_media_MediaDrm_release(JNIEnv *env, jobject thiz) { - setDrm(env, thiz, NULL); + sp<JDrm> drm = setDrm(env, thiz, NULL); + if (drm != NULL) { + drm->setListener(NULL); + } } static void android_media_MediaDrm_native_init(JNIEnv *env) { jclass clazz; FIND_CLASS(clazz, "android/media/MediaDrm"); GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "I"); - - FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest"); - GET_FIELD_ID(gFields.licenseRequest.data, clazz, "data", "[B"); - GET_FIELD_ID(gFields.licenseRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;"); + GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative", + "(Ljava/lang/Object;IILjava/lang/Object;)V"); + + jfieldID field; + GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_PROVISION_REQUIRED", "I"); + gEventTypes.kEventProvisionRequired = env->GetStaticIntField(clazz, field); + GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_KEY_REQUIRED", "I"); + gEventTypes.kEventKeyRequired = env->GetStaticIntField(clazz, field); + GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_KEY_EXPIRED", "I"); + gEventTypes.kEventKeyExpired = env->GetStaticIntField(clazz, field); + GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_VENDOR_DEFINED", "I"); + gEventTypes.kEventVendorDefined = env->GetStaticIntField(clazz, field); + + FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); + GET_FIELD_ID(gFields.keyRequest.data, clazz, "data", "[B"); + GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;"); FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); GET_FIELD_ID(gFields.provisionRequest.data, clazz, "data", "[B"); @@ -388,6 +526,8 @@ static void android_media_MediaDrm_native_setup( return; } + sp<JNIDrmListener> listener = new JNIDrmListener(env, thiz, weak_this); + drm->setListener(listener); setDrm(env, thiz, drm); } @@ -451,9 +591,9 @@ static void android_media_MediaDrm_closeSession( throwExceptionAsNecessary(env, err, "Failed to close session"); } -static jobject android_media_MediaDrm_getLicenseRequest( +static jobject android_media_MediaDrm_getKeyRequest( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jinitData, - jstring jmimeType, jint jlicenseType, jobject joptParams) { + jstring jmimeType, jint jkeyType, jobject joptParams) { sp<IDrm> drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { @@ -472,7 +612,7 @@ static jobject android_media_MediaDrm_getLicenseRequest( mimeType = JStringToString8(env, jmimeType); } - DrmPlugin::LicenseType licenseType = (DrmPlugin::LicenseType)jlicenseType; + DrmPlugin::KeyType keyType = (DrmPlugin::KeyType)jkeyType; KeyedVector<String8, String8> optParams; if (joptParams != NULL) { @@ -482,68 +622,94 @@ static jobject android_media_MediaDrm_getLicenseRequest( Vector<uint8_t> request; String8 defaultUrl; - status_t err = drm->getLicenseRequest(sessionId, initData, mimeType, - licenseType, optParams, request, defaultUrl); + status_t err = drm->getKeyRequest(sessionId, initData, mimeType, + keyType, optParams, request, defaultUrl); - if (throwExceptionAsNecessary(env, err, "Failed to get license request")) { + if (throwExceptionAsNecessary(env, err, "Failed to get key request")) { return NULL; } // Fill out return obj jclass clazz; - FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest"); + FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); - jobject licenseObj = NULL; + jobject keyObj = NULL; if (clazz) { - licenseObj = env->AllocObject(clazz); + keyObj = env->AllocObject(clazz); jbyteArray jrequest = VectorToJByteArray(env, request); - env->SetObjectField(licenseObj, gFields.licenseRequest.data, jrequest); + env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest); jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); - env->SetObjectField(licenseObj, gFields.licenseRequest.defaultUrl, jdefaultUrl); + env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl); } - return licenseObj; + return keyObj; } -static void android_media_MediaDrm_provideLicenseResponse( +static jbyteArray android_media_MediaDrm_provideKeyResponse( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jresponse) { sp<IDrm> drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { - return; + return NULL; } Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); if (jresponse == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; + return NULL; } Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); + Vector<uint8_t> keySetId; - status_t err = drm->provideLicenseResponse(sessionId, response); + status_t err = drm->provideKeyResponse(sessionId, response, keySetId); - throwExceptionAsNecessary(env, err, "Failed to handle license response"); + throwExceptionAsNecessary(env, err, "Failed to handle key response"); + return VectorToJByteArray(env, keySetId); } -static void android_media_MediaDrm_removeLicense( - JNIEnv *env, jobject thiz, jbyteArray jsessionId) { +static void android_media_MediaDrm_removeKeys( + JNIEnv *env, jobject thiz, jbyteArray jkeysetId) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (jkeysetId == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId)); + + status_t err = drm->removeKeys(keySetId); + + throwExceptionAsNecessary(env, err, "Failed to remove keys"); +} + +static void android_media_MediaDrm_restoreKeys( + JNIEnv *env, jobject thiz, jbyteArray jsessionId, + jbyteArray jkeysetId) { + sp<IDrm> drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return; } + if (jkeysetId == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId)); - status_t err = drm->removeLicense(sessionId); + status_t err = drm->restoreKeys(sessionId, keySetId); - throwExceptionAsNecessary(env, err, "Failed to remove license"); + throwExceptionAsNecessary(env, err, "Failed to restore keys"); } -static jobject android_media_MediaDrm_queryLicenseStatus( +static jobject android_media_MediaDrm_queryKeyStatus( JNIEnv *env, jobject thiz, jbyteArray jsessionId) { sp<IDrm> drm = GetDrm(env, thiz); @@ -554,9 +720,9 @@ static jobject android_media_MediaDrm_queryLicenseStatus( KeyedVector<String8, String8> infoMap; - status_t err = drm->queryLicenseStatus(sessionId, infoMap); + status_t err = drm->queryKeyStatus(sessionId, infoMap); - if (throwExceptionAsNecessary(env, err, "Failed to query license")) { + if (throwExceptionAsNecessary(env, err, "Failed to query key status")) { return NULL; } @@ -752,6 +918,162 @@ static void android_media_MediaDrm_setPropertyByteArray( throwExceptionAsNecessary(env, err, "Failed to set property"); } +static void android_media_MediaDrm_setCipherAlgorithmNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jstring jalgorithm) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + if (jalgorithm == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + String8 algorithm = JStringToString8(env, jalgorithm); + + status_t err = drm->setCipherAlgorithm(sessionId, algorithm); + + throwExceptionAsNecessary(env, err, "Failed to set cipher algorithm"); +} + +static void android_media_MediaDrm_setMacAlgorithmNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jstring jalgorithm) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + if (jalgorithm == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + String8 algorithm = JStringToString8(env, jalgorithm); + + status_t err = drm->setMacAlgorithm(sessionId, algorithm); + + throwExceptionAsNecessary(env, err, "Failed to set mac algorithm"); +} + + +static jbyteArray android_media_MediaDrm_encryptNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return NULL; + } + + if (jkeyId == NULL || jinput == NULL || jiv == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); + Vector<uint8_t> input(JByteArrayToVector(env, jinput)); + Vector<uint8_t> iv(JByteArrayToVector(env, jiv)); + Vector<uint8_t> output; + + status_t err = drm->encrypt(sessionId, keyId, input, iv, output); + + throwExceptionAsNecessary(env, err, "Failed to encrypt"); + + return VectorToJByteArray(env, output); +} + +static jbyteArray android_media_MediaDrm_decryptNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return NULL; + } + + if (jkeyId == NULL || jinput == NULL || jiv == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); + Vector<uint8_t> input(JByteArrayToVector(env, jinput)); + Vector<uint8_t> iv(JByteArrayToVector(env, jiv)); + Vector<uint8_t> output; + + status_t err = drm->decrypt(sessionId, keyId, input, iv, output); + throwExceptionAsNecessary(env, err, "Failed to decrypt"); + + return VectorToJByteArray(env, output); +} + +static jbyteArray android_media_MediaDrm_signNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jbyteArray jkeyId, jbyteArray jmessage) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return NULL; + } + + if (jkeyId == NULL || jmessage == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); + Vector<uint8_t> message(JByteArrayToVector(env, jmessage)); + Vector<uint8_t> signature; + + status_t err = drm->sign(sessionId, keyId, message, signature); + + throwExceptionAsNecessary(env, err, "Failed to sign"); + + return VectorToJByteArray(env, signature); +} + +static jboolean android_media_MediaDrm_verifyNative( + JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, + jbyteArray jkeyId, jbyteArray jmessage, jbyteArray jsignature) { + + sp<IDrm> drm = GetDrm(env, jdrm); + + if (!CheckSession(env, drm, jsessionId)) { + return false; + } + + if (jkeyId == NULL || jmessage == NULL || jsignature == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); + Vector<uint8_t> message(JByteArrayToVector(env, jmessage)); + Vector<uint8_t> signature(JByteArrayToVector(env, jsignature)); + bool match; + + status_t err = drm->verify(sessionId, keyId, message, signature, match); + + throwExceptionAsNecessary(env, err, "Failed to verify"); + return match; +} + static JNINativeMethod gMethods[] = { { "release", "()V", (void *)android_media_MediaDrm_release }, @@ -772,18 +1094,21 @@ static JNINativeMethod gMethods[] = { { "closeSession", "([B)V", (void *)android_media_MediaDrm_closeSession }, - { "getLicenseRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)" - "Landroid/media/MediaDrm$LicenseRequest;", - (void *)android_media_MediaDrm_getLicenseRequest }, + { "getKeyRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)" + "Landroid/media/MediaDrm$KeyRequest;", + (void *)android_media_MediaDrm_getKeyRequest }, - { "provideLicenseResponse", "([B[B)V", - (void *)android_media_MediaDrm_provideLicenseResponse }, + { "provideKeyResponse", "([B[B)[B", + (void *)android_media_MediaDrm_provideKeyResponse }, - { "removeLicense", "([B)V", - (void *)android_media_MediaDrm_removeLicense }, + { "removeKeys", "([B)V", + (void *)android_media_MediaDrm_removeKeys }, - { "queryLicenseStatus", "([B)Ljava/util/HashMap;", - (void *)android_media_MediaDrm_queryLicenseStatus }, + { "restoreKeys", "([B[B)V", + (void *)android_media_MediaDrm_restoreKeys }, + + { "queryKeyStatus", "([B)Ljava/util/HashMap;", + (void *)android_media_MediaDrm_queryKeyStatus }, { "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;", (void *)android_media_MediaDrm_getProvisionRequest }, @@ -808,6 +1133,26 @@ static JNINativeMethod gMethods[] = { { "setPropertyByteArray", "(Ljava/lang/String;[B)V", (void *)android_media_MediaDrm_setPropertyByteArray }, + + { "setCipherAlgorithmNative", + "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", + (void *)android_media_MediaDrm_setCipherAlgorithmNative }, + + { "setMacAlgorithmNative", + "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", + (void *)android_media_MediaDrm_setMacAlgorithmNative }, + + { "encryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", + (void *)android_media_MediaDrm_encryptNative }, + + { "decryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", + (void *)android_media_MediaDrm_decryptNative }, + + { "signNative", "(Landroid/media/MediaDrm;[B[B[B)[B", + (void *)android_media_MediaDrm_signNative }, + + { "verifyNative", "(Landroid/media/MediaDrm;[B[B[B[B)Z", + (void *)android_media_MediaDrm_verifyNative }, }; int register_android_media_Drm(JNIEnv *env) { diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h index 01067c4..9b3917f 100644 --- a/media/jni/android_media_MediaDrm.h +++ b/media/jni/android_media_MediaDrm.h @@ -20,6 +20,8 @@ #include "jni.h" #include <media/stagefright/foundation/ABase.h> +#include <media/IDrm.h> +#include <media/IDrmClient.h> #include <utils/Errors.h> #include <utils/RefBase.h> @@ -27,15 +29,24 @@ namespace android { struct IDrm; -struct JDrm : public RefBase { +class DrmListener: virtual public RefBase +{ +public: + virtual void notify(DrmPlugin::EventType eventType, int extra, + const Parcel *obj) = 0; +}; + +struct JDrm : public BnDrmClient { static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16]); status_t initCheck() const; - sp<IDrm> getDrm() { return mDrm; } + void notify(DrmPlugin::EventType, int extra, const Parcel *obj); + status_t setListener(const sp<DrmListener>& listener); + protected: virtual ~JDrm(); @@ -43,6 +54,10 @@ private: jweak mObject; sp<IDrm> mDrm; + sp<DrmListener> mListener; + Mutex mNotifyLock; + Mutex mLock; + static sp<IDrm> MakeDrm(); static sp<IDrm> MakeDrm(const uint8_t uuid[16]); diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index bc65de5..fbd5d21 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -775,7 +775,8 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, env->ReleaseIntArrayElements(mIntBuffer, intValues, 0); jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); - info.mDateModified = longValues[0]; + info.mDateCreated = longValues[0]; + info.mDateModified = longValues[1]; env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); // info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ? diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index b498368..5041617 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -76,11 +76,13 @@ class SaveImageInBackgroundData { int result; void clearImage() { - context = null; image = null; imageUri = null; iconSize = 0; } + void clearContext() { + context = null; + } } /** @@ -172,8 +174,6 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi mNotificationBuilder.setLargeIcon(croppedIcon); // But we still don't set it for the expanded view, allowing the smallIcon to show here. mNotificationStyle.bigLargeIcon(null); - - Log.d(TAG, "SaveImageInBackgroundTask constructor"); } @Override @@ -181,7 +181,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi if (params.length != 1) return null; if (isCancelled()) { params[0].clearImage(); - Log.d(TAG, "doInBackground cancelled"); + params[0].clearContext(); return null; } @@ -246,7 +246,6 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi // mounted params[0].clearImage(); params[0].result = 1; - Log.d(TAG, "doInBackground failed"); } // Recycle the bitmap data @@ -254,7 +253,6 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi image.recycle(); } - Log.d(TAG, "doInBackground complete"); return params[0]; } @@ -263,7 +261,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi if (isCancelled()) { params.finisher.run(); params.clearImage(); - Log.d(TAG, "onPostExecute cancelled"); + params.clearContext(); return; } @@ -291,7 +289,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi mNotificationManager.notify(mNotificationId, n); } params.finisher.run(); - Log.d(TAG, "onPostExecute complete"); + params.clearContext(); } } @@ -395,15 +393,12 @@ class GlobalScreenshot { // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK); - - Log.d(TAG, "GlobalScreenshot constructor"); } /** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread(Runnable finisher) { - Log.d(TAG, "saveScreenshotInWorkerThread"); SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.context = mContext; data.image = mScreenBitmap; @@ -411,7 +406,6 @@ class GlobalScreenshot { data.finisher = finisher; if (mSaveInBgTask != null) { mSaveInBgTask.cancel(false); - Log.d(TAG, "saveScreenshotInWorkerThread cancel"); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager, SCREENSHOT_NOTIFICATION_ID).execute(data); @@ -436,8 +430,6 @@ class GlobalScreenshot { * Takes a screenshot of the current display and shows an animation. */ void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { - Log.d(TAG, "takeScreenshot"); - // We need to orient the screenshot correctly (and the Surface api seems to take screenshots // only in the natural orientation of the device :!) mDisplay.getRealMetrics(mDisplayMetrics); @@ -451,8 +443,6 @@ class GlobalScreenshot { mDisplayMatrix.mapPoints(dims); dims[0] = Math.abs(dims[0]); dims[1] = Math.abs(dims[1]); - - Log.d(TAG, "takeScreenshot requiresRotation"); } // Take the screenshot @@ -460,7 +450,6 @@ class GlobalScreenshot { if (mScreenBitmap == null) { notifyScreenshotError(mContext, mNotificationManager); finisher.run(); - Log.d(TAG, "takeScreenshot null bitmap"); return; } @@ -477,7 +466,6 @@ class GlobalScreenshot { // Recycle the previous bitmap mScreenBitmap.recycle(); mScreenBitmap = ss; - Log.d(TAG, "takeScreenshot rotation bitmap created"); } // Optimizations @@ -487,7 +475,6 @@ class GlobalScreenshot { // Start the post-screenshot animation startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, statusBarVisible, navBarVisible); - Log.d(TAG, "takeScreenshot startedAnimation"); } @@ -496,7 +483,6 @@ class GlobalScreenshot { */ private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible, boolean navBarVisible) { - Log.d(TAG, "startAnimation"); // Add the view for the animation mScreenshotView.setImageBitmap(mScreenBitmap); mScreenshotLayout.requestFocus(); @@ -505,11 +491,9 @@ class GlobalScreenshot { if (mScreenshotAnimation != null) { mScreenshotAnimation.end(); mScreenshotAnimation.removeAllListeners(); - Log.d(TAG, "startAnimation reset previous animations"); } mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - Log.d(TAG, "startAnimation layout added to WM"); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); @@ -525,7 +509,6 @@ class GlobalScreenshot { // Clear any references to the bitmap mScreenBitmap = null; mScreenshotView.setImageBitmap(null); - Log.d(TAG, "startAnimation onAnimationEnd"); } }); mScreenshotLayout.post(new Runnable() { @@ -537,7 +520,6 @@ class GlobalScreenshot { mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotView.buildLayer(); mScreenshotAnimation.start(); - Log.d(TAG, "startAnimation post runnable"); } }); } @@ -675,7 +657,6 @@ class GlobalScreenshot { } static void notifyScreenshotError(Context context, NotificationManager nManager) { - Log.d(TAG, "notifyScreenshotError"); Resources r = context.getResources(); // Clear all existing notification, compose the new notification and show it diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 1954af8..6a0fe47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -38,15 +38,12 @@ public class TakeScreenshotService extends Service { final Messenger callback = msg.replyTo; if (mScreenshot == null) { mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); - Log.d(TAG, "Global screenshot initialized"); } - Log.d(TAG, "Global screenshot captured"); mScreenshot.takeScreenshot(new Runnable() { @Override public void run() { Message reply = Message.obtain(null, 1); try { callback.send(reply); - Log.d(TAG, "Global screenshot completed"); } catch (RemoteException e) { } } diff --git a/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java new file mode 100644 index 0000000..3cf7e82 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/GlobalKeyManager.java @@ -0,0 +1,126 @@ +/* + * + * 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.internal.policy.impl; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyEvent; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Stores a mapping of global keys. + * <p> + * A global key will NOT go to the foreground application and instead only ever be sent via targeted + * broadcast to the specified component. The action of the intent will be + * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with + * {@link Intent#EXTRA_KEY_EVENT}. + */ +final class GlobalKeyManager { + + private static final String TAG = "GlobalKeyManager"; + + private static final String TAG_GLOBAL_KEYS = "global_keys"; + private static final String ATTR_VERSION = "version"; + private static final String TAG_KEY = "key"; + private static final String ATTR_KEY_CODE = "keyCode"; + private static final String ATTR_COMPONENT = "component"; + + private static final int GLOBAL_KEY_FILE_VERSION = 1; + + private SparseArray<ComponentName> mKeyMapping; + + public GlobalKeyManager(Context context) { + mKeyMapping = new SparseArray<ComponentName>(); + loadGlobalKeys(context); + } + + /** + * Broadcasts an intent if the keycode is part of the global key mapping. + * + * @param context context used to broadcast the event + * @param keyCode keyCode which triggered this function + * @param event keyEvent which trigged this function + * @return {@code true} if this was handled + */ + boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { + if (mKeyMapping.size() > 0) { + ComponentName component = mKeyMapping.get(keyCode); + if (component != null) { + Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) + .setComponent(component) + .putExtra(Intent.EXTRA_KEY_EVENT, event); + context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); + return true; + } + } + return false; + } + + /** + * Returns {@code true} if the key will be handled globally. + */ + boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) { + return mKeyMapping.get(keyCode) != null; + } + + private void loadGlobalKeys(Context context) { + XmlResourceParser parser = null; + try { + parser = context.getResources().getXml(com.android.internal.R.xml.global_keys); + XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS); + int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0); + if (GLOBAL_KEY_FILE_VERSION == version) { + while (true) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (TAG_KEY.equals(element)) { + String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE); + String componentName = parser.getAttributeValue(null, ATTR_COMPONENT); + int keyCode = KeyEvent.keyCodeFromString(keyCodeName); + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + mKeyMapping.put(keyCode, ComponentName.unflattenFromString( + componentName)); + } + } + } + } + } catch (Resources.NotFoundException e) { + Log.w(TAG, "global keys file not found", e); + } catch (XmlPullParserException e) { + Log.w(TAG, "XML parser exception reading global keys file", e); + } catch (IOException e) { + Log.w(TAG, "I/O exception reading global keys file", e); + } finally { + if (parser != null) { + parser.close(); + } + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index ad5e20b..6b28e8e 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -380,6 +380,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { st.createdPanelView = cb.onCreatePanelView(st.featureId); } + final boolean isActionBarMenu = + (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); + + if (isActionBarMenu && mActionBar != null) { + // Enforce ordering guarantees around events so that the action bar never + // dispatches menu-related events before the panel is prepared. + mActionBar.setMenuPrepared(); + } + if (st.createdPanelView == null) { // Init the panel state's menu--return false if init failed if (st.menu == null || st.refreshMenuContent) { @@ -389,7 +398,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { if (mActionMenuPresenterCallback == null) { mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); } @@ -405,7 +414,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Ditch the menu created above st.setMenu(null); - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { // Don't show it in the action bar either mActionBar.setMenu(null, mActionMenuPresenterCallback); } @@ -430,7 +439,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { // The app didn't want to show the menu for now but it still exists. // Clear it out of the action bar. mActionBar.setMenu(null, mActionMenuPresenterCallback); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index bb05325..49460de 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -441,6 +441,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.WakeLock mBroadcastWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; + // Maps global key codes to the components that will handle them. + private GlobalKeyManager mGlobalKeyManager; + // Fallback actions by key code. private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions = new SparseArray<KeyCharacterMap.FallbackAction>(); @@ -898,6 +901,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableScreenshotChord); + mGlobalKeyManager = new GlobalKeyManager(mContext); + // Controls rotation and the like. initializeHdmiState(); @@ -2140,6 +2145,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return -1; } + if (mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { + return -1; + } + // Let the application handle the key. return 0; } @@ -3585,6 +3594,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + // If the key would be handled globally, just return the result, don't worry about special + // key processing. + if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { + return result; + } + // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: diff --git a/policy/src/com/android/internal/policy/impl/keyguard/ClockView.java b/policy/src/com/android/internal/policy/impl/keyguard/ClockView.java index 6c701c7..34bf6e7 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/ClockView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/ClockView.java @@ -42,7 +42,7 @@ import com.android.internal.R; public class ClockView extends RelativeLayout { private static final String ANDROID_CLOCK_FONT_FILE = "/system/fonts/AndroidClock.ttf"; private final static String M12 = "h:mm"; - private final static String M24 = "kk:mm"; + private final static String M24 = "HH:mm"; private Calendar mCalendar; private String mFormat; diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java index 4df434c..965e378 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java @@ -18,17 +18,22 @@ package com.android.internal.policy.impl.keyguard; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; +import android.view.IRotationWatcher; +import android.view.IWindowManager; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; import com.android.internal.R; - import com.android.internal.widget.LockPatternUtils; +import java.lang.Math; + public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView { private static final String TAG = "FULKeyguardFaceUnlockView"; @@ -45,6 +50,30 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu private boolean mIsShowing = false; private final Object mIsShowingLock = new Object(); + private int mLastRotation; + private boolean mWatchingRotation; + private final IWindowManager mWindowManager = + IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + + private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() { + public void onRotationChanged(int rotation) { + if (DEBUG) Log.d(TAG, "onRotationChanged(): " + mLastRotation + "->" + rotation); + + // If the difference between the new rotation value and the previous rotation value is + // equal to 2, the rotation change was 180 degrees. This stops the biometric unlock + // and starts it in the new position. This is not performed for 90 degree rotations + // since a 90 degree rotation is a configuration change, which takes care of this for + // us. + if (Math.abs(rotation - mLastRotation) == 2) { + if (mBiometricUnlock != null) { + mBiometricUnlock.stop(); + maybeStartBiometricUnlock(); + } + } + mLastRotation = rotation; + } + }; + public KeyguardFaceUnlockView(Context context) { this(context, null); } @@ -91,6 +120,14 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu mBiometricUnlock.stop(); } KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); + if (mWatchingRotation) { + try { + mWindowManager.removeRotationWatcher(mRotationWatcher); + mWatchingRotation = false; + } catch (RemoteException e) { + Log.e(TAG, "Remote exception when removing rotation watcher"); + } + } } @Override @@ -100,6 +137,14 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu mBiometricUnlock.stop(); } KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); + if (mWatchingRotation) { + try { + mWindowManager.removeRotationWatcher(mRotationWatcher); + mWatchingRotation = false; + } catch (RemoteException e) { + Log.e(TAG, "Remote exception when removing rotation watcher"); + } + } } @Override @@ -108,6 +153,17 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu mIsShowing = KeyguardUpdateMonitor.getInstance(mContext).isKeyguardVisible(); maybeStartBiometricUnlock(); KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback); + + // Registers a callback which handles stopping the biometric unlock and restarting it in + // the new position for a 180 degree rotation change. + if (!mWatchingRotation) { + try { + mLastRotation = mWindowManager.watchRotation(mRotationWatcher); + mWatchingRotation = true; + } catch (RemoteException e) { + Log.e(TAG, "Remote exception when adding rotation watcher"); + } + } } @Override @@ -172,9 +228,15 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu return; } - // TODO: Some of these conditions are handled in KeyguardSecurityModel and may not be - // necessary here. + // Although these same conditions are handled in KeyguardSecurityModel, they are still + // necessary here. When a tablet is rotated 90 degrees, a configuration change is + // triggered and everything is torn down and reconstructed. That means + // KeyguardSecurityModel gets a chance to take care of the logic and doesn't even + // reconstruct KeyguardFaceUnlockView if the biometric unlock should be suppressed. + // However, for a 180 degree rotation, no configuration change is triggered, so only + // the logic here is capable of suppressing Face Unlock. if (monitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE + && monitor.isAlternateUnlockEnabled() && !monitor.getMaxBiometricUnlockAttemptsReached() && !backupIsTimedOut) { mBiometricUnlock.start(); diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java index d5798d7..5e3b7da 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java @@ -147,7 +147,7 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick } } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java index e958e9a..159a92d 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java @@ -211,7 +211,7 @@ public class KeyguardUpdateMonitor { } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { } diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index 9b19008..35345f5 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -117,7 +117,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { boolean printedHeader = false; F filter; for (int i=0; i<N && (filter=a[i]) != null; i++) { - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { continue; } if (title != null) { @@ -357,11 +357,11 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } /** - * Return the package that owns this filter. This must be implemented to - * provide correct filtering of Intents that have specified a package name - * they are to be delivered to. + * Returns whether this filter is owned by this package. This must be + * implemented to provide correct filtering of Intents that have + * specified a package name they are to be delivered to. */ - protected abstract String packageForFilter(F filter); + protected abstract boolean isPackageForFilter(String packageName, F filter); protected abstract F[] newArray(int size); @@ -556,7 +556,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } // Is delivery being limited to filters owned by a particular package? - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { if (debug) { Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); } @@ -710,8 +710,8 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() { - @Override protected String packageForFilter(F filter) { - return IntentResolver.this.packageForFilter(filter); + @Override protected boolean isPackageForFilter(String packageName, F filter) { + return IntentResolver.this.isPackageForFilter(packageName, filter); } @Override protected boolean allowFilterResult(F filter, List<R> dest) { return IntentResolver.this.allowFilterResult(filter, dest); diff --git a/services/java/com/android/server/IntentResolverOld.java b/services/java/com/android/server/IntentResolverOld.java index 4dd77ce..94a2379 100644 --- a/services/java/com/android/server/IntentResolverOld.java +++ b/services/java/com/android/server/IntentResolverOld.java @@ -106,7 +106,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object boolean printedHeader = false; for (int i=0; i<N; i++) { F filter = a.get(i); - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { continue; } if (title != null) { @@ -336,11 +336,11 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object } /** - * Return the package that owns this filter. This must be implemented to - * provide correct filtering of Intents that have specified a package name - * they are to be delivered to. + * Returns whether this filter is owned by this package. This must be + * implemented to provide correct filtering of Intents that have + * specified a package name they are to be delivered to. */ - protected abstract String packageForFilter(F filter); + protected abstract boolean isPackageForFilter(String packageName, F filter); @SuppressWarnings("unchecked") protected R newResult(F filter, int match, int userId) { @@ -529,7 +529,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object } // Is delivery being limited to filters owned by a particular package? - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { if (debug) { Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5cf1c28..44d730c 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -237,9 +237,15 @@ public class NotificationManagerService extends INotificationManager.Stub ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE); public Archive() { - } + public void record(StatusBarNotification nr) { + // Nuke heavy parts of notification before storing in archive + nr.notification.tickerView = null; + nr.notification.contentView = null; + nr.notification.bigContentView = null; + nr.notification.largeIcon = null; + if (mBuffer.size() == BUFFER_SIZE) { mBuffer.removeFirst(); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4631395..a30fc3b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -862,6 +862,11 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); + try { + ActivityManagerService.self().startObservingNativeCrashes(); + } catch (Throwable e) { + reportWtf("observing native crashes", e); + } if (!headless) startSystemUi(contextF); try { if (mountServiceF != null) mountServiceF.systemReady(); diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 1663106..167e7af 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -88,7 +88,6 @@ public class Watchdog extends Thread { AlarmManagerService mAlarm; ActivityManagerService mActivity; boolean mCompleted; - boolean mForceKillSystem; Monitor mCurrentMonitor; int mPhonePid; @@ -135,7 +134,9 @@ public class Watchdog extends Thread { final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { - mCurrentMonitor = mMonitors.get(i); + synchronized (Watchdog.this) { + mCurrentMonitor = mMonitors.get(i); + } mCurrentMonitor.monitor(); } @@ -388,6 +389,8 @@ public class Watchdog extends Thread { mCompleted = false; mHandler.sendEmptyMessage(MONITOR); + + final String name; synchronized (this) { long timeout = TIME_TO_WAIT; @@ -396,16 +399,16 @@ public class Watchdog extends Thread { // to timeout on is asleep as well and won't have a chance to run, causing a false // positive on when to kill things. long start = SystemClock.uptimeMillis(); - while (timeout > 0 && !mForceKillSystem) { + while (timeout > 0) { try { - wait(timeout); // notifyAll() is called when mForceKillSystem is set + wait(timeout); } catch (InterruptedException e) { Log.wtf(TAG, e); } timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); } - if (mCompleted && !mForceKillSystem) { + if (mCompleted) { // The monitors have returned. waitedHalf = false; continue; @@ -421,14 +424,14 @@ public class Watchdog extends Thread { waitedHalf = true; continue; } + + name = (mCurrentMonitor != null) ? + mCurrentMonitor.getClass().getName() : "null"; } // If we got here, that means that the system is most likely hung. // First collect stack traces from all threads of the system process. // Then kill this process so that the system will restart. - - final String name = (mCurrentMonitor != null) ? - mCurrentMonitor.getClass().getName() : "null"; EventLog.writeEvent(EventLogTags.WATCHDOG, name); ArrayList<Integer> pids = new ArrayList<Integer>(); diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index eb144ab..14d808f 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -716,7 +716,7 @@ public class AccountManagerService * @param account the account to share with limited users */ private void addAccountToLimitedUsers(Account account) { - List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> users = getUserManager().getUsers(); for (UserInfo user : users) { if (user.isRestricted()) { addSharedAccountAsUser(account, user.id); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 88ef884..7710f13 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -30,6 +30,7 @@ import com.android.server.ProcessMap; import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.firewall.IntentFirewall; import com.android.server.pm.UserManagerService; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; @@ -274,6 +275,8 @@ public final class ActivityManagerService extends ActivityManagerNative public ActivityStack mMainStack; + public IntentFirewall mIntentFirewall; + private final boolean mHeadless; // Whether we should show our dialogs (ANR, crash, etc) or just perform their @@ -570,8 +573,8 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - protected String packageForFilter(BroadcastFilter filter) { - return filter.packageName; + protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) { + return packageName.equals(filter.packageName); } }; @@ -1407,7 +1410,7 @@ public final class ActivityManagerService extends ActivityManagerNative public static void setSystemProcess() { try { ActivityManagerService m = mSelf; - + ServiceManager.addService("activity", m, true); ServiceManager.addService("meminfo", new MemBinder(m)); ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); @@ -1445,6 +1448,11 @@ public final class ActivityManagerService extends ActivityManagerNative mWindowManager = wm; } + public void startObservingNativeCrashes() { + final NativeCrashListener ncl = new NativeCrashListener(); + ncl.start(); + } + public static final Context main(int factoryTest) { AThread thr = new AThread(); thr.start(); @@ -1467,7 +1475,8 @@ public final class ActivityManagerService extends ActivityManagerNative m.mContext = context; m.mFactoryTest = factoryTest; m.mMainStack = new ActivityStack(m, context, true, thr.mLooper); - + m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface()); + m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); m.mAppOpsService.publish(context); @@ -4943,6 +4952,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } + class IntentFirewallInterface implements IntentFirewall.AMSInterface { + public int checkComponentPermission(String permission, int pid, int uid, + int owningUid, boolean exported) { + return ActivityManagerService.this.checkComponentPermission(permission, pid, uid, + owningUid, exported); + } + } + /** * This can be called with or without the global lock held. */ @@ -8333,6 +8350,14 @@ public final class ActivityManagerService extends ActivityManagerNative final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); + handleApplicationCrashInner(r, processName, crashInfo); + } + + /* Native crash reporting uses this inner version because it needs to be somewhat + * decoupled from the AM-managed cleanup lifecycle + */ + void handleApplicationCrashInner(ProcessRecord r, String processName, + ApplicationErrorReport.CrashInfo crashInfo) { EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(), UserHandle.getUserId(Binder.getCallingUid()), processName, r == null ? -1 : r.info.flags, @@ -8846,7 +8871,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } - if (!r.crashing && !r.notResponding) { + if (!r.crashing && !r.notResponding && !r.forceCrashReport) { return null; } @@ -8857,7 +8882,7 @@ public final class ActivityManagerService extends ActivityManagerNative report.time = timeMillis; report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - if (r.crashing) { + if (r.crashing || r.forceCrashReport) { report.type = ApplicationErrorReport.TYPE_CRASH; report.crashInfo = crashInfo; } else if (r.notResponding) { @@ -10867,7 +10892,7 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessesToGc.remove(app); // Dismiss any open dialogs. - if (app.crashDialog != null) { + if (app.crashDialog != null && !app.forceCrashReport) { app.crashDialog.dismiss(); app.crashDialog = null; } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 526b24f..3d7da7b 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -2489,6 +2489,7 @@ final class ActivityStack { int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; + if (caller != null) { callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { @@ -2592,34 +2593,37 @@ final class ActivityStack { throw new SecurityException(msg); } + boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, + callerApp==null?null:callerApp.info, callingPackage, callingUid, callingPid, + resolvedType, aInfo); + if (mMainStack) { if (mService.mController != null) { - boolean abort = false; try { // The Intent we give to the watcher has the extra data // stripped off, since it can contain private information. Intent watchIntent = intent.cloneFilter(); - abort = !mService.mController.activityStarting(watchIntent, + abort |= !mService.mController.activityStarting(watchIntent, aInfo.applicationInfo.packageName); } catch (RemoteException e) { mService.mController = null; } - - if (abort) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - mDismissKeyguardOnNextActivity = false; - ActivityOptions.abort(options); - return ActivityManager.START_SUCCESS; - } } } + if (abort) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + mDismissKeyguardOnNextActivity = false; + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, callingPackage, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java new file mode 100644 index 0000000..e83433f --- /dev/null +++ b/services/java/com/android/server/am/NativeCrashListener.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2013 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.am; + +import android.app.ApplicationErrorReport.CrashInfo; +import android.util.Slog; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.StructTimeval; +import libcore.io.StructUcred; + +import static libcore.io.OsConstants.*; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.net.InetSocketAddress; +import java.net.InetUnixAddress; + +/** + * Set up a Unix domain socket that debuggerd will connect() to in + * order to write a description of a native crash. The crash info is + * then parsed and forwarded to the ActivityManagerService's normal + * crash handling code. + * + * Note that this component runs in a separate thread. + */ +class NativeCrashListener extends Thread { + static final String TAG = "NativeCrashListener"; + static final boolean DEBUG = false; + + // Must match the path defined in debuggerd.c. + static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket"; + + // Use a short timeout on socket operations and abandon the connection + // on hard errors + static final long SOCKET_TIMEOUT_MILLIS = 1000; // 1 second + + final ActivityManagerService mAm; + + /* + * Spin the actual work of handling a debuggerd crash report into a + * separate thread so that the listener can go immediately back to + * accepting incoming connections. + */ + class NativeCrashReporter extends Thread { + ProcessRecord mApp; + int mSignal; + String mCrashReport; + + NativeCrashReporter(ProcessRecord app, int signal, String report) { + super("NativeCrashReport"); + mApp = app; + mSignal = signal; + mCrashReport = report; + } + + @Override + public void run() { + try { + CrashInfo ci = new CrashInfo(); + ci.exceptionClassName = "Native crash"; + ci.exceptionMessage = Libcore.os.strsignal(mSignal); + ci.throwFileName = "unknown"; + ci.throwClassName = "unknown"; + ci.throwMethodName = "unknown"; + ci.stackTrace = mCrashReport; + + if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()"); + mAm.handleApplicationCrashInner(mApp, mApp.processName, ci); + if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned"); + } catch (Exception e) { + Slog.e(TAG, "Unable to report native crash", e); + } + } + } + + /* + * Daemon thread that accept()s incoming domain socket connections from debuggerd + * and processes the crash dump that is passed through. + */ + NativeCrashListener() { + mAm = ActivityManagerService.self(); + } + + @Override + public void run() { + final byte[] ackSignal = new byte[1]; + + if (DEBUG) Slog.i(TAG, "Starting up"); + + // The file system entity for this socket is created with 0700 perms, owned + // by system:system. debuggerd runs as root, so is capable of connecting to + // it, but 3rd party apps cannot. + { + File socketFile = new File(DEBUGGERD_SOCKET_PATH); + if (socketFile.exists()) { + socketFile.delete(); + } + } + + try { + FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0); + final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH); + Libcore.os.bind(serverFd, sockAddr, 0); + Libcore.os.listen(serverFd, 1); + + while (true) { + InetSocketAddress peer = new InetSocketAddress(); + FileDescriptor peerFd = null; + try { + if (DEBUG) Slog.v(TAG, "Waiting for debuggerd connection"); + peerFd = Libcore.os.accept(serverFd, peer); + if (DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd); + if (peerFd != null) { + // Only the superuser is allowed to talk to us over this socket + StructUcred credentials = + Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED); + if (credentials.uid == 0) { + // the reporting thread may take responsibility for + // acking the debugger; make sure we play along. + consumeNativeCrashData(peerFd); + } + } + } catch (Exception e) { + Slog.w(TAG, "Error handling connection", e); + } finally { + // Always ack debuggerd's connection to us. The actual + // byte written is irrelevant. + if (peerFd != null) { + try { + Libcore.os.write(peerFd, ackSignal, 0, 1); + } catch (Exception e) { /* we don't care about failures here */ } + } + } + } + } catch (Exception e) { + Slog.e(TAG, "Unable to init native debug socket!", e); + } + } + + static int unpackInt(byte[] buf, int offset) { + int b0, b1, b2, b3; + + b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension + b1 = ((int) buf[offset+1]) & 0xFF; + b2 = ((int) buf[offset+2]) & 0xFF; + b3 = ((int) buf[offset+3]) & 0xFF; + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes) + throws ErrnoException { + int totalRead = 0; + while (numBytes > 0) { + int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes); + if (n <= 0) { + if (DEBUG) { + Slog.w(TAG, "Needed " + numBytes + " but saw " + n); + } + return -1; // premature EOF or timeout + } + numBytes -= n; + totalRead += n; + } + return totalRead; + } + + // Read the crash report from the debuggerd connection + void consumeNativeCrashData(FileDescriptor fd) { + if (DEBUG) Slog.i(TAG, "debuggerd connected"); + final byte[] buf = new byte[4096]; + final ByteArrayOutputStream os = new ByteArrayOutputStream(4096); + + try { + StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS); + Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout); + Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout); + + // first, the pid and signal number + int headerBytes = readExactly(fd, buf, 0, 8); + if (headerBytes != 8) { + // protocol failure; give up + Slog.e(TAG, "Unable to read from debuggerd"); + return; + } + + int pid = unpackInt(buf, 0); + int signal = unpackInt(buf, 4); + if (DEBUG) { + Slog.v(TAG, "Read pid=" + pid + " signal=" + signal); + } + + // now the text of the dump + if (pid > 0) { + final ProcessRecord pr; + synchronized (mAm.mPidsSelfLocked) { + pr = mAm.mPidsSelfLocked.get(pid); + } + if (pr != null) { + int bytes; + do { + // get some data + bytes = Libcore.os.read(fd, buf, 0, buf.length); + if (bytes > 0) { + if (DEBUG) { + String s = new String(buf, 0, bytes, "UTF-8"); + Slog.v(TAG, "READ=" + bytes + "> " + s); + } + // did we just get the EOD null byte? + if (buf[bytes-1] == 0) { + os.write(buf, 0, bytes-1); // exclude the EOD token + break; + } + // no EOD, so collect it and read more + os.write(buf, 0, bytes); + } + } while (bytes > 0); + + // Okay, we've got the report. + if (DEBUG) Slog.v(TAG, "processing"); + + // Mark the process record as being a native crash so that the + // cleanup mechanism knows we're still submitting the report + // even though the process will vanish as soon as we let + // debuggerd proceed. + synchronized (mAm) { + pr.crashing = true; + pr.forceCrashReport = true; + } + + // Crash reporting is synchronous but we want to let debuggerd + // go about it business right away, so we spin off the actual + // reporting logic on a thread and let it take it's time. + final String reportString = new String(os.toByteArray(), "UTF-8"); + (new NativeCrashReporter(pr, signal, reportString)).start(); + } else { + Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid); + } + } else { + Slog.e(TAG, "Bogus pid!"); + } + } catch (Exception e) { + Slog.e(TAG, "Exception dealing with report", e); + // ugh, fail. + } + } + +} diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index a32af2f..7929f96 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -138,6 +138,7 @@ class ProcessRecord { boolean persistent; // always keep this application running? boolean crashing; // are we in the process of crashing? Dialog crashDialog; // dialog being displayed due to crash. + boolean forceCrashReport; // suppress normal auto-dismiss of crash dialog & report UI? boolean notResponding; // does the app have a not responding dialog? Dialog anrDialog; // dialog being displayed due to app not resp. boolean removed; // has app package been removed from device? diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java new file mode 100644 index 0000000..cabf00b --- /dev/null +++ b/services/java/com/android/server/firewall/AndFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class AndFilter extends FilterList { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + for (int i=0; i<children.size(); i++) { + if (!children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid, + callerPid, resolvedType, resolvedApp)) { + return false; + } + } + return true; + } + + public static final FilterFactory FACTORY = new FilterFactory("and") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return new AndFilter().readFromXml(parser); + } + }; +} diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java new file mode 100644 index 0000000..d5e9fe8 --- /dev/null +++ b/services/java/com/android/server/firewall/CategoryFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Set; + +class CategoryFilter implements Filter { + private static final String ATTR_NAME = "name"; + + private final String mCategoryName; + + private CategoryFilter(String categoryName) { + mCategoryName = categoryName; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage, + int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + Set<String> categories = intent.getCategories(); + if (categories == null) { + return false; + } + return categories.contains(mCategoryName); + } + + public static final FilterFactory FACTORY = new FilterFactory("category") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + String categoryName = parser.getAttributeValue(null, ATTR_NAME); + if (categoryName == null) { + throw new XmlPullParserException("Category name must be specified.", + parser, null); + } + return new CategoryFilter(categoryName); + } + }; +} diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java new file mode 100644 index 0000000..7639466 --- /dev/null +++ b/services/java/com/android/server/firewall/Filter.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; + +interface Filter { + /** + * Does the given intent + context info match this filter? + * + * @param ifw The IntentFirewall instance + * @param intent The intent being started/bound/broadcast + * @param callerApp An ApplicationInfo of an application in the caller's process. This may not + * be the specific app that is actually sending the intent. This also may be + * null, if the caller is the system process, or an unrecognized process (e.g. + * am start) + * @param callerPackage The package name of the component sending the intent. This value is +* provided by the caller and might be forged/faked. + * @param callerUid + * @param callerPid + * @param resolvedType The resolved mime type of the intent + * @param resolvedApp The application that contains the resolved component that the intent is + */ + boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp); +} diff --git a/services/java/com/android/server/firewall/FilterFactory.java b/services/java/com/android/server/firewall/FilterFactory.java new file mode 100644 index 0000000..dea8b40 --- /dev/null +++ b/services/java/com/android/server/firewall/FilterFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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.firewall; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +public abstract class FilterFactory { + private final String mTag; + + protected FilterFactory(String tag) { + if (tag == null) { + throw new NullPointerException(); + } + mTag = tag; + } + + public String getTagName() { + return mTag; + } + + public abstract Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException; +} diff --git a/services/java/com/android/server/firewall/FilterList.java b/services/java/com/android/server/firewall/FilterList.java new file mode 100644 index 0000000..d34b203 --- /dev/null +++ b/services/java/com/android/server/firewall/FilterList.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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.firewall; + +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +abstract class FilterList implements Filter { + protected final ArrayList<Filter> children = new ArrayList<Filter>(); + + public FilterList readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + readChild(parser); + } + return this; + } + + protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { + Filter filter = IntentFirewall.parseFilter(parser); + children.add(filter); + } +} diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java new file mode 100644 index 0000000..062183b --- /dev/null +++ b/services/java/com/android/server/firewall/IntentFirewall.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.os.ServiceManager; +import android.util.Slog; +import android.util.Xml; +import com.android.internal.util.XmlUtils; +import com.android.server.IntentResolver; +import com.android.server.pm.PackageManagerService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class IntentFirewall { + private static final String TAG = "IntentFirewall"; + + // e.g. /data/system/ifw/ifw.xml or /data/secure/system/ifw/ifw.xml + private static final File RULES_FILE = + new File(Environment.getSystemSecureDirectory(), "ifw/ifw.xml"); + + private static final String TAG_RULES = "rules"; + private static final String TAG_ACTIVITY = "activity"; + private static final String TAG_SERVICE = "service"; + private static final String TAG_BROADCAST = "broadcast"; + + private static final HashMap<String, FilterFactory> factoryMap; + + private final AMSInterface mAms; + + private final IntentResolver<FirewallIntentFilter, Rule> mActivityResolver = + new FirewallIntentResolver(); + private final IntentResolver<FirewallIntentFilter, Rule> mServiceResolver = + new FirewallIntentResolver(); + private final IntentResolver<FirewallIntentFilter, Rule> mBroadcastResolver = + new FirewallIntentResolver(); + + static { + FilterFactory[] factories = new FilterFactory[] { + AndFilter.FACTORY, + OrFilter.FACTORY, + NotFilter.FACTORY, + + StringFilter.ACTION, + StringFilter.COMPONENT, + StringFilter.COMPONENT_NAME, + StringFilter.COMPONENT_PACKAGE, + StringFilter.DATA, + StringFilter.HOST, + StringFilter.MIME_TYPE, + StringFilter.PATH, + StringFilter.SENDER_PACKAGE, + StringFilter.SSP, + + CategoryFilter.FACTORY, + SenderFilter.FACTORY, + SenderPermissionFilter.FACTORY, + PortFilter.FACTORY + }; + + // load factor ~= .75 + factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3); + for (int i=0; i<factories.length; i++) { + FilterFactory factory = factories[i]; + factoryMap.put(factory.getTagName(), factory); + } + } + + public IntentFirewall(AMSInterface ams) { + mAms = ams; + readRules(getRulesFile()); + } + + public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ActivityInfo resolvedActivity) { + List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0); + boolean log = false; + boolean block = false; + + for (int i=0; i< matchingRules.size(); i++) { + Rule rule = matchingRules.get(i); + if (rule.matches(this, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedActivity.applicationInfo)) { + block |= rule.getBlock(); + log |= rule.getLog(); + + // if we've already determined that we should both block and log, there's no need + // to continue trying rules + if (block && log) { + break; + } + } + } + + if (log) { + // TODO: log info about intent to event log + } + + return !block; + } + + public static File getRulesFile() { + return RULES_FILE; + } + + private void readRules(File rulesFile) { + FileInputStream fis; + try { + fis = new FileInputStream(rulesFile); + } catch (FileNotFoundException ex) { + // Nope, no rules. Nothing else to do! + return; + } + + try { + XmlPullParser parser = Xml.newPullParser(); + + parser.setInput(fis, null); + + XmlUtils.beginDocument(parser, TAG_RULES); + + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + IntentResolver<FirewallIntentFilter, Rule> resolver = null; + String tagName = parser.getName(); + if (tagName.equals(TAG_ACTIVITY)) { + resolver = mActivityResolver; + } else if (tagName.equals(TAG_SERVICE)) { + resolver = mServiceResolver; + } else if (tagName.equals(TAG_BROADCAST)) { + resolver = mBroadcastResolver; + } + + if (resolver != null) { + Rule rule = new Rule(); + + try { + rule.readFromXml(parser); + } catch (XmlPullParserException ex) { + Slog.e(TAG, "Error reading intent firewall rule", ex); + continue; + } catch (IOException ex) { + Slog.e(TAG, "Error reading intent firewall rule", ex); + continue; + } + + for (int i=0; i<rule.getIntentFilterCount(); i++) { + resolver.addFilter(rule.getIntentFilter(i)); + } + } + } + } catch (XmlPullParserException ex) { + Slog.e(TAG, "Error reading intent firewall rules", ex); + } catch (IOException ex) { + Slog.e(TAG, "Error reading intent firewall rules", ex); + } finally { + try { + fis.close(); + } catch (IOException ex) { + Slog.e(TAG, "Error while closing " + rulesFile, ex); + } + } + } + + static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException { + String elementName = parser.getName(); + + FilterFactory factory = factoryMap.get(elementName); + + if (factory == null) { + throw new XmlPullParserException("Unknown element in filter list: " + elementName); + } + return factory.newFilter(parser); + } + + private static class Rule extends AndFilter { + private static final String TAG_INTENT_FILTER = "intent-filter"; + + private static final String ATTR_BLOCK = "block"; + private static final String ATTR_LOG = "log"; + + private final ArrayList<FirewallIntentFilter> mIntentFilters = + new ArrayList<FirewallIntentFilter>(1); + private boolean block; + private boolean log; + + @Override + public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { + block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK)); + log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG)); + + super.readFromXml(parser); + return this; + } + + @Override + protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { + if (parser.getName().equals(TAG_INTENT_FILTER)) { + FirewallIntentFilter intentFilter = new FirewallIntentFilter(this); + intentFilter.readFromXml(parser); + mIntentFilters.add(intentFilter); + } else { + super.readChild(parser); + } + } + + public int getIntentFilterCount() { + return mIntentFilters.size(); + } + + public FirewallIntentFilter getIntentFilter(int index) { + return mIntentFilters.get(index); + } + + public boolean getBlock() { + return block; + } + + public boolean getLog() { + return log; + } + } + + private static class FirewallIntentFilter extends IntentFilter { + private final Rule rule; + + public FirewallIntentFilter(Rule rule) { + this.rule = rule; + } + } + + private static class FirewallIntentResolver + extends IntentResolver<FirewallIntentFilter, Rule> { + @Override + protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) { + return !dest.contains(filter.rule); + } + + @Override + protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) { + return true; + } + + @Override + protected FirewallIntentFilter[] newArray(int size) { + return new FirewallIntentFilter[size]; + } + + @Override + protected Rule newResult(FirewallIntentFilter filter, int match, int userId) { + return filter.rule; + } + + @Override + protected void sortResults(List<Rule> results) { + // there's no need to sort the results + return; + } + } + + /** + * This interface contains the methods we need from ActivityManagerService. This allows AMS to + * export these methods to us without making them public, and also makes it easier to test this + * component. + */ + public interface AMSInterface { + int checkComponentPermission(String permission, int pid, int uid, + int owningUid, boolean exported); + } + + /** + * Checks if the caller has access to a component + * + * @param permission If present, the caller must have this permission + * @param pid The pid of the caller + * @param uid The uid of the caller + * @param owningUid The uid of the application that owns the component + * @param exported Whether the component is exported + * @return True if the caller can access the described component + */ + boolean checkComponentPermission(String permission, int pid, int uid, int owningUid, + boolean exported) { + return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) == + PackageManager.PERMISSION_GRANTED; + } + + boolean signaturesMatch(int uid1, int uid2) { + PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); + return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH; + } +} diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java new file mode 100644 index 0000000..2ff108a --- /dev/null +++ b/services/java/com/android/server/firewall/NotFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class NotFilter implements Filter { + private final Filter mChild; + + private NotFilter(Filter child) { + mChild = child; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + return !mChild.matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedApp); + } + + public static final FilterFactory FACTORY = new FilterFactory("not") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + Filter child = null; + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + Filter filter = IntentFirewall.parseFilter(parser); + if (child == null) { + child = filter; + } else { + throw new XmlPullParserException( + "<not> tag can only contain a single child filter.", parser, null); + } + } + return new NotFilter(child); + } + }; +} diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java new file mode 100644 index 0000000..1ed1c85 --- /dev/null +++ b/services/java/com/android/server/firewall/OrFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class OrFilter extends FilterList { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + for (int i=0; i<children.size(); i++) { + if (children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedApp)) { + return true; + } + } + return false; + } + + public static final FilterFactory FACTORY = new FilterFactory("or") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return new OrFilter().readFromXml(parser); + } + }; +} diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java new file mode 100644 index 0000000..2b2a198 --- /dev/null +++ b/services/java/com/android/server/firewall/PortFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class PortFilter implements Filter { + private static final String ATTR_EQUALS = "equals"; + private static final String ATTR_MIN = "min"; + private static final String ATTR_MAX = "max"; + + private static final int NO_BOUND = -1; + + // both bounds are inclusive + private final int mLowerBound; + private final int mUpperBound; + + private PortFilter(int lowerBound, int upperBound) { + mLowerBound = lowerBound; + mUpperBound = upperBound; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + int port = -1; + Uri uri = intent.getData(); + if (uri != null) { + port = uri.getPort(); + } + return port != -1 && + (mLowerBound == NO_BOUND || mLowerBound <= port) && + (mUpperBound == NO_BOUND || mUpperBound >= port); + } + + public static final FilterFactory FACTORY = new FilterFactory("port") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + int lowerBound = NO_BOUND; + int upperBound = NO_BOUND; + + String equalsValue = parser.getAttributeValue(null, ATTR_EQUALS); + if (equalsValue != null) { + int value; + try { + value = Integer.parseInt(equalsValue); + } catch (NumberFormatException ex) { + throw new XmlPullParserException("Invalid port value: " + equalsValue, + parser, null); + } + lowerBound = value; + upperBound = value; + } + + String lowerBoundString = parser.getAttributeValue(null, ATTR_MIN); + String upperBoundString = parser.getAttributeValue(null, ATTR_MAX); + if (lowerBoundString != null || upperBoundString != null) { + if (equalsValue != null) { + throw new XmlPullParserException( + "Port filter cannot use both equals and range filtering", + parser, null); + } + + if (lowerBoundString != null) { + try { + lowerBound = Integer.parseInt(lowerBoundString); + } catch (NumberFormatException ex) { + throw new XmlPullParserException( + "Invalid minimum port value: " + lowerBoundString, + parser, null); + } + } + + if (upperBoundString != null) { + try { + upperBound = Integer.parseInt(upperBoundString); + } catch (NumberFormatException ex) { + throw new XmlPullParserException( + "Invalid maximum port value: " + upperBoundString, + parser, null); + } + } + } + + // an empty port filter is explicitly allowed, and checks for the existence of a port + return new PortFilter(lowerBound, upperBound); + } + }; +} diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java new file mode 100644 index 0000000..0b790bd --- /dev/null +++ b/services/java/com/android/server/firewall/SenderFilter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.os.Process; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class SenderFilter { + private static final String ATTR_TYPE = "type"; + + private static final String VAL_SIGNATURE = "signature"; + private static final String VAL_SYSTEM = "system"; + private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature"; + private static final String VAL_USER_ID = "userId"; + + static boolean isSystemApp(ApplicationInfo callerApp, int callerUid, int callerPid) { + if (callerUid == Process.SYSTEM_UID || + callerPid == Process.myPid()) { + return true; + } + if (callerApp == null) { + return false; + } + return (callerApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + public static final FilterFactory FACTORY = new FilterFactory("sender") { + @Override + public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException { + String typeString = parser.getAttributeValue(null, ATTR_TYPE); + if (typeString == null) { + throw new XmlPullParserException("type attribute must be specified for <sender>", + parser, null); + } + if (typeString.equals(VAL_SYSTEM)) { + return SYSTEM; + } else if (typeString.equals(VAL_SIGNATURE)) { + return SIGNATURE; + } else if (typeString.equals(VAL_SYSTEM_OR_SIGNATURE)) { + return SYSTEM_OR_SIGNATURE; + } else if (typeString.equals(VAL_USER_ID)) { + return USER_ID; + } + throw new XmlPullParserException( + "Invalid type attribute for <sender>: " + typeString, parser, null); + } + }; + + private static final Filter SIGNATURE = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + if (callerApp == null) { + return false; + } + return ifw.signaturesMatch(callerUid, resolvedApp.uid); + } + }; + + private static final Filter SYSTEM = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + if (callerApp == null) { + // if callerApp is null, the caller is the system process + return false; + } + return isSystemApp(callerApp, callerUid, callerPid); + } + }; + + private static final Filter SYSTEM_OR_SIGNATURE = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + return isSystemApp(callerApp, callerUid, callerPid) || + ifw.signaturesMatch(callerUid, resolvedApp.uid); + } + }; + + private static final Filter USER_ID = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + // This checks whether the caller is either the system process, or has the same user id + // I.e. the same app, or an app that uses the same shared user id. + // This is the same set of applications that would be able to access the component if + // it wasn't exported. + return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false); + } + }; +} diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java new file mode 100644 index 0000000..02d8b15 --- /dev/null +++ b/services/java/com/android/server/firewall/SenderPermissionFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class SenderPermissionFilter implements Filter { + private static final String ATTR_NAME = "name"; + + private final String mPermission; + + private SenderPermissionFilter(String permission) { + mPermission = permission; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + // We assume the component is exported here. If the component is not exported, then + // ActivityManager would only resolve to this component for callers from the same uid. + // In this case, it doesn't matter whether the component is exported or not. + return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid, + true); + } + + public static final FilterFactory FACTORY = new FilterFactory("sender-permission") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + String permission = parser.getAttributeValue(null, ATTR_NAME); + if (permission == null) { + throw new XmlPullParserException("Permission name must be specified.", + parser, null); + } + return new SenderPermissionFilter(permission); + } + }; +} diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java new file mode 100644 index 0000000..de5a69f --- /dev/null +++ b/services/java/com/android/server/firewall/StringFilter.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.PatternMatcher; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.regex.Pattern; + +abstract class StringFilter implements Filter { + private static final String ATTR_EQUALS = "equals"; + private static final String ATTR_STARTS_WITH = "startsWith"; + private static final String ATTR_CONTAINS = "contains"; + private static final String ATTR_PATTERN = "pattern"; + private static final String ATTR_REGEX = "regex"; + private static final String ATTR_IS_NULL = "isNull"; + + private final ValueProvider mValueProvider; + + private StringFilter(ValueProvider valueProvider) { + this.mValueProvider = valueProvider; + } + + /** + * Constructs a new StringFilter based on the string filter attribute on the current + * element, and the given StringValueMatcher. + * + * The current node should contain exactly 1 string filter attribute. E.g. equals, + * contains, etc. Otherwise, an XmlPullParserException will be thrown. + * + * @param parser An XmlPullParser object positioned at an element that should + * contain a string filter attribute + * @return This StringFilter object + */ + public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser) + throws IOException, XmlPullParserException { + StringFilter filter = null; + + for (int i=0; i<parser.getAttributeCount(); i++) { + StringFilter newFilter = getFilter(valueProvider, parser, i); + if (newFilter != null) { + if (filter != null) { + throw new XmlPullParserException("Multiple string filter attributes found"); + } + filter = newFilter; + } + } + + if (filter == null) { + // if there are no string filter attributes, we default to isNull="false" so that an + // empty filter is equivalent to an existence check + filter = new IsNullFilter(valueProvider, false); + } + + return filter; + } + + private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser, + int attributeIndex) { + String attributeName = parser.getAttributeName(attributeIndex); + + switch (attributeName.charAt(0)) { + case 'e': + if (!attributeName.equals(ATTR_EQUALS)) { + return null; + } + return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 'i': + if (!attributeName.equals(ATTR_IS_NULL)) { + return null; + } + return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 's': + if (!attributeName.equals(ATTR_STARTS_WITH)) { + return null; + } + return new StartsWithFilter(valueProvider, + parser.getAttributeValue(attributeIndex)); + case 'c': + if (!attributeName.equals(ATTR_CONTAINS)) { + return null; + } + return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 'p': + if (!attributeName.equals(ATTR_PATTERN)) { + return null; + } + return new PatternStringFilter(valueProvider, + parser.getAttributeValue(attributeIndex)); + case 'r': + if (!attributeName.equals(ATTR_REGEX)) { + return null; + } + return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + } + return null; + } + + protected abstract boolean matchesValue(String value); + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage, + int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + String value = mValueProvider.getValue(intent, callerApp, callerPackage, resolvedType, + resolvedApp); + return matchesValue(value); + } + + private static abstract class ValueProvider extends FilterFactory { + protected ValueProvider(String tag) { + super(tag); + } + + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return StringFilter.readFromXml(this, parser); + } + + public abstract String getValue(Intent intent, ApplicationInfo callerApp, + String callerPackage, String resolvedType, ApplicationInfo resolvedApp); + } + + private static class EqualsFilter extends StringFilter { + private final String mFilterValue; + + public EqualsFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.equals(mFilterValue); + } + } + + private static class ContainsFilter extends StringFilter { + private final String mFilterValue; + + public ContainsFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.contains(mFilterValue); + } + } + + private static class StartsWithFilter extends StringFilter { + private final String mFilterValue; + + public StartsWithFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.startsWith(mFilterValue); + } + } + + private static class PatternStringFilter extends StringFilter { + private final PatternMatcher mPattern; + + public PatternStringFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB); + } + + @Override + public boolean matchesValue(String value) { + return value != null && mPattern.match(value); + } + } + + private static class RegexFilter extends StringFilter { + private final Pattern mPattern; + + public RegexFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + this.mPattern = Pattern.compile(attrValue); + } + + @Override + public boolean matchesValue(String value) { + return value != null && mPattern.matcher(value).matches(); + } + } + + private static class IsNullFilter extends StringFilter { + private final boolean mIsNull; + + public IsNullFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mIsNull = Boolean.parseBoolean(attrValue); + } + + public IsNullFilter(ValueProvider valueProvider, boolean isNull) { + super(valueProvider); + mIsNull = isNull; + } + + @Override + public boolean matchesValue(String value) { + return (value == null) == mIsNull; + } + } + + public static final ValueProvider COMPONENT = new ValueProvider("component") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.flattenToString(); + } + return null; + } + }; + + public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.getClassName(); + } + return null; + } + }; + + public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.getPackageName(); + } + return null; + } + }; + + public static final ValueProvider SENDER_PACKAGE = new ValueProvider("sender-package") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + // TODO: We can't trust this value, so maybe should check all packages in the caller process? + return callerPackage; + } + }; + + + public static final FilterFactory ACTION = new ValueProvider("action") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + return intent.getAction(); + } + }; + + public static final ValueProvider DATA = new ValueProvider("data") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.toString(); + } + return null; + } + }; + + public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + return resolvedType; + } + }; + + public static final ValueProvider SCHEME = new ValueProvider("scheme") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getScheme(); + } + return null; + } + }; + + public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getSchemeSpecificPart(); + } + return null; + } + }; + + public static final ValueProvider HOST = new ValueProvider("host") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getHost(); + } + return null; + } + }; + + public static final ValueProvider PATH = new ValueProvider("path") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getPath(); + } + return null; + } + }; +} diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index dc5916b..ca7bba2 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -110,8 +110,10 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.UserManager; import android.os.Environment.UserEnvironment; +import android.os.UserManager; +import android.provider.Settings.Secure; +import android.security.KeyStore; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -1323,6 +1325,12 @@ public class PackageManagerService extends IPackageManager.Stub { ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) : 0)); + // If this is the first boot, and it is a normal boot, then + // we need to initialize the default preferred apps. + if (!mRestoredSettings && !onlyCore) { + mSettings.readDefaultPreferredAppsLPw(this, 0); + } + // can downgrade to reader mSettings.writeLPr(); @@ -5114,141 +5122,90 @@ public class PackageManagerService extends IPackageManager.Stub { Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } } - if (bp != null && bp.packageSetting != null) { - final String perm = bp.name; - boolean allowed; - boolean allowedSig = false; - final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; - if (level == PermissionInfo.PROTECTION_NORMAL - || level == PermissionInfo.PROTECTION_DANGEROUS) { - // If the permission is required, or it's optional and was previously - // granted to the application, then allow it. Otherwise deny. - allowed = (required || origPermissions.contains(perm)); - } else if (bp.packageSetting == null) { - // This permission is invalid; skip it. - allowed = false; - } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { - allowed = (compareSignatures( - bp.packageSetting.signatures.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH) - || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH); - if (!allowed && (bp.protectionLevel - & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) { - if (isSystemApp(pkg)) { - // For updated system applications, a system permission - // is granted only if it had been defined by the original application. - if (isUpdatedSystemApp(pkg)) { - final PackageSetting sysPs = mSettings - .getDisabledSystemPkgLPr(pkg.packageName); - final GrantedPermissions origGp = sysPs.sharedUser != null - ? sysPs.sharedUser : sysPs; - if (origGp.grantedPermissions.contains(perm)) { - allowed = true; - } else { - // The system apk may have been updated with an older - // version of the one on the data partition, but which - // granted a new system permission that it didn't have - // before. In this case we do want to allow the app to - // now get the new permission, because it is allowed by - // the system image. - allowed = false; - if (sysPs.pkg != null) { - for (int j=0; - j<sysPs.pkg.requestedPermissions.size(); j++) { - if (perm.equals( - sysPs.pkg.requestedPermissions.get(j))) { - allowed = true; - break; - } - } - } - } - } else { - allowed = true; - } - } - } - if (!allowed && (bp.protectionLevel - & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { - // For development permissions, a development permission - // is granted only if it was already granted. - allowed = origPermissions.contains(perm); - } - if (allowed) { - allowedSig = true; - } - } else { - allowed = false; + + if (bp == null || bp.packageSetting == null) { + Slog.w(TAG, "Unknown permission " + name + + " in package " + pkg.packageName); + continue; + } + + final String perm = bp.name; + boolean allowed; + boolean allowedSig = false; + final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + if (level == PermissionInfo.PROTECTION_NORMAL + || level == PermissionInfo.PROTECTION_DANGEROUS) { + // We grant a normal or dangerous permission if any of the following + // are true: + // 1) The permission is required + // 2) The permission is optional, but was granted in the past + // 3) The permission is optional, but was requested by an + // app in /system (not /data) + // + // Otherwise, reject the permission. + allowed = (required || origPermissions.contains(perm) + || (isSystemApp(ps) && !isUpdatedSystemApp(ps))); + } else if (bp.packageSetting == null) { + // This permission is invalid; skip it. + allowed = false; + } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { + allowed = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowed) { + allowedSig = true; } - if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); + } else { + allowed = false; + } + if (DEBUG_INSTALL) { + if (gp != ps) { + Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); + } + } + if (allowed) { + if (!isSystemApp(ps) && ps.permissionsFixed) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. + if (!allowedSig && !gp.grantedPermissions.contains(perm)) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + allowed = isNewPlatformPermissionForPackage(perm, pkg); } } if (allowed) { - if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0 - && ps.permissionsFixed) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. - if (!allowedSig && !gp.grantedPermissions.contains(perm)) { - allowed = false; - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - final int NP = PackageParser.NEW_PERMISSIONS.length; - for (int ip=0; ip<NP; ip++) { - final PackageParser.NewPermissionInfo npi - = PackageParser.NEW_PERMISSIONS[ip]; - if (npi.name.equals(perm) - && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { - allowed = true; - Log.i(TAG, "Auto-granting " + perm + " to old pkg " - + pkg.packageName); - break; - } - } - } - } - if (allowed) { - if (!gp.grantedPermissions.contains(perm)) { - changedPermission = true; - gp.grantedPermissions.add(perm); - gp.gids = appendInts(gp.gids, bp.gids); - } else if (!ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); - } - } else { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } - } else { - if (gp.grantedPermissions.remove(perm)) { + if (!gp.grantedPermissions.contains(perm)) { changedPermission = true; - gp.gids = removeInts(gp.gids, bp.gids); - Slog.i(TAG, "Un-granting permission " + perm - + " from package " + pkg.packageName - + " (protectionLevel=" + bp.protectionLevel - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } else { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " (protectionLevel=" + bp.protectionLevel - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); + gp.grantedPermissions.add(perm); + gp.gids = appendInts(gp.gids, bp.gids); + } else if (!ps.haveGids) { + gp.gids = appendInts(gp.gids, bp.gids); } + } else { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); } } else { - Slog.w(TAG, "Unknown permission " + name - + " in package " + pkg.packageName); + if (gp.grantedPermissions.remove(perm)) { + changedPermission = true; + gp.gids = removeInts(gp.gids, bp.gids); + Slog.i(TAG, "Un-granting permission " + perm + + " from package " + pkg.packageName + + " (protectionLevel=" + bp.protectionLevel + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } else { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " (protectionLevel=" + bp.protectionLevel + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } } } if ((changedPermission || replace) && !ps.permissionsFixed && - ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) || - ((ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)){ + !isSystemApp(ps) || isUpdatedSystemApp(ps)){ // This is the first that we have heard about this package, so the // permissions we have now selected are fixed until explicitly // changed. @@ -5256,7 +5213,77 @@ public class PackageManagerService extends IPackageManager.Stub { } ps.haveGids = true; } - + + private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { + boolean allowed = false; + final int NP = PackageParser.NEW_PERMISSIONS.length; + for (int ip=0; ip<NP; ip++) { + final PackageParser.NewPermissionInfo npi + = PackageParser.NEW_PERMISSIONS[ip]; + if (npi.name.equals(perm) + && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { + allowed = true; + Log.i(TAG, "Auto-granting " + perm + " to old pkg " + + pkg.packageName); + break; + } + } + return allowed; + } + + private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, + BasePermission bp, HashSet<String> origPermissions) { + boolean allowed; + allowed = (compareSignatures( + bp.packageSetting.signatures.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH) + || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH); + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) { + if (isSystemApp(pkg)) { + // For updated system applications, a system permission + // is granted only if it had been defined by the original application. + if (isUpdatedSystemApp(pkg)) { + final PackageSetting sysPs = mSettings + .getDisabledSystemPkgLPr(pkg.packageName); + final GrantedPermissions origGp = sysPs.sharedUser != null + ? sysPs.sharedUser : sysPs; + if (origGp.grantedPermissions.contains(perm)) { + allowed = true; + } else { + // The system apk may have been updated with an older + // version of the one on the data partition, but which + // granted a new system permission that it didn't have + // before. In this case we do want to allow the app to + // now get the new permission, because it is allowed by + // the system image. + allowed = false; + if (sysPs.pkg != null) { + for (int j=0; + j<sysPs.pkg.requestedPermissions.size(); j++) { + if (perm.equals( + sysPs.pkg.requestedPermissions.get(j))) { + allowed = true; + break; + } + } + } + } + } else { + allowed = true; + } + } + } + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { + // For development permissions, a development permission + // is granted only if it was already granted. + allowed = origPermissions.contains(perm); + } + return allowed; + } + final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, @@ -5383,8 +5410,9 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected String packageForFilter(PackageParser.ActivityIntentInfo info) { - return info.activity.owner.packageName; + protected boolean isPackageForFilter(String packageName, + PackageParser.ActivityIntentInfo info) { + return packageName.equals(info.activity.owner.packageName); } @Override @@ -5580,8 +5608,9 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected String packageForFilter(PackageParser.ServiceIntentInfo info) { - return info.service.owner.packageName; + protected boolean isPackageForFilter(String packageName, + PackageParser.ServiceIntentInfo info) { + return packageName.equals(info.service.owner.packageName); } @Override @@ -8359,6 +8388,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } + private static boolean isUpdatedSystemApp(PackageSetting ps) { + return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + private static boolean isUpdatedSystemApp(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } @@ -8614,6 +8647,17 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.writeLPr(); } } + // A user ID was deleted here. Go through all users and remove it from + // KeyStore. + final int appId = outInfo.removedAppId; + if (appId != -1) { + final KeyStore keyStore = KeyStore.getInstance(); + if (keyStore != null) { + for (final int userId : sUserManager.getUserIds()) { + keyStore.clearUid(UserHandle.getUid(userId, appId)); + } + } + } } /* diff --git a/services/java/com/android/server/pm/PreferredIntentResolver.java b/services/java/com/android/server/pm/PreferredIntentResolver.java index 3f1e50c..7fe6a05 100644 --- a/services/java/com/android/server/pm/PreferredIntentResolver.java +++ b/services/java/com/android/server/pm/PreferredIntentResolver.java @@ -27,8 +27,8 @@ public class PreferredIntentResolver return new PreferredActivity[size]; } @Override - protected String packageForFilter(PreferredActivity filter) { - return filter.mPref.mComponent.getPackageName(); + protected boolean isPackageForFilter(String packageName, PreferredActivity filter) { + return packageName.equals(filter.mPref.mComponent.getPackageName()); } @Override protected void dumpFilter(PrintWriter out, String prefix, diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 70183df..2e48074 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -1603,9 +1603,6 @@ final class Settings { mReadMessages.append("No settings file found\n"); PackageManagerService.reportSettingsProblem(Log.INFO, "No settings file; creating initial state"); - if (!onlyCore) { - readDefaultPreferredAppsLPw(service, 0); - } mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion; return false; } diff --git a/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java new file mode 100644 index 0000000..9185903 --- /dev/null +++ b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 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.updates; + +import com.android.server.firewall.IntentFirewall; + +public class IntentFirewallInstallReceiver extends ConfigUpdateInstallReceiver { + + public IntentFirewallInstallReceiver() { + super(IntentFirewall.getRulesFile().getParent(), IntentFirewall.getRulesFile().getName(), + "metadata/", "version"); + } +} diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 3c40238..af603fd 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -5802,6 +5802,19 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void removeRotationWatcher(IRotationWatcher watcher) { + final IBinder watcherBinder = watcher.asBinder(); + synchronized (mWindowMap) { + for (int i=0; i<mRotationWatchers.size(); i++) { + if (watcherBinder == mRotationWatchers.get(i).asBinder()) { + mRotationWatchers.remove(i); + i--; + } + } + } + } + /** * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact * theme attribute) on devices that feature a physical options menu key attempt to position diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 8c47332..4aee902 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -907,6 +907,24 @@ public class TelephonyManager { } /** + * Returns the Group Identifier Level1 for a GSM phone. + * Return null if it is unavailable. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + */ + public String getGroupIdLevel1() { + try { + return getSubscriberInfo().getGroupIdLevel1(); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** * Returns the phone number string for line 1, for example, the MSISDN * for a GSM phone. Return null if it is unavailable. * <p> diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index da0326c..03940dc 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -39,6 +39,11 @@ interface IPhoneSubInfo { String getSubscriberId(); /** + * Retrieves the Group Identifier Level1 for GSM phones. + */ + String getGroupIdLevel1(); + + /** * Retrieves the serial number of the ICC, if applicable. */ String getIccSerialNumber(); diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 84f5a5c..cadac02 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -592,6 +592,10 @@ int doDump(Bundle* bundle) goto bail; } printf("uses-permission: %s\n", name.string()); + int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); + if (!req) { + printf("optional-permission: %s\n", name.string()); + } } } } else if (strcmp("badging", option) == 0) { @@ -1033,6 +1037,10 @@ int doDump(Bundle* bundle) hasWriteCallLogPermission = true; } printf("uses-permission:'%s'\n", name.string()); + int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); + if (!req) { + printf("optional-permission:'%s'\n", name.string()); + } } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 6fbe32f..434b131 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -449,6 +449,10 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException { + } + + @Override public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) { return false; } |