diff --git a/api/current.txt b/api/current.txt
index d2db84ac24c9..17a41b26353d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1268,6 +1268,8 @@ package android {
field public static final int tertiary_text_dark = 17170448; // 0x1060010
field public static final int tertiary_text_light = 17170449; // 0x1060011
field public static final int transparent = 17170445; // 0x106000d
+ field public static final int trds_background = 17170460; // 0x106001c
+ field public static final int trds_text = 17170461; // 0x106001d
field public static final int white = 17170443; // 0x106000b
field public static final int widget_edittext_dark = 17170442; // 0x106000a
}
@@ -1494,10 +1496,12 @@ package android {
field public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
field public static final int message = 16908299; // 0x102000b
+ field public static final int monitor_box = 16908334; // 0x102002e
field public static final int paste = 16908322; // 0x1020022
field public static final int primary = 16908300; // 0x102000c
field public static final int progress = 16908301; // 0x102000d
field public static final int secondaryProgress = 16908303; // 0x102000f
+ field public static final int seek_bar = 16908335; // 0x102002f
field public static final int selectAll = 16908319; // 0x102001f
field public static final int selectTextMode = 16908333; // 0x102002d
field public static final int selectedIcon = 16908302; // 0x102000e
@@ -1513,8 +1517,6 @@ package android {
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int widget_frame = 16908312; // 0x1020018
- field public static final int monitor_box = 16908334; // 0x0102002e
- field public static final int seek_bar = 16908335; // 0x0102002f
}
public static final class R.integer {
@@ -1566,9 +1568,9 @@ package android {
field public static final int simple_selectable_list_item = 17367061; // 0x1090015
field public static final int simple_spinner_dropdown_item = 17367049; // 0x1090009
field public static final int simple_spinner_item = 17367048; // 0x1090008
+ field public static final int slider_preference = 17367064; // 0x1090018
field public static final int test_list_item = 17367052; // 0x109000c
field public static final int two_line_list_item = 17367053; // 0x109000d
- field public static final int slider_preference = 17367064; // 0x01090018
}
public static final class R.menu {
@@ -1600,6 +1602,7 @@ package android {
field public static final int cut = 17039363; // 0x1040003
field public static final int defaultMsisdnAlphaTag = 17039365; // 0x1040005
field public static final int defaultVoiceMailAlphaTag = 17039364; // 0x1040004
+ field public static final int default_string = 17039384; // 0x1040018
field public static final int dialog_alert_title = 17039380; // 0x1040014
field public static final int emptyPhoneNumber = 17039366; // 0x1040006
field public static final int httpErrorBadUrl = 17039367; // 0x1040007
@@ -1614,7 +1617,6 @@ package android {
field public static final int unknownName = 17039374; // 0x104000e
field public static final int untitled = 17039375; // 0x104000f
field public static final int yes = 17039379; // 0x1040013
- field public static final int default_string = 17039384; // 0x01040018
}
public static final class R.style {
@@ -10542,7 +10544,6 @@ package android.hardware {
method public int getJpegQuality();
method public int getJpegThumbnailQuality();
method public android.hardware.Camera.Size getJpegThumbnailSize();
- method public java.lang.String getPowerMode();
method public int getMaxExposureCompensation();
method public int getMaxNumDetectedFaces();
method public int getMaxNumFocusAreas();
@@ -10552,6 +10553,7 @@ package android.hardware {
method public int getMinExposureCompensation();
method public int getPictureFormat();
method public android.hardware.Camera.Size getPictureSize();
+ method public java.lang.String getPowerMode();
method public android.hardware.Camera.Size getPreferredPreviewSizeForVideo();
method public int getPreviewFormat();
method public void getPreviewFpsRange(int[]);
@@ -11999,6 +12001,7 @@ package android.media {
field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
field public static final java.lang.String TAG_APERTURE = "FNumber";
field public static final java.lang.String TAG_DATETIME = "DateTime";
+ field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime";
field public static final java.lang.String TAG_FLASH = "Flash";
field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength";
@@ -12017,6 +12020,9 @@ package android.media {
field public static final java.lang.String TAG_MAKE = "Make";
field public static final java.lang.String TAG_MODEL = "Model";
field public static final java.lang.String TAG_ORIENTATION = "Orientation";
+ field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime";
+ field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+ field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance";
field public static final int WHITEBALANCE_AUTO = 0; // 0x0
field public static final int WHITEBALANCE_MANUAL = 1; // 0x1
@@ -17448,6 +17454,7 @@ package android.os {
field public static final java.lang.String RELEASE;
field public static final deprecated java.lang.String SDK;
field public static final int SDK_INT;
+ field public static final java.lang.String SECURITY_PATCH;
}
public static class Build.VERSION_CODES {
@@ -17591,7 +17598,7 @@ package android.os {
public abstract class CountDownTimer {
ctor public CountDownTimer(long, long);
- method public final void cancel();
+ method public final synchronized void cancel();
method public abstract void onFinish();
method public abstract void onTick(long);
method public final synchronized android.os.CountDownTimer start();
@@ -18772,6 +18779,23 @@ package android.preference {
method public void setShowSilent(boolean);
}
+ public class SlimSeekBarPreference extends android.preference.Preference implements android.widget.SeekBar.OnSeekBarChangeListener {
+ ctor public SlimSeekBarPreference(android.content.Context, android.util.AttributeSet);
+ method public void disablePercentageValue(boolean);
+ method public void disableText(boolean);
+ method public void isMilliseconds(boolean);
+ method public void minimumValue(int);
+ method public void multiplyValue(int);
+ method public void onProgressChanged(android.widget.SeekBar, int, boolean);
+ method public void onStartTrackingTouch(android.widget.SeekBar);
+ method public void onStopTrackingTouch(android.widget.SeekBar);
+ method public void setDefault(int);
+ method public void setInitValue(int);
+ method public void setInterval(int);
+ field public int interval;
+ field public static int maximum;
+ }
+
public class SwitchPreference extends android.preference.TwoStatePreference {
ctor public SwitchPreference(android.content.Context, android.util.AttributeSet, int);
ctor public SwitchPreference(android.content.Context, android.util.AttributeSet);
@@ -21251,12 +21275,14 @@ package android.provider {
field public static final android.net.Uri DEFAULT_NOTIFICATION_URI;
field public static final android.net.Uri DEFAULT_RINGTONE_URI;
field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned";
+ field public static final java.lang.String DIALKEY_PADDING = "dialkey_padding";
field public static final deprecated java.lang.String DIM_SCREEN = "dim_screen";
field public static final java.lang.String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
field public static final java.lang.String END_BUTTON_BEHAVIOR = "end_button_behavior";
field public static final java.lang.String FONT_SCALE = "font_scale";
field public static final java.lang.String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
+ field public static final java.lang.String INCALL_GLOWPAD_TRANSPARENCY = "incall_glowpad_transparency";
field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
field public static final deprecated java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
field public static final deprecated java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
@@ -21801,7 +21827,7 @@ package android.renderscript {
}
public class BaseObj {
- method public synchronized void destroy();
+ method public void destroy();
method public java.lang.String getName();
method public void setName(java.lang.String);
}
@@ -32737,6 +32763,7 @@ package android.widget {
method public boolean isFlipping();
method public void setAutoStart(boolean);
method public void setFlipInterval(int);
+ method public void setSelfMaintained(boolean);
method public void startFlipping();
method public void stopFlipping();
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 85226a7204db..1bf736b31dc4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1444,9 +1444,10 @@ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
case START_BACKUP_AGENT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ String packageName = data.readString();
int backupRestoreMode = data.readInt();
- boolean success = bindBackupAgent(info, backupRestoreMode);
+ int userId = data.readInt();
+ boolean success = bindBackupAgent(packageName, backupRestoreMode, userId);
reply.writeNoException();
reply.writeInt(success ? 1 : 0);
return true;
@@ -3168,13 +3169,14 @@ public IBinder peekService(Intent service, String resolvedType) throws RemoteExc
return binder;
}
- public boolean bindBackupAgent(ApplicationInfo app, int backupRestoreMode)
+ public boolean bindBackupAgent(String packageName, int backupRestoreMode, int userId)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
- app.writeToParcel(data, 0);
+ data.writeString(packageName);
data.writeInt(backupRestoreMode);
+ data.writeInt(userId);
mRemote.transact(START_BACKUP_AGENT_TRANSACTION, data, reply, 0);
reply.readException();
boolean success = reply.readInt() != 0;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5b3c53a8aada..8981c67badb4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -220,7 +220,7 @@ public class AppOpsManager {
OP_WRITE_CALL_LOG,
OP_READ_CALENDAR,
OP_WRITE_CALENDAR,
- OP_COARSE_LOCATION,
+ OP_WIFI_SCAN,
OP_POST_NOTIFICATION,
OP_COARSE_LOCATION,
OP_CALL_PHONE,
@@ -370,8 +370,8 @@ public class AppOpsManager {
android.Manifest.permission.WRITE_CALL_LOG,
android.Manifest.permission.READ_CALENDAR,
android.Manifest.permission.WRITE_CALENDAR,
+ null, // no permission for wifi scan available
null, // no permission required for notifications
- android.Manifest.permission.ACCESS_WIFI_STATE,
null, // neighboring cells shares the coarse location perm
android.Manifest.permission.CALL_PHONE,
android.Manifest.permission.READ_SMS,
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 73baeb5ddcb0..75843b10cc9b 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -164,7 +164,7 @@ public void serviceDoneExecuting(IBinder token, int type, int startId,
int res) throws RemoteException;
public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
- public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
+ public boolean bindBackupAgent(String packageName, int backupRestoreMode, int userId)
throws RemoteException;
public void clearPendingBackup() throws RemoteException;
public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 6f929f261ae6..7b709acd911c 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -162,7 +162,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothA2dp.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6b4cd3ed5387..83edb34b6e4c 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -648,7 +648,11 @@ public String getName() {
return null;
}
try {
- return sService.getRemoteName(this);
+ String name = sService.getRemoteName(this);
+ if (name != null) {
+ return name.replaceAll("[\\t\\n\\r]+", " ");
+ }
+ return null;
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -1023,7 +1027,7 @@ public boolean setPasskey(int passkey) {
/**
* Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing.
- *
Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ *
Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
*
* @return true confirmation has been sent out
* false for error
diff --git a/core/java/android/bluetooth/BluetoothDun.java b/core/java/android/bluetooth/BluetoothDun.java
index aa906d3781f9..79444c94d53b 100644
--- a/core/java/android/bluetooth/BluetoothDun.java
+++ b/core/java/android/bluetooth/BluetoothDun.java
@@ -105,7 +105,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothDun.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Dun Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHandsfreeClient.java b/core/java/android/bluetooth/BluetoothHandsfreeClient.java
index dd4212a6825f..3cc06237c869 100644
--- a/core/java/android/bluetooth/BluetoothHandsfreeClient.java
+++ b/core/java/android/bluetooth/BluetoothHandsfreeClient.java
@@ -390,7 +390,8 @@ public void onBluetoothStateChange(boolean up) {
try {
if (mService == null) {
if (VDBG) Log.d(TAG,"Binding service...");
- if (!mContext.bindService(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0)) {
+ if (!mContext.bindServiceAsUser(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Handsfree Client Service");
}
}
@@ -419,7 +420,8 @@ public void onBluetoothStateChange(boolean up) {
}
}
- if (!context.bindService(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0)) {
+ if (!context.bindServiceAsUser(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Handsfree Client Service");
}
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 2b17d6f4c0c3..179380018523 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -280,7 +280,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothHeadset.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 2e950faeb4e1..cb54b46d7e43 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -488,7 +488,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothHealth.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 4995dda2f2ac..ce4a6a5b4b49 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -222,7 +222,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothHidDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index db88f0d74c03..022bd02a5027 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -20,12 +20,15 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.EventLog;
import java.util.Random;
/** @hide */
public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
+ private static final int MAX_DESCRIPTOR_SIZE = 2048;
+
final public String name;
final public String description;
final public String provider;
@@ -38,6 +41,12 @@ public BluetoothHidDeviceAppSdpSettings(String name, String description, String
this.description = description;
this.provider = provider;
this.subclass = subclass;
+
+ if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) {
+ EventLog.writeEvent(0x534e4554, "119819889", -1, "");
+ throw new IllegalArgumentException("descriptors must be not null and shorter than "
+ + MAX_DESCRIPTOR_SIZE);
+ }
this.descriptors = descriptors.clone();
}
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index ee14b871cde7..0c7553fe4fb0 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -253,7 +253,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothInputDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 92a2f1e4253c..a12865a77afb 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -106,7 +106,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothMap.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index cbb43483a082..6a9bf7fcbd2b 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -145,7 +145,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothPan.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 7f456528a9fb..1bd760f2a053 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -161,7 +161,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothPbap.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 5e36fb245680..dfe58911d6bf 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -105,7 +105,8 @@ boolean doBind() {
Intent intent = new Intent(IBluetoothSap.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Sap Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 9f7817bc91f6..15775a2a74ba 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -196,6 +196,7 @@ private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
as.close();
throw new IOException("bt socket acept failed");
}
+ as.mPfd = new ParcelFileDescriptor(fds[0]);
as.mSocket = new LocalSocket(fds[0]);
try {
as.mSocket.closeExternalFd();
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index b586eec76748..e27870f96786 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.accounts.Account;
+import java.util.Objects;
+
/**
* Value type that contains information about a periodic sync.
*/
@@ -147,7 +149,9 @@ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
if (!b2.containsKey(key)) {
return false;
}
- if (!b1.get(key).equals(b2.get(key))) {
+ // Null check. According to ContentResolver#validateSyncExtrasBundle null-valued keys
+ // are allowed in the bundle.
+ if (!Objects.equals(b1.get(key), b2.get(key))) {
return false;
}
}
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index cffc653e44ef..51292c4ad1e1 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -25,6 +25,13 @@
* Information about the sync operation that is currently underway.
*/
public class SyncInfo implements Parcelable {
+ /**
+ * Used when the caller receiving this object doesn't have permission to access the accounts
+ * on device.
+ * @See Manifest.permission.GET_ACCOUNTS
+ */
+ private static final Account REDACTED_ACCOUNT = new Account("*****", "*****");
+
/** @hide */
public final int authorityId;
@@ -45,6 +52,17 @@ public class SyncInfo implements Parcelable {
*/
public final long startTime;
+ /**
+ * Creates a SyncInfo object with an unusable Account. Used when the caller receiving this
+ * object doesn't have access to the accounts on the device.
+ * @See Manifest.permission.GET_ACCOUNTS
+ * @hide
+ */
+ public static SyncInfo createAccountRedacted(
+ int authorityId, String authority, long startTime) {
+ return new SyncInfo(authorityId, REDACTED_ACCOUNT, authority, startTime);
+ }
+
/** @hide */
public SyncInfo(int authorityId, Account account, String authority,
long startTime) {
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 91884abaf992..6637c8186cf0 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -376,6 +376,11 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn,
return null;
}
+ final String sql;
+ final String unwrappedSql = buildQuery(
+ projectionIn, selection, groupBy, having,
+ sortOrder, limit);
+
if (mStrict && selection != null && selection.length() > 0) {
// Validate the user-supplied selection to detect syntactic anomalies
// in the selection string that could indicate a SQL injection attempt.
@@ -384,16 +389,24 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn,
// originally specified. An attacker cannot create an expression that
// would escape the SQL expression while maintaining balanced parentheses
// in both the wrapped and original forms.
- String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
+
+ // NOTE: The ordering of the below operations is important; we must
+ // execute the wrapped query to ensure the untrusted clause has been
+ // fully isolated.
+
+ // Validate the unwrapped query
+ validateQuerySql(db, unwrappedSql,
+ cancellationSignal); // will throw if query is invalid
+
+ // Execute wrapped query for extra protection
+ final String wrappedSql = buildQuery(projectionIn, "(" + selection + ")", groupBy,
having, sortOrder, limit);
- validateQuerySql(db, sqlForValidation,
- cancellationSignal); // will throw if query is invalid
+ sql = wrappedSql;
+ } else {
+ // Execute unwrapped query
+ sql = unwrappedSql;
}
- String sql = buildQuery(
- projectionIn, selection, groupBy, having,
- sortOrder, limit);
-
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Performing query: " + sql);
}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index ded7a94c5395..951ad4411e75 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -413,6 +413,9 @@ public String getTcpBufferSizesPropName() {
case TelephonyManager.NETWORK_TYPE_HSPAP:
networkTypeStr = "hspap";
break;
+ case TelephonyManager.NETWORK_TYPE_DCHSPAP:
+ networkTypeStr = "dchspap";
+ break;
case TelephonyManager.NETWORK_TYPE_CDMA:
networkTypeStr = "cdma";
break;
@@ -432,6 +435,7 @@ public String getTcpBufferSizesPropName() {
networkTypeStr = "iden";
break;
case TelephonyManager.NETWORK_TYPE_LTE:
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
networkTypeStr = "lte";
break;
case TelephonyManager.NETWORK_TYPE_EHRPD:
@@ -863,6 +867,7 @@ static class NetworkDataEntry {
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSUPA, 14400, 5760, UNKNOWN),
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPA, 14400, 5760, UNKNOWN),
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPAP, 21000, 5760, UNKNOWN),
+ new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_DCHSPAP, 42000, 5760, UNKNOWN),
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_CDMA, UNKNOWN, UNKNOWN, UNKNOWN),
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_1xRTT, UNKNOWN, UNKNOWN, UNKNOWN),
new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_0, 2468, 153, UNKNOWN),
@@ -896,6 +901,7 @@ private static int getNormalizedSignalStrength(int networkType, SignalStrength s
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSPAP:
+ case TelephonyManager.NETWORK_TYPE_DCHSPAP:
level = ss.getGsmLevel();
break;
case TelephonyManager.NETWORK_TYPE_CDMA:
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 7d75afe725c4..3b53ddd19697 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -258,6 +258,7 @@ public static void snapToCycleDay(Time time, int cycleDay, int cycleLength) {
if (cycleLength == CYCLE_MONTHLY) {
snapToCycleDay(time, cycleDay);
} else if (cycleLength == CYCLE_WEEKLY) {
+ cycleDay = cycleDay % 7;
time.monthDay += (cycleDay - time.weekDay);
time.normalize(true);
}
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 8a2c2b6c675f..ce7b337bb9af 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -31,6 +31,7 @@
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
/**
@@ -65,7 +66,15 @@ public List select(URI uri) {
String response = null;
String urlString;
try {
+ // Strip path and username/password from URI so it's not visible to PAC script. The
+ // path often contains credentials the app does not want exposed to a potentially
+ // malicious PAC script.
+ if (!"http".equalsIgnoreCase(uri.getScheme())) {
+ uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "/", null, null);
+ }
urlString = uri.toURL().toString();
+ } catch (URISyntaxException e) {
+ urlString = uri.getHost();
} catch (MalformedURLException e) {
urlString = uri.getHost();
}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index a7a8a0a8f32b..0246bb6d72af 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -711,6 +711,10 @@ static String parseAuthority(String uriString, int ssi) {
LOOP: while (end < length) {
switch (uriString.charAt(end)) {
case '/': // Start of path
+ case '\\':// Start of path
+ // Per http://url.spec.whatwg.org/#host-state, the \ character
+ // is treated as if it were a / character when encountered in a
+ // host
case '?': // Start of query
case '#': // Start of fragment
break LOOP;
@@ -749,6 +753,10 @@ static String parsePath(String uriString, int ssi) {
case '#': // Start of fragment
return ""; // Empty path.
case '/': // Start of path!
+ case '\\':// Start of path!
+ // Per http://url.spec.whatwg.org/#host-state, the \ character
+ // is treated as if it were a / character when encountered in a
+ // host
break LOOP;
}
pathStart++;
@@ -1057,7 +1065,7 @@ private String parseUserInfo() {
return null;
}
- int end = authority.indexOf('@');
+ int end = authority.lastIndexOf('@');
return end == NOT_FOUND ? null : authority.substring(0, end);
}
@@ -1081,7 +1089,7 @@ private String parseHost() {
}
// Parse out user info and then port.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
String encodedHost = portSeparator == NOT_FOUND
@@ -1107,7 +1115,7 @@ private int parsePort() {
// Make sure we look for the port separtor *after* the user info
// separator. We have URLs with a ':' in the user info.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
if (portSeparator == NOT_FOUND) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9ada6e640089..59dd3b29920e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -855,12 +855,13 @@ public abstract long getPhoneSignalScanningTime(
public static final int DATA_CONNECTION_LTE = 13;
public static final int DATA_CONNECTION_EHRPD = 14;
public static final int DATA_CONNECTION_HSPAP = 15;
- public static final int DATA_CONNECTION_OTHER = 16;
+ public static final int DATA_CONNECTION_DCHSPAP = 16;
+ public static final int DATA_CONNECTION_OTHER = 17;
static final String[] DATA_CONNECTION_NAMES = {
"none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
"1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte",
- "ehrpd", "hspap", "other"
+ "ehrpd", "hspa+","dc-hspa+", "other"
};
public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 25e6aa56db3f..cd8e5ab6c700 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -588,11 +588,19 @@ public final void writeMap(Map val) {
return;
}
Set> entries = val.entrySet();
- writeInt(entries.size());
+ int size = entries.size();
+ writeInt(size);
+
for (Map.Entry e : entries) {
writeValue(e.getKey());
writeValue(e.getValue());
+ size--;
+ }
+
+ if (size != 0) {
+ throw new BadParcelableException("Map size does not match number of entries!");
}
+
}
/**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index cf9ddb3b258b..45800a7e4cd6 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -495,6 +495,15 @@ private static ProcessStartResult zygoteSendArgsAndGetResult(ArrayList a
openZygoteSocketIfNeeded();
try {
+ // Throw early if any of the arguments are malformed. This means we can
+ // avoid writing a partial response to the zygote.
+ int sz = args.size();
+ for (int i = 0; i < sz; i++) {
+ if (args.get(i).indexOf('\n') >= 0) {
+ throw new ZygoteStartFailedEx("embedded newlines not allowed");
+ }
+ }
+
/**
* See com.android.internal.os.ZygoteInit.readArgumentList()
* Presently the wire format to the zygote process is:
@@ -509,13 +518,8 @@ private static ProcessStartResult zygoteSendArgsAndGetResult(ArrayList a
sZygoteWriter.write(Integer.toString(args.size()));
sZygoteWriter.newLine();
- int sz = args.size();
for (int i = 0; i < sz; i++) {
String arg = args.get(i);
- if (arg.indexOf('\n') >= 0) {
- throw new ZygoteStartFailedEx(
- "embedded newlines not allowed");
- }
sZygoteWriter.write(arg);
sZygoteWriter.newLine();
}
@@ -524,11 +528,15 @@ private static ProcessStartResult zygoteSendArgsAndGetResult(ArrayList a
// Should there be a timeout on this?
ProcessStartResult result = new ProcessStartResult();
+ // Always read the entire result from the input stream to avoid leaving
+ // bytes in the stream for future process starts to accidentally stumble
+ // upon.
result.pid = sZygoteInputStream.readInt();
+ result.usingWrapper = sZygoteInputStream.readBoolean();
+
if (result.pid < 0) {
throw new ZygoteStartFailedEx("fork() failed");
}
- result.usingWrapper = sZygoteInputStream.readBoolean();
return result;
} catch (IOException ex) {
try {
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index deb138d43563..c1e2e6a21ec1 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -23,6 +23,7 @@
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
+import android.util.Log;
import android.util.Patterns;
import android.webkit.WebView;
import android.widget.TextView;
@@ -58,6 +59,9 @@
*/
public class Linkify {
+
+ private static final String LOG_TAG = "Linkify";
+
/**
* Bit field indicating that web URLs should be matched in methods that
* take an options mask
@@ -202,6 +206,11 @@ public interface TransformFilter {
* repeatedly on the same text.
*/
public static final boolean addLinks(Spannable text, int mask) {
+ if (text != null && containsUnsupportedCharacters(text.toString())) {
+ android.util.EventLog.writeEvent(0x534e4554, "116321860", -1, "");
+ return false;
+ }
+
if (mask == 0) {
return false;
}
@@ -247,6 +256,29 @@ public static final boolean addLinks(Spannable text, int mask) {
return true;
}
+ /**
+ * Returns true if the specified text contains at least one unsupported character for applying
+ * links. Also logs the error.
+ *
+ * @param text the text to apply links to
+ * @hide
+ */
+ public static boolean containsUnsupportedCharacters(String text) {
+ if (text.contains("\u202C")) {
+ Log.e(LOG_TAG, "Unsupported character for applying links: u202C");
+ return true;
+ }
+ if (text.contains("\u202D")) {
+ Log.e(LOG_TAG, "Unsupported character for applying links: u202D");
+ return true;
+ }
+ if (text.contains("\u202E")) {
+ Log.e(LOG_TAG, "Unsupported character for applying links: u202E");
+ return true;
+ }
+ return false;
+ }
+
/**
* Scans the text of the provided TextView and turns all occurrences of
* the link types indicated in the mask into clickable links. If matches
@@ -344,6 +376,10 @@ public static final void addLinks(TextView text, Pattern p, String scheme,
* a scheme specified in the link text
*/
public static final boolean addLinks(Spannable text, Pattern pattern, String scheme) {
+ if (text != null && containsUnsupportedCharacters(text.toString())) {
+ android.util.EventLog.writeEvent(0x534e4554, "116321860", -1, "");
+ return false;
+ }
return addLinks(text, pattern, scheme, null, null);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d74ee0f19a3c..5bb111599dcc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -547,6 +547,25 @@ public static class LayoutParams extends ViewGroup.LayoutParams
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
+ /**
+ * Return true if the window type is an alert window.
+ *
+ * @param type The window type.
+ * @return If the window type is an alert window.
+ * @hide
+ */
+ public static boolean isSystemAlertWindowType(int type) {
+ switch (type) {
+ case TYPE_PHONE:
+ case TYPE_PRIORITY_PHONE:
+ case TYPE_SYSTEM_ALERT:
+ case TYPE_SYSTEM_ERROR:
+ case TYPE_SYSTEM_OVERLAY:
+ return true;
+ }
+ return false;
+ }
+
/** @deprecated this is ignored, this value is set automatically when needed. */
@Deprecated
public static final int MEMORY_TYPE_NORMAL = 0;
@@ -1090,6 +1109,15 @@ public static class LayoutParams extends ViewGroup.LayoutParams
* {@hide} */
public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
+ /**
+ * Flag to indicate that any window added by an application process that is of type
+ * {@link #TYPE_TOAST} or that requires
+ * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
+ * this window is visible.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+
/**
* Control flags that are private to the platform.
* @hide
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0a702ff8ddec..843d9654cf75 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -2177,6 +2177,9 @@ public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData) {
case TelephonyManager.NETWORK_TYPE_HSPAP:
bin = DATA_CONNECTION_HSPAP;
break;
+ case TelephonyManager.NETWORK_TYPE_DCHSPAP:
+ bin = DATA_CONNECTION_DCHSPAP;
+ break;
default:
bin = DATA_CONNECTION_OTHER;
break;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index aed5d2e85665..5e90ae6bf52a 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -746,9 +746,18 @@ public boolean usingBiometricWeak() {
* @return The pattern.
*/
public List stringToPattern(String string) {
- List result = Lists.newArrayList();
-
final byte size = getLockPatternSize();
+ return stringToPattern(string, size);
+ }
+
+ /**
+ * Deserialize a pattern.
+ * @param string The pattern serialized with {@link #patternToString}
+ * @param byte The pattern size
+ * @return The pattern.
+ */
+ public List stringToPattern(String string, byte size) {
+ List result = Lists.newArrayList();
LockPatternView.Cell.updateSize(size);
final byte[] bytes = string.getBytes();
@@ -765,6 +774,16 @@ public List stringToPattern(String string) {
* @return The pattern in string form.
*/
public String patternToString(List pattern) {
+ return patternToString(pattern, getLockPatternSize());
+ }
+
+ /**
+ * Serialize a pattern.
+ * @param pattern The pattern.
+ * @param size The pattern size.
+ * @return The pattern in string form.
+ */
+ public String patternToString(List pattern, byte size) {
if (pattern == null) {
return "";
}
@@ -773,7 +792,7 @@ public String patternToString(List pattern) {
byte[] res = new byte[patternSize];
for (int i = 0; i < patternSize; i++) {
LockPatternView.Cell cell = pattern.get(i);
- res[i] = (byte) (cell.getRow() * getLockPatternSize() + cell.getColumn());
+ res[i] = (byte) (cell.getRow() * size + cell.getColumn());
}
return new String(res);
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 531563187a37..adcfbb85590d 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1084,7 +1084,8 @@ private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPatter
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
return new SavedState(superState,
- mLockPatternUtils.patternToString(mPattern),
+ mLockPatternUtils == null ? "" : mLockPatternUtils.patternToString(mPattern,
+ mPatternSize),
mPatternDisplayMode.ordinal(), mPatternSize,
mInputEnabled, mInStealthMode, mEnableHapticFeedback);
}
@@ -1093,11 +1094,13 @@ protected Parcelable onSaveInstanceState() {
protected void onRestoreInstanceState(Parcelable state) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
- setPattern(
- DisplayMode.Correct,
- mLockPatternUtils.stringToPattern(ss.getSerializedPattern()));
- mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
mPatternSize = ss.getPatternSize();
+ if (mLockPatternUtils != null) {
+ setPattern(
+ DisplayMode.Correct,
+ mLockPatternUtils.stringToPattern(ss.getSerializedPattern(), mPatternSize));
+ }
+ mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
mInputEnabled = ss.isInputEnabled();
mInStealthMode = ss.isInStealthMode();
mEnableHapticFeedback = ss.isTactileFeedbackEnabled();
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index ca278cf00d4f..db818d1b2843 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -415,7 +415,7 @@ static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr) {
return false;
}
- int ret = selinux_android_restorecon(pathname.c_str());
+ int ret = selinux_android_restorecon(pathname.c_str(), 0);
ALOGV("restorecon(%s) => %d", pathname.c_str(), ret);
return (ret == 0);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c2cd208f044..62a5f9a9dbb0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1128,6 +1128,11 @@
android:description="@string/permdesc_bind_call_service"
android:label="@string/permlab_bind_call_service" />
+
+
+
@@ -1851,6 +1856,13 @@
android:description="@string/permdesc_internalSystemWindow"
android:protectionLevel="signature" />
+
+
+
diff --git a/core/res/res/values-mcc214-mnc32/config.xml b/core/res/res/values-mcc214-mnc32/config.xml
new file mode 100644
index 000000000000..874e3a59a04e
--- /dev/null
+++ b/core/res/res/values-mcc214-mnc32/config.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ - 21405
+ - 21407
+
+
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3309bb100c30..bceda4ce00c0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1195,8 +1195,10 @@
10
- 1500
+ may have a specific value set in an overlay config.xml file.
+ This really must be 1358 as per 3GPP standards and packet segmentation on the LTE
+ radio.-->
+ 1358
false
250
+
+
+ false
+
+
+ false
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 22de76a43001..48f552371fe3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -289,6 +289,7 @@
+
@@ -1763,4 +1764,5 @@
+
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 6fb8946c29e0..a40e9610fcd1 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -185,6 +185,17 @@ public void testAuthorityParsing() {
uri = Uri.parse("http://localhost");
assertEquals("localhost", uri.getHost());
assertEquals(-1, uri.getPort());
+
+ uri = Uri.parse("http://a:a@example.com:a@example2.com/path");
+ assertEquals("a:a@example.com:a@example2.com", uri.getAuthority());
+ assertEquals("example2.com", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("/path", uri.getPath());
+
+ uri = Uri.parse("http://a.foo.com\\.example.com/path");
+ assertEquals("a.foo.com", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("\\.example.com/path", uri.getPath());
}
@SmallTest
diff --git a/include/androidfw/CursorWindow.h b/include/androidfw/CursorWindow.h
index 8a2979a3756d..a1f842d5559d 100644
--- a/include/androidfw/CursorWindow.h
+++ b/include/androidfw/CursorWindow.h
@@ -18,6 +18,7 @@
#define _ANDROID__DATABASE_WINDOW_H
#include
+#include
#include
#include
@@ -128,12 +129,13 @@ class CursorWindow {
inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
size_t* outSizeIncludingNull) {
*outSizeIncludingNull = fieldSlot->data.buffer.size;
- return static_cast(offsetToPtr(fieldSlot->data.buffer.offset));
+ return static_cast(offsetToPtr(
+ fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size));
}
inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
*outSize = fieldSlot->data.buffer.size;
- return offsetToPtr(fieldSlot->data.buffer.offset);
+ return offsetToPtr(fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size);
}
private:
@@ -166,7 +168,16 @@ class CursorWindow {
bool mReadOnly;
Header* mHeader;
- inline void* offsetToPtr(uint32_t offset) {
+ inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
+ if (offset >= mSize) {
+ ALOGE("Offset %lu out of bounds, max value %zu", offset, mSize);
+ return NULL;
+ }
+ if (offset + bufferSize > mSize) {
+ ALOGE("End offset %lu out of bounds, max value %zu",
+ offset + bufferSize, mSize);
+ return NULL;
+ }
return static_cast(mData) + offset;
}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 0f54edb6f915..a54410d7d0e3 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -98,9 +98,14 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor
if (dupAshmemFd < 0) {
result = -errno;
} else {
+ // the size of the ashmem descriptor can be modified between ashmem_get_size_region
+ // call and mmap, so we'll check again immediately after memory is mapped
void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
+ } else if (ashmem_get_size_region(dupAshmemFd) != size) {
+ ::munmap(data, size);
+ result = BAD_VALUE;
} else {
CursorWindow* window = new CursorWindow(name, dupAshmemFd,
data, size, true /*readOnly*/);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2f59dafacdbf..34ee3bc38708 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -330,6 +330,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
uninit();
+ // The chunk must be at least the size of the string pool header.
+ if (size < sizeof(ResStringPool_header)) {
+ ALOGW("Bad string block: data size %zu is too small to be a string block", size);
+ return (mError=BAD_TYPE);
+ }
+
+ // The data is at least as big as a ResChunk_header, so we can safely validate the other
+ // header fields.
+ // `data + size` is safe because the source of `size` comes from the kernel/filesystem.
+ if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header),
+ reinterpret_cast(data) + size,
+ "ResStringPool_header") != NO_ERROR) {
+ ALOGW("Bad string block: malformed block dimensions");
+ return (mError=BAD_TYPE);
+ }
+
const bool notDeviceEndian = htods(0xf0) != 0xf0;
if (copyData || notDeviceEndian) {
@@ -341,6 +357,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
data = mOwnedData;
}
+ // The size has been checked, so it is safe to read the data in the ResStringPool_header
+ // data structure.
mHeader = (const ResStringPool_header*)data;
if (notDeviceEndian) {
@@ -660,7 +678,13 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
*outLen = decodeLength(&str);
size_t encLen = decodeLength(&str);
if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
- return (const char*)str;
+ // Reject malformed (non null-terminated) strings
+ if (str[encLen] != 0x00) {
+ ALOGW("Bad string block: string #%d is not null-terminated",
+ (int)idx);
+ return NULL;
+ }
+ return (const char*)str;
} else {
ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
(int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index 6c7ca71353f5..2f2debc89dd1 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -140,7 +140,11 @@ void PatchCache::clearGarbage() {
Mutex::Autolock _l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
- remove(patchesToRemove, mGarbage[i]);
+ Res_png_9patch* patch = mGarbage[i];
+ remove(patchesToRemove, patch);
+ // A Res_png_9patch is actually an array of byte that's larger
+ // than sizeof(Res_png_9patch). It must be freed as an array.
+ delete[] (int8_t*) patch;
}
mGarbage.clear();
}
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 5df64080b102..cf8adf8772f3 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -395,7 +395,9 @@ void PathCache::clearGarbage() {
Mutex::Autolock l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
- remove(pathsToRemove, mGarbage.itemAt(i));
+ const path_pair_t& pair = mGarbage.itemAt(i);
+ remove(pathsToRemove, pair);
+ delete pair.getFirst();
}
mGarbage.clear();
}
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 3f77021da02a..d276a295a7dc 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -213,8 +213,9 @@ void ResourceCache::destructorLocked(SkPath* resource) {
// If we're not tracking this resource, just delete it
if (Caches::hasInstance()) {
Caches::getInstance().pathCache.removeDeferred(resource);
+ } else {
+ delete resource;
}
- delete resource;
return;
}
ref->destroyed = true;
@@ -235,8 +236,9 @@ void ResourceCache::destructorLocked(SkBitmap* resource) {
// If we're not tracking this resource, just delete it
if (Caches::hasInstance()) {
Caches::getInstance().textureCache.removeDeferred(resource);
+ } else {
+ delete resource;
}
- delete resource;
return;
}
ref->destroyed = true;
@@ -292,13 +294,14 @@ void ResourceCache::destructorLocked(Res_png_9patch* resource) {
ssize_t index = mCache->indexOfKey(resource);
ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
if (ref == NULL) {
+ // If we're not tracking this resource, just delete it
if (Caches::hasInstance()) {
Caches::getInstance().patchCache.removeDeferred(resource);
+ } else {
+ // A Res_png_9patch is actually an array of byte that's larger
+ // than sizeof(Res_png_9patch). It must be freed as an array.
+ delete[] (int8_t*) resource;
}
- // If we're not tracking this resource, just delete it
- // A Res_png_9patch is actually an array of byte that's larger
- // than sizeof(Res_png_9patch). It must be freed as an array.
- delete[] (int8_t*) resource;
return;
}
ref->destroyed = true;
@@ -355,16 +358,18 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere
SkBitmap* bitmap = (SkBitmap*) resource;
if (Caches::hasInstance()) {
Caches::getInstance().textureCache.removeDeferred(bitmap);
+ } else {
+ delete bitmap;
}
- delete bitmap;
}
break;
case kPath: {
SkPath* path = (SkPath*) resource;
if (Caches::hasInstance()) {
Caches::getInstance().pathCache.removeDeferred(path);
+ } else {
+ delete path;
}
- delete path;
}
break;
case kShader: {
@@ -380,11 +385,12 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere
case kNinePatch: {
if (Caches::hasInstance()) {
Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource);
+ } else {
+ // A Res_png_9patch is actually an array of byte that's larger
+ // than sizeof(Res_png_9patch). It must be freed as an array.
+ int8_t* patch = (int8_t*) resource;
+ delete[] patch;
}
- // A Res_png_9patch is actually an array of byte that's larger
- // than sizeof(Res_png_9patch). It must be freed as an array.
- int8_t* patch = (int8_t*) resource;
- delete[] patch;
}
break;
case kLayer: {
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 8d0874f3e03b..d5ba8c3008d5 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -184,7 +184,9 @@ void TextureCache::clearGarbage() {
Mutex::Autolock _l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
- mCache.remove(mGarbage.itemAt(i));
+ SkBitmap* bitmap = mGarbage.itemAt(i);
+ mCache.remove(bitmap);
+ delete bitmap;
}
mGarbage.clear();
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 9db35fce7db7..994a4f7a1a94 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -16,70 +16,307 @@
package android.media;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.util.Pair;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* This is a class for reading and writing Exif tags in a JPEG file.
*/
public class ExifInterface {
+ private static final String TAG = "ExifInterface";
+ private static final boolean DEBUG = false;
+
// The Exif tag names
- /** Type is int. */
- public static final String TAG_ORIENTATION = "Orientation";
+ /** Type is String. @hide */
+ public static final String TAG_ARTIST = "Artist";
+ /** Type is int. @hide */
+ public static final String TAG_BITS_PER_SAMPLE = "BitsPerSample";
+ /** Type is int. @hide */
+ public static final String TAG_COMPRESSION = "Compression";
+ /** Type is String. @hide */
+ public static final String TAG_COPYRIGHT = "Copyright";
/** Type is String. */
public static final String TAG_DATETIME = "DateTime";
+ /** Type is String. @hide */
+ public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription";
+ /** Type is int. */
+ public static final String TAG_IMAGE_LENGTH = "ImageLength";
+ /** Type is int. */
+ public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+ /** Type is int. @hide */
+ public static final String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
+ /** Type is int. @hide */
+ public static final String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
/** Type is String. */
public static final String TAG_MAKE = "Make";
/** Type is String. */
public static final String TAG_MODEL = "Model";
/** Type is int. */
- public static final String TAG_FLASH = "Flash";
+ public static final String TAG_ORIENTATION = "Orientation";
+ /** Type is int. @hide */
+ public static final String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+ /** Type is int. @hide */
+ public static final String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
+ /** Type is rational. @hide */
+ public static final String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
+ /** Type is rational. @hide */
+ public static final String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
+ /** Type is int. @hide */
+ public static final String TAG_RESOLUTION_UNIT = "ResolutionUnit";
+ /** Type is int. @hide */
+ public static final String TAG_ROWS_PER_STRIP = "RowsPerStrip";
+ /** Type is int. @hide */
+ public static final String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
+ /** Type is String. @hide */
+ public static final String TAG_SOFTWARE = "Software";
+ /** Type is int. @hide */
+ public static final String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
+ /** Type is int. @hide */
+ public static final String TAG_STRIP_OFFSETS = "StripOffsets";
+ /** Type is int. @hide */
+ public static final String TAG_TRANSFER_FUNCTION = "TransferFunction";
+ /** Type is rational. @hide */
+ public static final String TAG_WHITE_POINT = "WhitePoint";
+ /** Type is rational. @hide */
+ public static final String TAG_X_RESOLUTION = "XResolution";
+ /** Type is rational. @hide */
+ public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
+ /** Type is int. @hide */
+ public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
+ /** Type is int. @hide */
+ public static final String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
+ /** Type is rational. @hide */
+ public static final String TAG_Y_RESOLUTION = "YResolution";
+ /** Type is rational. @hide */
+ public static final String TAG_APERTURE_VALUE = "ApertureValue";
+ /** Type is rational. @hide */
+ public static final String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
+ /** Type is String. @hide */
+ public static final String TAG_CFA_PATTERN = "CFAPattern";
+ /** Type is int. @hide */
+ public static final String TAG_COLOR_SPACE = "ColorSpace";
+ /** Type is String. @hide */
+ public static final String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
+ /** Type is rational. @hide */
+ public static final String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
+ /** Type is int. @hide */
+ public static final String TAG_CONTRAST = "Contrast";
+ /** Type is int. @hide */
+ public static final String TAG_CUSTOM_RENDERED = "CustomRendered";
+ /** Type is String. */
+ public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ /** Type is String. @hide */
+ public static final String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
+ /** Type is String. @hide */
+ public static final String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
+ /** Type is double. @hide */
+ public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
+ /** Type is String. @hide */
+ public static final String TAG_EXIF_VERSION = "ExifVersion";
+ /** Type is double. @hide */
+ public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
+ /** Type is rational. @hide */
+ public static final String TAG_EXPOSURE_INDEX = "ExposureIndex";
+ /** Type is int. @hide */
+ public static final String TAG_EXPOSURE_MODE = "ExposureMode";
+ /** Type is int. @hide */
+ public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
+ /** Type is double. */
+ public static final String TAG_EXPOSURE_TIME = "ExposureTime";
+ /** Type is double. */
+ public static final String TAG_APERTURE = "FNumber";
+ /** Type is String. @hide */
+ public static final String TAG_FILE_SOURCE = "FileSource";
/** Type is int. */
- public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+ public static final String TAG_FLASH = "Flash";
+ /** Type is rational. @hide */
+ public static final String TAG_FLASH_ENERGY = "FlashEnergy";
+ /** Type is String. @hide */
+ public static final String TAG_FLASHPIX_VERSION = "FlashpixVersion";
+ /** Type is rational. */
+ public static final String TAG_FOCAL_LENGTH = "FocalLength";
+ /** Type is int. @hide */
+ public static final String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
+ /** Type is int. @hide */
+ public static final String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
+ /** Type is rational. @hide */
+ public static final String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution";
+ /** Type is rational. @hide */
+ public static final String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution";
+ /** Type is int. @hide */
+ public static final String TAG_GAIN_CONTROL = "GainControl";
/** Type is int. */
- public static final String TAG_IMAGE_LENGTH = "ImageLength";
- /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
- public static final String TAG_GPS_LATITUDE = "GPSLatitude";
- /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
- public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
- /** Type is String. */
- public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
- /** Type is String. */
- public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ public static final String TAG_ISO = "ISOSpeedRatings";
+ /** Type is String. @hide */
+ public static final String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
+ /** Type is int. @hide */
+ public static final String TAG_LIGHT_SOURCE = "LightSource";
+ /** Type is String. @hide */
+ public static final String TAG_MAKER_NOTE = "MakerNote";
+ /** Type is rational. @hide */
+ public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
+ /** Type is int. @hide */
+ public static final String TAG_METERING_MODE = "MeteringMode";
+ /** Type is String. @hide */
+ public static final String TAG_OECF = "OECF";
+ /** Type is int. @hide */
+ public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
+ /** Type is int. @hide */
+ public static final String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
+ /** Type is String. @hide */
+ public static final String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
+ /** Type is int. @hide */
+ public static final String TAG_SATURATION = "Saturation";
+ /** Type is int. @hide */
+ public static final String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
+ /** Type is String. @hide */
+ public static final String TAG_SCENE_TYPE = "SceneType";
+ /** Type is int. @hide */
+ public static final String TAG_SENSING_METHOD = "SensingMethod";
+ /** Type is int. @hide */
+ public static final String TAG_SHARPNESS = "Sharpness";
+ /** Type is rational. @hide */
+ public static final String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
+ /** Type is String. @hide */
+ public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
+ /** Type is String. @hide */
+ public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
/** Type is String. */
- public static final String TAG_EXPOSURE_TIME = "ExposureTime";
+ public static final String TAG_SUBSEC_TIME = "SubSecTime";
/** Type is String. */
- public static final String TAG_APERTURE = "FNumber";
+ public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
/** Type is String. */
- public static final String TAG_ISO = "ISOSpeedRatings";
-
+ public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
+ /** Type is int. @hide */
+ public static final String TAG_SUBJECT_AREA = "SubjectArea";
+ /** Type is double. @hide */
+ public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
+ /** Type is int. @hide */
+ public static final String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
+ /** Type is int. @hide */
+ public static final String TAG_SUBJECT_LOCATION = "SubjectLocation";
+ /** Type is String. @hide */
+ public static final String TAG_USER_COMMENT = "UserComment";
+ /** Type is int. */
+ public static final String TAG_WHITE_BALANCE = "WhiteBalance";
/**
* The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
* Type is rational.
*/
public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
-
/**
* 0 if the altitude is above sea level. 1 if the altitude is below sea
* level. Type is int.
*/
public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
-
- /** Type is String. */
- public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_DOP = "GPSDOP";
/** Type is String. */
public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
- /** Type is int. */
- public static final String TAG_WHITE_BALANCE = "WhiteBalance";
- /** Type is rational. */
- public static final String TAG_FOCAL_LENGTH = "FocalLength";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_DEST_BEARING = "GPSDestBearing";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
+ /** Type is int. @hide */
+ public static final String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
+ /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
+ public static final String TAG_GPS_LATITUDE = "GPSLatitude";
+ /** Type is String. */
+ public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+ /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
+ public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+ /** Type is String. */
+ public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_MAP_DATUM = "GPSMapDatum";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
/** Type is String. Name of GPS processing method used for location finding. */
public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_SATELLITES = "GPSSatellites";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_SPEED = "GPSSpeed";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_STATUS = "GPSStatus";
+ /** Type is String. Format is "hh:mm:ss". */
+ public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+ /** Type is rational. @hide */
+ public static final String TAG_GPS_TRACK = "GPSTrack";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_TRACK_REF = "GPSTrackRef";
+ /** Type is String. @hide */
+ public static final String TAG_GPS_VERSION_ID = "GPSVersionID";
+ /** Type is String. @hide */
+ public static final String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
+ /** Type is int. @hide */
+ public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
+ /** Type is int. @hide */
+ public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
+
+ // Private tags used for pointing the other IFD offset. The types of the following tags are int.
+ private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer";
+ private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer";
+ private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer";
+
+ // Private tags used for thumbnail information.
+ private static final String TAG_HAS_THUMBNAIL = "HasThumbnail";
+ private static final String TAG_THUMBNAIL_OFFSET = "ThumbnailOffset";
+ private static final String TAG_THUMBNAIL_LENGTH = "ThumbnailLength";
+ private static final String TAG_THUMBNAIL_DATA = "ThumbnailData";
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
@@ -87,34 +324,731 @@ public class ExifInterface {
public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror
public static final int ORIENTATION_ROTATE_180 = 3;
public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror
- public static final int ORIENTATION_TRANSPOSE = 5; // flipped about top-left <--> bottom-right axis
+ // flipped about top-left <--> bottom-right axis
+ public static final int ORIENTATION_TRANSPOSE = 5;
public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it
- public static final int ORIENTATION_TRANSVERSE = 7; // flipped about top-right <--> bottom-left axis
+ // flipped about top-right <--> bottom-left axis
+ public static final int ORIENTATION_TRANSVERSE = 7;
public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it
// Constants used for white balance
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
+
private static SimpleDateFormat sFormatter;
+ // See Exchangeable image file format for digital still cameras: Exif version 2.2.
+ // The following values are for parsing EXIF data area. There are tag groups in EXIF data area.
+ // They are called "Image File Directory". They have multiple data formats to cover various
+ // image metadata from GPS longitude to camera model name.
+
+ // Types of Exif byte alignments (see JEITA CP-3451 page 10)
+ private static final short BYTE_ALIGN_II = 0x4949; // II: Intel order
+ private static final short BYTE_ALIGN_MM = 0x4d4d; // MM: Motorola order
+
+ // Formats for the value in IFD entry (See TIFF 6.0 spec Types page 15).
+ private static final int IFD_FORMAT_BYTE = 1;
+ private static final int IFD_FORMAT_STRING = 2;
+ private static final int IFD_FORMAT_USHORT = 3;
+ private static final int IFD_FORMAT_ULONG = 4;
+ private static final int IFD_FORMAT_URATIONAL = 5;
+ private static final int IFD_FORMAT_SBYTE = 6;
+ private static final int IFD_FORMAT_UNDEFINED = 7;
+ private static final int IFD_FORMAT_SSHORT = 8;
+ private static final int IFD_FORMAT_SLONG = 9;
+ private static final int IFD_FORMAT_SRATIONAL = 10;
+ private static final int IFD_FORMAT_SINGLE = 11;
+ private static final int IFD_FORMAT_DOUBLE = 12;
+ // Names for the data formats for debugging purpose.
+ private static final String[] IFD_FORMAT_NAMES = new String[] {
+ "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT",
+ "SLONG", "SRATIONAL", "SINGLE", "DOUBLE"
+ };
+ // Sizes of the components of each IFD value format
+ private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] {
+ 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8
+ };
+ private static final byte[] EXIF_ASCII_PREFIX = new byte[] {
+ 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0
+ };
+
+ // A class for indicating EXIF rational type.
+ private static class Rational {
+ public final long numerator;
+ public final long denominator;
+
+ private Rational(long numerator, long denominator) {
+ // Handle erroneous case
+ if (denominator == 0) {
+ this.numerator = 0;
+ this.denominator = 1;
+ return;
+ }
+ this.numerator = numerator;
+ this.denominator = denominator;
+ }
+
+ @Override
+ public String toString() {
+ return numerator + "/" + denominator;
+ }
+
+ public double calculate() {
+ return (double) numerator / denominator;
+ }
+ }
+
+ // A class for indicating EXIF attribute.
+ private static class ExifAttribute {
+ public final int format;
+ public final int numberOfComponents;
+ public final byte[] bytes;
+
+ private ExifAttribute(int format, int numberOfComponents, byte[] bytes) {
+ this.format = format;
+ this.numberOfComponents = numberOfComponents;
+ this.bytes = bytes;
+ }
+
+ public static ExifAttribute createUShort(int[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_USHORT] * values.length]);
+ buffer.order(byteOrder);
+ for (int value : values) {
+ buffer.putShort((short) value);
+ }
+ return new ExifAttribute(IFD_FORMAT_USHORT, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createUShort(int value, ByteOrder byteOrder) {
+ return createUShort(new int[] {value}, byteOrder);
+ }
+
+ public static ExifAttribute createULong(long[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_ULONG] * values.length]);
+ buffer.order(byteOrder);
+ for (long value : values) {
+ buffer.putInt((int) value);
+ }
+ return new ExifAttribute(IFD_FORMAT_ULONG, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createULong(long value, ByteOrder byteOrder) {
+ return createULong(new long[] {value}, byteOrder);
+ }
+
+ public static ExifAttribute createSLong(int[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SLONG] * values.length]);
+ buffer.order(byteOrder);
+ for (int value : values) {
+ buffer.putInt(value);
+ }
+ return new ExifAttribute(IFD_FORMAT_SLONG, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createSLong(int value, ByteOrder byteOrder) {
+ return createSLong(new int[] {value}, byteOrder);
+ }
+
+ public static ExifAttribute createByte(String value) {
+ // Exception for GPSAltitudeRef tag
+ if (value.length() == 1 && value.charAt(0) >= '0' && value.charAt(0) <= '1') {
+ final byte[] bytes = new byte[] { (byte) (value.charAt(0) - '0') };
+ return new ExifAttribute(IFD_FORMAT_BYTE, bytes.length, bytes);
+ }
+ final byte[] ascii = value.getBytes(ASCII);
+ return new ExifAttribute(IFD_FORMAT_BYTE, ascii.length, ascii);
+ }
+
+ public static ExifAttribute createString(String value) {
+ final byte[] ascii = (value + '\0').getBytes(ASCII);
+ return new ExifAttribute(IFD_FORMAT_STRING, ascii.length, ascii);
+ }
+
+ public static ExifAttribute createURational(Rational[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_URATIONAL] * values.length]);
+ buffer.order(byteOrder);
+ for (Rational value : values) {
+ buffer.putInt((int) value.numerator);
+ buffer.putInt((int) value.denominator);
+ }
+ return new ExifAttribute(IFD_FORMAT_URATIONAL, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createURational(Rational value, ByteOrder byteOrder) {
+ return createURational(new Rational[] {value}, byteOrder);
+ }
+
+ public static ExifAttribute createSRational(Rational[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SRATIONAL] * values.length]);
+ buffer.order(byteOrder);
+ for (Rational value : values) {
+ buffer.putInt((int) value.numerator);
+ buffer.putInt((int) value.denominator);
+ }
+ return new ExifAttribute(IFD_FORMAT_SRATIONAL, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createSRational(Rational value, ByteOrder byteOrder) {
+ return createSRational(new Rational[] {value}, byteOrder);
+ }
+
+ public static ExifAttribute createDouble(double[] values, ByteOrder byteOrder) {
+ final ByteBuffer buffer = ByteBuffer.wrap(
+ new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_DOUBLE] * values.length]);
+ buffer.order(byteOrder);
+ for (double value : values) {
+ buffer.putDouble(value);
+ }
+ return new ExifAttribute(IFD_FORMAT_DOUBLE, values.length, buffer.array());
+ }
+
+ public static ExifAttribute createDouble(double value, ByteOrder byteOrder) {
+ return createDouble(new double[] {value}, byteOrder);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + IFD_FORMAT_NAMES[format] + ", data length:" + bytes.length + ")";
+ }
+
+ private Object getValue(ByteOrder byteOrder) {
+ try {
+ ByteOrderAwarenessDataInputStream inputStream =
+ new ByteOrderAwarenessDataInputStream(bytes);
+ inputStream.setByteOrder(byteOrder);
+ switch (format) {
+ case IFD_FORMAT_BYTE:
+ case IFD_FORMAT_SBYTE: {
+ // Exception for GPSAltitudeRef tag
+ if (bytes.length == 1 && bytes[0] >= 0 && bytes[0] <= 1) {
+ return new String(new char[] { (char) (bytes[0] + '0') });
+ }
+ return new String(bytes, ASCII);
+ }
+ case IFD_FORMAT_UNDEFINED:
+ case IFD_FORMAT_STRING: {
+ int index = 0;
+ if (numberOfComponents >= EXIF_ASCII_PREFIX.length) {
+ boolean same = true;
+ for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) {
+ if (bytes[i] != EXIF_ASCII_PREFIX[i]) {
+ same = false;
+ break;
+ }
+ }
+ if (same) {
+ index = EXIF_ASCII_PREFIX.length;
+ }
+ }
+
+ StringBuilder stringBuilder = new StringBuilder();
+ while (index < numberOfComponents) {
+ int ch = bytes[index];
+ if (ch == 0) {
+ break;
+ }
+ if (ch >= 32) {
+ stringBuilder.append((char) ch);
+ } else {
+ stringBuilder.append('?');
+ }
+ ++index;
+ }
+ return stringBuilder.toString();
+ }
+ case IFD_FORMAT_USHORT: {
+ final int[] values = new int[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readUnsignedShort();
+ }
+ return values;
+ }
+ case IFD_FORMAT_ULONG: {
+ final long[] values = new long[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readUnsignedInt();
+ }
+ return values;
+ }
+ case IFD_FORMAT_URATIONAL: {
+ final Rational[] values = new Rational[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ final long numerator = inputStream.readUnsignedInt();
+ final long denominator = inputStream.readUnsignedInt();
+ values[i] = new Rational(numerator, denominator);
+ }
+ return values;
+ }
+ case IFD_FORMAT_SSHORT: {
+ final int[] values = new int[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readShort();
+ }
+ return values;
+ }
+ case IFD_FORMAT_SLONG: {
+ final int[] values = new int[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readInt();
+ }
+ return values;
+ }
+ case IFD_FORMAT_SRATIONAL: {
+ final Rational[] values = new Rational[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ final long numerator = inputStream.readInt();
+ final long denominator = inputStream.readInt();
+ values[i] = new Rational(numerator, denominator);
+ }
+ return values;
+ }
+ case IFD_FORMAT_SINGLE: {
+ final double[] values = new double[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readFloat();
+ }
+ return values;
+ }
+ case IFD_FORMAT_DOUBLE: {
+ final double[] values = new double[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ values[i] = inputStream.readDouble();
+ }
+ return values;
+ }
+ default:
+ return null;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "IOException occurred during reading a value", e);
+ return null;
+ }
+ }
+
+ public double getDoubleValue(ByteOrder byteOrder) {
+ Object value = getValue(byteOrder);
+ if (value == null) {
+ throw new NumberFormatException("NULL can't be converted to a double value");
+ }
+ if (value instanceof String) {
+ return Double.parseDouble((String) value);
+ }
+ if (value instanceof long[]) {
+ long[] array = (long[]) value;
+ if (array.length == 1) {
+ return array[0];
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ if (value instanceof int[]) {
+ int[] array = (int[]) value;
+ if (array.length == 1) {
+ return array[0];
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ if (value instanceof double[]) {
+ double[] array = (double[]) value;
+ if (array.length == 1) {
+ return array[0];
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ if (value instanceof Rational[]) {
+ Rational[] array = (Rational[]) value;
+ if (array.length == 1) {
+ return array[0].calculate();
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ throw new NumberFormatException("Couldn't find a double value");
+ }
+
+ public int getIntValue(ByteOrder byteOrder) {
+ Object value = getValue(byteOrder);
+ if (value == null) {
+ throw new NumberFormatException("NULL can't be converted to a integer value");
+ }
+ if (value instanceof String) {
+ return Integer.parseInt((String) value);
+ }
+ if (value instanceof long[]) {
+ long[] array = (long[]) value;
+ if (array.length == 1) {
+ return (int) array[0];
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ if (value instanceof int[]) {
+ int[] array = (int[]) value;
+ if (array.length == 1) {
+ return array[0];
+ }
+ throw new NumberFormatException("There are more than one component");
+ }
+ throw new NumberFormatException("Couldn't find a integer value");
+ }
+
+ public String getStringValue(ByteOrder byteOrder) {
+ Object value = getValue(byteOrder);
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof String) {
+ return (String) value;
+ }
+
+ final StringBuilder stringBuilder = new StringBuilder();
+ if (value instanceof long[]) {
+ long[] array = (long[]) value;
+ for (int i = 0; i < array.length; ++i) {
+ stringBuilder.append(array[i]);
+ if (i + 1 != array.length) {
+ stringBuilder.append(",");
+ }
+ }
+ return stringBuilder.toString();
+ }
+ if (value instanceof int[]) {
+ int[] array = (int[]) value;
+ for (int i = 0; i < array.length; ++i) {
+ stringBuilder.append(array[i]);
+ if (i + 1 != array.length) {
+ stringBuilder.append(",");
+ }
+ }
+ return stringBuilder.toString();
+ }
+ if (value instanceof double[]) {
+ double[] array = (double[]) value;
+ for (int i = 0; i < array.length; ++i) {
+ stringBuilder.append(array[i]);
+ if (i + 1 != array.length) {
+ stringBuilder.append(",");
+ }
+ }
+ return stringBuilder.toString();
+ }
+ if (value instanceof Rational[]) {
+ Rational[] array = (Rational[]) value;
+ for (int i = 0; i < array.length; ++i) {
+ stringBuilder.append(array[i].numerator);
+ stringBuilder.append('/');
+ stringBuilder.append(array[i].denominator);
+ if (i + 1 != array.length) {
+ stringBuilder.append(",");
+ }
+ }
+ return stringBuilder.toString();
+ }
+ return null;
+ }
+
+ public int size() {
+ return IFD_FORMAT_BYTES_PER_FORMAT[format] * numberOfComponents;
+ }
+ }
+
+ // A class for indicating EXIF tag.
+ private static class ExifTag {
+ public final int number;
+ public final String name;
+ public final int primaryFormat;
+ public final int secondaryFormat;
+
+ private ExifTag(String name, int number, int format) {
+ this.name = name;
+ this.number = number;
+ this.primaryFormat = format;
+ this.secondaryFormat = -1;
+ }
+
+ private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) {
+ this.name = name;
+ this.number = number;
+ this.primaryFormat = primaryFormat;
+ this.secondaryFormat = secondaryFormat;
+ }
+ }
+
+ // Primary image IFD TIFF tags (See JEITA CP-3451 Table 14. page 54).
+ private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] {
+ new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING),
+ new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
+ new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
+ new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING),
+ new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING),
+ new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
+ new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
+ new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
+ };
+
+ // Primary image IFD Exif Private tags (See JEITA CP-3451 Table 15. page 55).
+ private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] {
+ new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_APERTURE, 33437, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852, IFD_FORMAT_STRING),
+ new ExifTag(TAG_ISO, 34855, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_OECF, 34856, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING),
+ new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING),
+ new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING),
+ new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_COMPRESSED_BITS_PER_PIXEL, 37122, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL),
+ new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL),
+ new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL),
+ new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_SUBJECT_DISTANCE, 37382, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_SUBJECT_AREA, 37396, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_MAKER_NOTE, 37500, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_USER_COMMENT, 37510, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING),
+ new ExifTag(TAG_SUBSEC_TIME_ORIG, 37521, IFD_FORMAT_STRING),
+ new ExifTag(TAG_SUBSEC_TIME_DIG, 37522, IFD_FORMAT_STRING),
+ new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_RELATED_SOUND_FILE, 40964, IFD_FORMAT_STRING),
+ new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_FLASH_ENERGY, 41483, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_SPATIAL_FREQUENCY_RESPONSE, 41484, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_FOCAL_PLANE_X_RESOLUTION, 41486, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_FOCAL_PLANE_Y_RESOLUTION, 41487, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SUBJECT_LOCATION, 41492, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_EXPOSURE_INDEX, 41493, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_CFA_PATTERN, 41730, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_DIGITAL_ZOOM_RATIO, 41988, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_FOCAL_LENGTH_IN_35MM_FILM, 41989, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_GAIN_CONTROL, 41991, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016, IFD_FORMAT_STRING),
+ };
+
+ // Primary image IFD GPS Info tags (See JEITA CP-3451 Table 16. page 56).
+ private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] {
+ new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE),
+ new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE),
+ new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_SATELLITES, 8, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_STATUS, 9, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_MEASURE_MODE, 10, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DOP, 11, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_SPEED, 13, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_TRACK, 15, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_IMG_DIRECTION, 17, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_MAP_DATUM, 18, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DEST_LATITUDE_REF, 19, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DEST_LATITUDE, 20, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_DEST_LONGITUDE_REF, 21, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DEST_LONGITUDE, 22, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DEST_BEARING, 24, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DEST_DISTANCE, 26, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_GPS_PROCESSING_METHOD, 27, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_GPS_AREA_INFORMATION, 28, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_GPS_DATESTAMP, 29, IFD_FORMAT_STRING),
+ new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT),
+ };
+ // Primary image IFD Interoperability tag (See JEITA CP-3451 Table 17. page 56).
+ private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] {
+ new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING),
+ };
+ // IFD Thumbnail tags (See JEITA CP-3451 Table 18. page 57).
+ private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] {
+ new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING),
+ new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
+ new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
+ new ExifTag(TAG_STRIP_OFFSETS, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING),
+ new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING),
+ new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
+ new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
+ new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
+ new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
+ new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
+ };
+
+ // See JEITA CP-3451 Figure 5. page 9.
+ // The following values are used for indicating pointers to the other Image File Directorys.
+
+ // Indices of Exif Ifd tag groups
+ private static final int IFD_TIFF_HINT = 0;
+ private static final int IFD_EXIF_HINT = 1;
+ private static final int IFD_GPS_HINT = 2;
+ private static final int IFD_INTEROPERABILITY_HINT = 3;
+ private static final int IFD_THUMBNAIL_HINT = 4;
+ // List of Exif tag groups
+ private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] {
+ IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS,
+ IFD_THUMBNAIL_TAGS
+ };
+ // List of tags for pointing to the other image file directory offset.
+ private static final ExifTag[] IFD_POINTER_TAGS = new ExifTag[] {
+ new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
+ new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
+ };
+ // List of indices of the indicated tag groups according to the IFD_POINTER_TAGS
+ private static final int[] IFD_POINTER_TAG_HINTS = new int[] {
+ IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT
+ };
+ // Tags for indicating the thumbnail offset and length
+ private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG);
+ private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG =
+ new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG);
+
+ // Mappings from tag number to tag name and each item represents one IFD tag group.
+ private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
+ // Mappings from tag name to tag number and each item represents one IFD tag group.
+ private static final HashMap[] sExifTagMapsForWriting = new HashMap[EXIF_TAGS.length];
+ private static final HashSet sTagSetForCompatibility = new HashSet(Arrays.asList(
+ TAG_APERTURE, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE,
+ TAG_GPS_TIMESTAMP));
+
+ // See JPEG File Interchange Format Version 1.02.
+ // The following values are defined for handling JPEG streams. In this implementation, we are
+ // not only getting information from EXIF but also from some JPEG special segments such as
+ // MARKER_COM for user comment and MARKER_SOFx for image width and height.
+
+ private static final Charset ASCII = Charset.forName("US-ASCII");
+ // Identifier for EXIF APP1 segment in JPEG
+ private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII);
+ // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with
+ // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start
+ // of frame(baseline DCT) and the image size info exists in its beginning part.
+ private static final byte MARKER = (byte) 0xff;
+ private static final byte MARKER_SOI = (byte) 0xd8;
+ private static final byte MARKER_SOF0 = (byte) 0xc0;
+ private static final byte MARKER_SOF1 = (byte) 0xc1;
+ private static final byte MARKER_SOF2 = (byte) 0xc2;
+ private static final byte MARKER_SOF3 = (byte) 0xc3;
+ private static final byte MARKER_SOF5 = (byte) 0xc5;
+ private static final byte MARKER_SOF6 = (byte) 0xc6;
+ private static final byte MARKER_SOF7 = (byte) 0xc7;
+ private static final byte MARKER_SOF9 = (byte) 0xc9;
+ private static final byte MARKER_SOF10 = (byte) 0xca;
+ private static final byte MARKER_SOF11 = (byte) 0xcb;
+ private static final byte MARKER_SOF13 = (byte) 0xcd;
+ private static final byte MARKER_SOF14 = (byte) 0xce;
+ private static final byte MARKER_SOF15 = (byte) 0xcf;
+ private static final byte MARKER_SOS = (byte) 0xda;
+ private static final byte MARKER_APP1 = (byte) 0xe1;
+ private static final byte MARKER_COM = (byte) 0xfe;
+ private static final byte MARKER_EOI = (byte) 0xd9;
+
static {
- System.loadLibrary("jhead_jni");
sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ // Build up the hash tables to look up Exif tags for reading Exif tags.
+ for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
+ sExifTagMapsForReading[hint] = new HashMap();
+ sExifTagMapsForWriting[hint] = new HashMap();
+ for (ExifTag tag : EXIF_TAGS[hint]) {
+ sExifTagMapsForReading[hint].put(tag.number, tag);
+ sExifTagMapsForWriting[hint].put(tag.name, tag);
+ }
+ }
}
- private String mFilename;
- private HashMap mAttributes;
+ private final String mFilename;
+ private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
+ private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
private boolean mHasThumbnail;
+ // The following values used for indicating a thumbnail position.
+ private int mThumbnailOffset;
+ private int mThumbnailLength;
+ private byte[] mThumbnailBytes;
+ private boolean mIsSupportedFile;
- // Because the underlying implementation (jhead) uses static variables,
- // there can only be one user at a time for the native functions (and
- // they cannot keep state in the native code across function calls). We
- // use sLock to serialize the accesses.
- private static final Object sLock = new Object();
+ // Pattern to check non zero timestamp
+ private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
+ // Pattern to check gps timestamp
+ private static final Pattern sGpsTimestampPattern =
+ Pattern.compile("^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$");
/**
- * Reads Exif tags from the specified JPEG file.
+ * Reads Exif tags from the specified image file.
*/
public ExifInterface(String filename) throws IOException {
if (filename == null) {
@@ -124,53 +1058,99 @@ public ExifInterface(String filename) throws IOException {
loadAttributes();
}
+
+ /**
+ * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in
+ * the image file.
+ *
+ * @param tag the name of the tag.
+ */
+ private ExifAttribute getExifAttribute(String tag) {
+ // Retrieves all tag groups. The value from primary image tag group has a higher priority
+ // than the value from the thumbnail tag group if there are more than one candidates.
+ for (int i = 0; i < EXIF_TAGS.length; ++i) {
+ Object value = mAttributes[i].get(tag);
+ if (value != null) {
+ return (ExifAttribute) value;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the value of the specified tag or {@code null} if there
- * is no such tag in the JPEG file.
+ * is no such tag in the image file.
*
* @param tag the name of the tag.
*/
public String getAttribute(String tag) {
- return mAttributes.get(tag);
+ ExifAttribute attribute = getExifAttribute(tag);
+ if (attribute != null) {
+ if (!sTagSetForCompatibility.contains(tag)) {
+ return attribute.getStringValue(mExifByteOrder);
+ }
+ if (tag.equals(TAG_GPS_TIMESTAMP)) {
+ // Convert the rational values to the custom formats for backwards compatibility.
+ if (attribute.format != IFD_FORMAT_URATIONAL
+ && attribute.format != IFD_FORMAT_SRATIONAL) {
+ return null;
+ }
+ Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder);
+ if (array.length != 3) {
+ return null;
+ }
+ return String.format("%02d:%02d:%02d",
+ (int) ((float) array[0].numerator / array[0].denominator),
+ (int) ((float) array[1].numerator / array[1].denominator),
+ (int) ((float) array[2].numerator / array[2].denominator));
+ }
+ try {
+ return Double.toString(attribute.getDoubleValue(mExifByteOrder));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ return null;
}
/**
* Returns the integer value of the specified tag. If there is no such tag
- * in the JPEG file or the value cannot be parsed as integer, return
+ * in the image file or the value cannot be parsed as integer, return
* defaultValue.
*
* @param tag the name of the tag.
* @param defaultValue the value to return if the tag is not available.
*/
public int getAttributeInt(String tag, int defaultValue) {
- String value = mAttributes.get(tag);
- if (value == null) return defaultValue;
+ ExifAttribute exifAttribute = getExifAttribute(tag);
+ if (exifAttribute == null) {
+ return defaultValue;
+ }
+
try {
- return Integer.valueOf(value);
- } catch (NumberFormatException ex) {
+ return exifAttribute.getIntValue(mExifByteOrder);
+ } catch (NumberFormatException e) {
return defaultValue;
}
}
/**
- * Returns the double value of the specified rational tag. If there is no
- * such tag in the JPEG file or the value cannot be parsed as double, return
- * defaultValue.
+ * Returns the double value of the tag that is specified as rational or contains a
+ * double-formatted value. If there is no such tag in the image file or the value cannot be
+ * parsed as double, return defaultValue.
*
* @param tag the name of the tag.
* @param defaultValue the value to return if the tag is not available.
*/
public double getAttributeDouble(String tag, double defaultValue) {
- String value = mAttributes.get(tag);
- if (value == null) return defaultValue;
+ ExifAttribute exifAttribute = getExifAttribute(tag);
+ if (exifAttribute == null) {
+ return defaultValue;
+ }
+
try {
- int index = value.indexOf("/");
- if (index == -1) return defaultValue;
- double denom = Double.parseDouble(value.substring(index + 1));
- if (denom == 0) return defaultValue;
- double num = Double.parseDouble(value.substring(0, index));
- return num / denom;
- } catch (NumberFormatException ex) {
+ return exifAttribute.getDoubleValue(mExifByteOrder);
+ } catch (NumberFormatException e) {
return defaultValue;
}
}
@@ -182,116 +1162,291 @@ public double getAttributeDouble(String tag, double defaultValue) {
* @param value the value of the tag.
*/
public void setAttribute(String tag, String value) {
- mAttributes.put(tag, value);
+ // Convert the given value to rational values for backwards compatibility.
+ if (value != null && sTagSetForCompatibility.contains(tag)) {
+ if (tag.equals(TAG_GPS_TIMESTAMP)) {
+ Matcher m = sGpsTimestampPattern.matcher(value);
+ if (!m.find()) {
+ Log.w(TAG, "Invalid value for " + tag + " : " + value);
+ return;
+ }
+ value = Integer.parseInt(m.group(1)) + "/1," + Integer.parseInt(m.group(2)) + "/1,"
+ + Integer.parseInt(m.group(3)) + "/1";
+ } else {
+ try {
+ double doubleValue = Double.parseDouble(value);
+ value = (long) (doubleValue * 10000L) + "/10000";
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid value for " + tag + " : " + value);
+ return;
+ }
+ }
+ }
+
+ for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ if (i == IFD_THUMBNAIL_HINT && !mHasThumbnail) {
+ continue;
+ }
+ final Object obj = sExifTagMapsForWriting[i].get(tag);
+ if (obj != null) {
+ if (value == null) {
+ mAttributes[i].remove(tag);
+ continue;
+ }
+ final ExifTag exifTag = (ExifTag) obj;
+ Pair guess = guessDataFormat(value);
+ int dataFormat;
+ if (exifTag.primaryFormat == guess.first || exifTag.primaryFormat == guess.second) {
+ dataFormat = exifTag.primaryFormat;
+ } else if (exifTag.secondaryFormat != -1 && (exifTag.secondaryFormat == guess.first
+ || exifTag.secondaryFormat == guess.second)) {
+ dataFormat = exifTag.secondaryFormat;
+ } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE
+ || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED
+ || exifTag.primaryFormat == IFD_FORMAT_STRING) {
+ dataFormat = exifTag.primaryFormat;
+ } else {
+ Log.w(TAG, "Given tag (" + tag + ") value didn't match with one of expected "
+ + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat]
+ + (exifTag.secondaryFormat == -1 ? "" : ", "
+ + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: "
+ + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? "" : ", "
+ + IFD_FORMAT_NAMES[guess.second]) + ")");
+ continue;
+ }
+ switch (dataFormat) {
+ case IFD_FORMAT_BYTE: {
+ mAttributes[i].put(tag, ExifAttribute.createByte(value));
+ break;
+ }
+ case IFD_FORMAT_UNDEFINED:
+ case IFD_FORMAT_STRING: {
+ mAttributes[i].put(tag, ExifAttribute.createString(value));
+ break;
+ }
+ case IFD_FORMAT_USHORT: {
+ final String[] values = value.split(",");
+ final int[] intArray = new int[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ intArray[j] = Integer.parseInt(values[j]);
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createUShort(intArray, mExifByteOrder));
+ break;
+ }
+ case IFD_FORMAT_SLONG: {
+ final String[] values = value.split(",");
+ final int[] intArray = new int[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ intArray[j] = Integer.parseInt(values[j]);
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createSLong(intArray, mExifByteOrder));
+ break;
+ }
+ case IFD_FORMAT_ULONG: {
+ final String[] values = value.split(",");
+ final long[] longArray = new long[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ longArray[j] = Long.parseLong(values[j]);
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createULong(longArray, mExifByteOrder));
+ break;
+ }
+ case IFD_FORMAT_URATIONAL: {
+ final String[] values = value.split(",");
+ final Rational[] rationalArray = new Rational[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ final String[] numbers = values[j].split("/");
+ rationalArray[j] = new Rational(Long.parseLong(numbers[0]),
+ Long.parseLong(numbers[1]));
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createURational(rationalArray, mExifByteOrder));
+ break;
+ }
+ case IFD_FORMAT_SRATIONAL: {
+ final String[] values = value.split(",");
+ final Rational[] rationalArray = new Rational[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ final String[] numbers = values[j].split("/");
+ rationalArray[j] = new Rational(Long.parseLong(numbers[0]),
+ Long.parseLong(numbers[1]));
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createSRational(rationalArray, mExifByteOrder));
+ break;
+ }
+ case IFD_FORMAT_DOUBLE: {
+ final String[] values = value.split(",");
+ final double[] doubleArray = new double[values.length];
+ for (int j = 0; j < values.length; ++j) {
+ doubleArray[j] = Double.parseDouble(values[j]);
+ }
+ mAttributes[i].put(tag,
+ ExifAttribute.createDouble(doubleArray, mExifByteOrder));
+ break;
+ }
+ default:
+ Log.w(TAG, "Data format isn't one of expected formats: " + dataFormat);
+ continue;
+ }
+ }
+ }
}
/**
- * Initialize mAttributes with the attributes from the file mFilename.
+ * Update the values of the tags in the tag groups if any value for the tag already was stored.
*
- * mAttributes is a HashMap which stores the Exif attributes of the file.
- * The key is the standard tag name and the value is the tag's value: e.g.
- * Model -> Nikon. Numeric values are stored as strings.
+ * @param tag the name of the tag.
+ * @param value the value of the tag in a form of {@link ExifAttribute}.
+ * @return Returns {@code true} if updating is placed.
+ */
+ private boolean updateAttribute(String tag, ExifAttribute value) {
+ boolean updated = false;
+ for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ if (mAttributes[i].containsKey(tag)) {
+ mAttributes[i].put(tag, value);
+ updated = true;
+ }
+ }
+ return updated;
+ }
+
+ /**
+ * Remove any values of the specified tag.
*
- * This function also initialize mHasThumbnail to indicate whether the
- * file has a thumbnail inside.
+ * @param tag the name of the tag.
+ */
+ private void removeAttribute(String tag) {
+ for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ mAttributes[i].remove(tag);
+ }
+ }
+
+ /**
+ * This function decides which parser to read the image data according to the given input stream
+ * type and the content of the input stream. In each case, it reads the first three bytes to
+ * determine whether the image data format is JPEG or not.
*/
private void loadAttributes() throws IOException {
- // format of string passed from native C code:
- // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
- // example:
- // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
- mAttributes = new HashMap();
-
- String attrStr;
- synchronized (sLock) {
- attrStr = getAttributesNative(mFilename);
- }
-
- // get count
- int ptr = attrStr.indexOf(' ');
- int count = Integer.parseInt(attrStr.substring(0, ptr));
- // skip past the space between item count and the rest of the attributes
- ++ptr;
-
- for (int i = 0; i < count; i++) {
- // extract the attribute name
- int equalPos = attrStr.indexOf('=', ptr);
- String attrName = attrStr.substring(ptr, equalPos);
- ptr = equalPos + 1; // skip past =
-
- // extract the attribute value length
- int lenPos = attrStr.indexOf(' ', ptr);
- int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
- ptr = lenPos + 1; // skip pas the space
-
- // extract the attribute value
- String attrValue = attrStr.substring(ptr, ptr + attrLen);
- ptr += attrLen;
-
- if (attrName.equals("hasThumbnail")) {
- mHasThumbnail = attrValue.equalsIgnoreCase("true");
- } else {
- mAttributes.put(attrName, attrValue);
+ // Initialize mAttributes.
+ for (int i = 0; i < EXIF_TAGS.length; ++i) {
+ mAttributes[i] = new HashMap();
+ }
+ InputStream in = null;
+ try {
+ in = new FileInputStream(mFilename);
+ getJpegAttributes(in);
+ mIsSupportedFile = true;
+ } catch (IOException e) {
+ // Ignore exceptions in order to keep the compatibility with the old versions of
+ // ExifInterface.
+ mIsSupportedFile = false;
+ Log.w(TAG, "Invalid image.", e);
+ } finally {
+ addDefaultValuesForCompatibility();
+ if (DEBUG) {
+ printAttributes();
+ }
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ // Prints out attributes for debugging.
+ private void printAttributes() {
+ for (int i = 0; i < mAttributes.length; ++i) {
+ Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size());
+ for (Map.Entry entry : (Set) mAttributes[i].entrySet()) {
+ final ExifAttribute tagValue = (ExifAttribute) entry.getValue();
+ Log.d(TAG, "tagName: " + entry.getKey() + ", tagType: " + tagValue.toString()
+ + ", tagValue: '" + tagValue.getStringValue(mExifByteOrder) + "'");
}
}
}
/**
- * Save the tag data into the JPEG file. This is expensive because it involves
- * copying all the JPG data from one file to another and deleting the old file
- * and renaming the other. It's best to use {@link #setAttribute(String,String)}
- * to set all attributes to write and make a single call rather than multiple
- * calls for each attribute.
+ * Save the tag data into the original image file. This is expensive because it involves
+ * copying all the data from one file to another and deleting the old file and renaming the
+ * other. It's best to use {@link #setAttribute(String,String)} to set all attributes to write
+ * and make a single call rather than multiple calls for each attribute.
*/
public void saveAttributes() throws IOException {
- // format of string passed to native C code:
- // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
- // example:
- // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
- StringBuilder sb = new StringBuilder();
- int size = mAttributes.size();
- if (mAttributes.containsKey("hasThumbnail")) {
- --size;
- }
- sb.append(size + " ");
- for (Map.Entry iter : mAttributes.entrySet()) {
- String key = iter.getKey();
- if (key.equals("hasThumbnail")) {
- // this is a fake attribute not saved as an exif tag
- continue;
- }
- String val = iter.getValue();
- sb.append(key + "=");
- sb.append(val.length() + " ");
- sb.append(val);
+ if (!mIsSupportedFile) {
+ throw new UnsupportedOperationException(
+ "ExifInterface only supports saving attributes on JPEG formats.");
}
- String s = sb.toString();
- synchronized (sLock) {
- saveAttributesNative(mFilename, s);
- commitChangesNative(mFilename);
+ // Keep the thumbnail in memory
+ mThumbnailBytes = getThumbnail();
+
+ File tempFile = null;
+ // Move the original file to temporary file.
+ tempFile = new File(mFilename + ".tmp");
+ File originalFile = new File(mFilename);
+ if (!originalFile.renameTo(tempFile)) {
+ throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
}
+
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ // Save the new file.
+ in = new FileInputStream(tempFile);
+ out = new FileOutputStream(mFilename);
+ saveJpegAttributes(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ tempFile.delete();
+ }
+
+ // Discard the thumbnail in memory
+ mThumbnailBytes = null;
}
/**
- * Returns true if the JPEG file has a thumbnail.
+ * Returns true if the image file has a thumbnail.
*/
public boolean hasThumbnail() {
return mHasThumbnail;
}
/**
- * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
+ * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail.
* The returned data is in JPEG format and can be decoded using
* {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
*/
public byte[] getThumbnail() {
- synchronized (sLock) {
- return getThumbnailNative(mFilename);
+ if (!mHasThumbnail) {
+ return null;
+ }
+ if (mThumbnailBytes != null) {
+ return mThumbnailBytes;
+ }
+
+ // Read the thumbnail.
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(mFilename);
+ if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
+ throw new IOException("Corrupted image");
+ }
+ byte[] buffer = new byte[mThumbnailLength];
+ if (in.read(buffer) != mThumbnailLength) {
+ throw new IOException("Corrupted image");
+ }
+ return buffer;
+ } catch (IOException e) {
+ // Couldn't get a thumbnail image.
+ } finally {
+ IoUtils.closeQuietly(in);
}
+ return null;
}
/**
- * Returns the offset and length of thumbnail inside the JPEG file, or
+ * Returns the offset and length of thumbnail inside the image file, or
* {@code null} if there is no thumbnail.
*
* @return two-element array, the offset in the first value, and length in
@@ -299,9 +1454,15 @@ public byte[] getThumbnail() {
* @hide
*/
public long[] getThumbnailRange() {
- synchronized (sLock) {
- return getThumbnailRangeNative(mFilename);
+ if (!mHasThumbnail) {
+ return null;
}
+
+ long[] range = new long[2];
+ range[0] = mThumbnailOffset;
+ range[1] = mThumbnailLength;
+
+ return range;
}
/**
@@ -310,10 +1471,10 @@ public long[] getThumbnailRange() {
* Exif tags are not available.
*/
public boolean getLatLong(float output[]) {
- String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
- String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
- String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
- String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
+ String latValue = getAttribute(TAG_GPS_LATITUDE);
+ String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
+ String lngValue = getAttribute(TAG_GPS_LONGITUDE);
+ String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF);
if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
try {
@@ -339,27 +1500,44 @@ public double getAltitude(double defaultValue) {
int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
if (altitude >= 0 && ref >= 0) {
- return (double) (altitude * ((ref == 1) ? -1 : 1));
+ return (altitude * ((ref == 1) ? -1 : 1));
} else {
return defaultValue;
}
}
/**
- * Returns number of milliseconds since Jan. 1, 1970, midnight.
+ * Returns number of milliseconds since Jan. 1, 1970, midnight local time.
* Returns -1 if the date time information if not available.
* @hide
*/
public long getDateTime() {
- String dateTimeString = mAttributes.get(TAG_DATETIME);
- if (dateTimeString == null) return -1;
+ String dateTimeString = getAttribute(TAG_DATETIME);
+ if (dateTimeString == null
+ || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
ParsePosition pos = new ParsePosition(0);
try {
+ // The exif field is in local time. Parsing it as if it is UTC will yield time
+ // since 1/1/1970 local time
Date datetime = sFormatter.parse(dateTimeString, pos);
if (datetime == null) return -1;
- return datetime.getTime();
- } catch (IllegalArgumentException ex) {
+ long msecs = datetime.getTime();
+
+ String subSecs = getAttribute(TAG_SUBSEC_TIME);
+ if (subSecs != null) {
+ try {
+ long sub = Long.valueOf(subSecs);
+ while (sub > 1000) {
+ sub /= 10;
+ }
+ msecs += sub;
+ } catch (NumberFormatException e) {
+ // Ignored
+ }
+ }
+ return msecs;
+ } catch (IllegalArgumentException e) {
return -1;
}
}
@@ -370,25 +1548,27 @@ public long getDateTime() {
* @hide
*/
public long getGpsDateTime() {
- String date = mAttributes.get(TAG_GPS_DATESTAMP);
- String time = mAttributes.get(TAG_GPS_TIMESTAMP);
- if (date == null || time == null) return -1;
+ String date = getAttribute(TAG_GPS_DATESTAMP);
+ String time = getAttribute(TAG_GPS_TIMESTAMP);
+ if (date == null || time == null
+ || (!sNonZeroTimePattern.matcher(date).matches()
+ && !sNonZeroTimePattern.matcher(time).matches())) {
+ return -1;
+ }
String dateTimeString = date + ' ' + time;
- if (dateTimeString == null) return -1;
ParsePosition pos = new ParsePosition(0);
try {
Date datetime = sFormatter.parse(dateTimeString, pos);
if (datetime == null) return -1;
return datetime.getTime();
- } catch (IllegalArgumentException ex) {
+ } catch (IllegalArgumentException e) {
return -1;
}
}
- private static float convertRationalLatLonToFloat(
- String rationalString, String ref) {
+ private static float convertRationalLatLonToFloat(String rationalString, String ref) {
try {
String [] parts = rationalString.split(",");
@@ -411,25 +1591,951 @@ private static float convertRationalLatLonToFloat(
}
return (float) result;
} catch (NumberFormatException e) {
- // Some of the nubmers are not valid
- throw new IllegalArgumentException();
+ // Not valid
+ throw new IllegalArgumentException(e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
- // Some of the rational does not follow the correct format
- throw new IllegalArgumentException();
+ // Not valid
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
+ // Loads EXIF attributes from a JPEG input stream.
+ private void getJpegAttributes(InputStream inputStream) throws IOException {
+ // See JPEG File Interchange Format Specification page 5.
+ if (DEBUG) {
+ Log.d(TAG, "getJpegAttributes starting with: " + inputStream);
+ }
+ DataInputStream dataInputStream = new DataInputStream(inputStream);
+ byte marker;
+ int bytesRead = 0;
+ if ((marker = dataInputStream.readByte()) != MARKER) {
+ throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
+ }
+ ++bytesRead;
+ if (dataInputStream.readByte() != MARKER_SOI) {
+ throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
+ }
+ ++bytesRead;
+ while (true) {
+ marker = dataInputStream.readByte();
+ if (marker != MARKER) {
+ throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
+ }
+ ++bytesRead;
+ marker = dataInputStream.readByte();
+ if (DEBUG) {
+ Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
+ }
+ ++bytesRead;
+
+ // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
+ // the image data will terminate right after.
+ if (marker == MARKER_EOI || marker == MARKER_SOS) {
+ break;
+ }
+ int length = dataInputStream.readUnsignedShort() - 2;
+ bytesRead += 2;
+ if (DEBUG) {
+ Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: "
+ + (length + 2) + ")");
+ }
+ if (length < 0) {
+ throw new IOException("Invalid length");
+ }
+ switch (marker) {
+ case MARKER_APP1: {
+ if (DEBUG) {
+ Log.d(TAG, "MARKER_APP1");
+ }
+ if (length < 6) {
+ // Skip if it's not an EXIF APP1 segment.
+ break;
+ }
+ byte[] identifier = new byte[6];
+ if (inputStream.read(identifier) != 6) {
+ throw new IOException("Invalid exif");
+ }
+ bytesRead += 6;
+ length -= 6;
+ if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
+ // Skip if it's not an EXIF APP1 segment.
+ break;
+ }
+ if (length <= 0) {
+ throw new IOException("Invalid exif");
+ }
+ if (DEBUG) {
+ Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")");
+ }
+ byte[] bytes = new byte[length];
+ if (dataInputStream.read(bytes) != length) {
+ throw new IOException("Invalid exif");
+ }
+ readExifSegment(bytes, bytesRead);
+ bytesRead += length;
+ length = 0;
+ break;
+ }
+
+ case MARKER_COM: {
+ byte[] bytes = new byte[length];
+ if (dataInputStream.read(bytes) != length) {
+ throw new IOException("Invalid exif");
+ }
+ length = 0;
+ if (getAttribute(TAG_USER_COMMENT) == null) {
+ mAttributes[IFD_EXIF_HINT].put(TAG_USER_COMMENT, ExifAttribute.createString(
+ new String(bytes, ASCII)));
+ }
+ break;
+ }
+
+ case MARKER_SOF0:
+ case MARKER_SOF1:
+ case MARKER_SOF2:
+ case MARKER_SOF3:
+ case MARKER_SOF5:
+ case MARKER_SOF6:
+ case MARKER_SOF7:
+ case MARKER_SOF9:
+ case MARKER_SOF10:
+ case MARKER_SOF11:
+ case MARKER_SOF13:
+ case MARKER_SOF14:
+ case MARKER_SOF15: {
+ if (dataInputStream.skipBytes(1) != 1) {
+ throw new IOException("Invalid SOFx");
+ }
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong(
+ dataInputStream.readUnsignedShort(), mExifByteOrder));
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong(
+ dataInputStream.readUnsignedShort(), mExifByteOrder));
+ length -= 5;
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+ if (length < 0) {
+ throw new IOException("Invalid length");
+ }
+ if (dataInputStream.skipBytes(length) != length) {
+ throw new IOException("Invalid JPEG segment");
+ }
+ bytesRead += length;
+ }
+ }
+
+ // Stores a new JPEG image with EXIF attributes into a given output stream.
+ private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
+ throws IOException {
+ // See JPEG File Interchange Format Specification page 5.
+ if (DEBUG) {
+ Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
+ + ", outputStream: " + outputStream + ")");
+ }
+ DataInputStream dataInputStream = new DataInputStream(inputStream);
+ ByteOrderAwarenessDataOutputStream dataOutputStream =
+ new ByteOrderAwarenessDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
+ if (dataInputStream.readByte() != MARKER) {
+ throw new IOException("Invalid marker");
+ }
+ dataOutputStream.writeByte(MARKER);
+ if (dataInputStream.readByte() != MARKER_SOI) {
+ throw new IOException("Invalid marker");
+ }
+ dataOutputStream.writeByte(MARKER_SOI);
+
+ // Write EXIF APP1 segment
+ dataOutputStream.writeByte(MARKER);
+ dataOutputStream.writeByte(MARKER_APP1);
+ writeExifSegment(dataOutputStream, 6);
+
+ byte[] bytes = new byte[4096];
+
+ while (true) {
+ byte marker = dataInputStream.readByte();
+ if (marker != MARKER) {
+ throw new IOException("Invalid marker");
+ }
+ marker = dataInputStream.readByte();
+ switch (marker) {
+ case MARKER_APP1: {
+ int length = dataInputStream.readUnsignedShort() - 2;
+ if (length < 0) {
+ throw new IOException("Invalid length");
+ }
+ byte[] identifier = new byte[6];
+ if (length >= 6) {
+ if (dataInputStream.read(identifier) != 6) {
+ throw new IOException("Invalid exif");
+ }
+ if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
+ // Skip the original EXIF APP1 segment.
+ if (dataInputStream.skip(length - 6) != length - 6) {
+ throw new IOException("Invalid length");
+ }
+ break;
+ }
+ }
+ // Copy non-EXIF APP1 segment.
+ dataOutputStream.writeByte(MARKER);
+ dataOutputStream.writeByte(marker);
+ dataOutputStream.writeUnsignedShort(length + 2);
+ if (length >= 6) {
+ length -= 6;
+ dataOutputStream.write(identifier);
+ }
+ int read;
+ while (length > 0 && (read = dataInputStream.read(
+ bytes, 0, Math.min(length, bytes.length))) >= 0) {
+ dataOutputStream.write(bytes, 0, read);
+ length -= read;
+ }
+ break;
+ }
+ case MARKER_EOI:
+ case MARKER_SOS: {
+ dataOutputStream.writeByte(MARKER);
+ dataOutputStream.writeByte(marker);
+ // Copy all the remaining data
+ Streams.copy(dataInputStream, dataOutputStream);
+ return;
+ }
+ default: {
+ // Copy JPEG segment
+ dataOutputStream.writeByte(MARKER);
+ dataOutputStream.writeByte(marker);
+ int length = dataInputStream.readUnsignedShort();
+ dataOutputStream.writeUnsignedShort(length);
+ length -= 2;
+ if (length < 0) {
+ throw new IOException("Invalid length");
+ }
+ int read;
+ while (length > 0 && (read = dataInputStream.read(
+ bytes, 0, Math.min(length, bytes.length))) >= 0) {
+ dataOutputStream.write(bytes, 0, read);
+ length -= read;
+ }
+ break;
+ }
+ }
}
}
- private native boolean appendThumbnailNative(String fileName,
- String thumbnailFileName);
+ // Reads the given EXIF byte area and save its tag data into attributes.
+ private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning) throws IOException {
+ // Parse TIFF Headers. See JEITA CP-3451C Table 1. page 10.
+ ByteOrderAwarenessDataInputStream dataInputStream =
+ new ByteOrderAwarenessDataInputStream(exifBytes);
- private native void saveAttributesNative(String fileName,
- String compressedAttributes);
+ // Read byte align
+ short byteOrder = dataInputStream.readShort();
+ switch (byteOrder) {
+ case BYTE_ALIGN_II:
+ if (DEBUG) {
+ Log.d(TAG, "readExifSegment: Byte Align II");
+ }
+ mExifByteOrder = ByteOrder.LITTLE_ENDIAN;
+ break;
+ case BYTE_ALIGN_MM:
+ if (DEBUG) {
+ Log.d(TAG, "readExifSegment: Byte Align MM");
+ }
+ mExifByteOrder = ByteOrder.BIG_ENDIAN;
+ break;
+ default:
+ throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
+ }
- private native String getAttributesNative(String fileName);
+ // Set byte order.
+ dataInputStream.setByteOrder(mExifByteOrder);
- private native void commitChangesNative(String fileName);
+ int startCode = dataInputStream.readUnsignedShort();
+ if (startCode != 0x2a) {
+ throw new IOException("Invalid exif start: " + Integer.toHexString(startCode));
+ }
- private native byte[] getThumbnailNative(String fileName);
+ // Read first ifd offset
+ long firstIfdOffset = dataInputStream.readUnsignedInt();
+ if (firstIfdOffset < 8 || firstIfdOffset >= exifBytes.length) {
+ throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
+ }
+ firstIfdOffset -= 8;
+ if (firstIfdOffset > 0) {
+ if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset) {
+ throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
+ }
+ }
+
+ // Read primary image TIFF image file directory.
+ readImageFileDirectory(dataInputStream, IFD_TIFF_HINT);
+
+ // Process thumbnail.
+ String jpegInterchangeFormatString = getAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
+ String jpegInterchangeFormatLengthString =
+ getAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
+ if (jpegInterchangeFormatString != null && jpegInterchangeFormatLengthString != null) {
+ try {
+ int jpegInterchangeFormat = Integer.parseInt(jpegInterchangeFormatString);
+ int jpegInterchangeFormatLength = Integer
+ .parseInt(jpegInterchangeFormatLengthString);
+ // The following code limits the size of thumbnail size not to overflow EXIF data area.
+ jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat
+ + jpegInterchangeFormatLength, exifBytes.length) - jpegInterchangeFormat;
+ if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) {
+ mHasThumbnail = true;
+ mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
+ mThumbnailLength = jpegInterchangeFormatLength;
+ }
+ } catch (NumberFormatException e) {
+ // Ignored the corrupted image.
+ }
+ }
+ }
+
+ private void addDefaultValuesForCompatibility() {
+ // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
+ String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
+ if (valueOfDateTimeOriginal != null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME,
+ ExifAttribute.createString(valueOfDateTimeOriginal));
+ }
+
+ // Add the default value.
+ if (getAttribute(TAG_IMAGE_WIDTH) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_IMAGE_LENGTH) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_ORIENTATION) == null) {
+ mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (getAttribute(TAG_LIGHT_SOURCE) == null) {
+ mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ }
+
+ // Reads image file directory, which is a tag group in EXIF.
+ private void readImageFileDirectory(ByteOrderAwarenessDataInputStream dataInputStream, int hint)
+ throws IOException {
+ if (dataInputStream.peek() + 2 > dataInputStream.mLength) {
+ // Return if there is no data from the offset.
+ return;
+ }
+ // See JEITA CP-3451 Figure 5. page 9.
+ short numberOfDirectoryEntry = dataInputStream.readShort();
+ if (dataInputStream.peek() + 12 * numberOfDirectoryEntry > dataInputStream.mLength) {
+ // Return if the size of entries is too big.
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
+ }
+
+ for (short i = 0; i < numberOfDirectoryEntry; ++i) {
+ int tagNumber = dataInputStream.readUnsignedShort();
+ int dataFormat = dataInputStream.readUnsignedShort();
+ int numberOfComponents = dataInputStream.readInt();
+ long nextEntryOffset = dataInputStream.peek() + 4; // next four bytes is for data
+ // offset or value.
+ // Look up a corresponding tag from tag number
+ final ExifTag tag = (ExifTag) sExifTagMapsForReading[hint].get(tagNumber);
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d, " +
+ "numberOfComponents: %d", hint, tagNumber, tag != null ? tag.name : null,
+ dataFormat, numberOfComponents));
+ }
+
+ if (tag == null || dataFormat <= 0 ||
+ dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
+ // Skip if the parsed tag number is not defined or invalid data format.
+ if (tag == null) {
+ Log.w(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber);
+ } else {
+ Log.w(TAG, "Skip the tag entry since data format is invalid: " + dataFormat);
+ }
+ dataInputStream.seek(nextEntryOffset);
+ continue;
+ }
+
+ // Read a value from data field or seek to the value offset which is stored in data
+ // field if the size of the entry value is bigger than 4.
+ int byteCount = numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
+ if (byteCount > 4) {
+ long offset = dataInputStream.readUnsignedInt();
+ if (DEBUG) {
+ Log.d(TAG, "seek to data offset: " + offset);
+ }
+ if (offset + byteCount <= dataInputStream.mLength) {
+ dataInputStream.seek(offset);
+ } else {
+ // Skip if invalid data offset.
+ Log.w(TAG, "Skip the tag entry since data offset is invalid: " + offset);
+ dataInputStream.seek(nextEntryOffset);
+ continue;
+ }
+ }
+
+ // Recursively parse IFD when a IFD pointer tag appears.
+ int innerIfdHint = getIfdHintFromTagNumber(tagNumber);
+ if (DEBUG) {
+ Log.d(TAG, "innerIfdHint: " + innerIfdHint + " byteCount: " + byteCount);
+ }
+
+ if (innerIfdHint >= 0) {
+ long offset = -1L;
+ // Get offset from data field
+ switch (dataFormat) {
+ case IFD_FORMAT_USHORT: {
+ offset = dataInputStream.readUnsignedShort();
+ break;
+ }
+ case IFD_FORMAT_SSHORT: {
+ offset = dataInputStream.readShort();
+ break;
+ }
+ case IFD_FORMAT_ULONG: {
+ offset = dataInputStream.readUnsignedInt();
+ break;
+ }
+ case IFD_FORMAT_SLONG: {
+ offset = dataInputStream.readInt();
+ break;
+ }
+ default: {
+ // Nothing to do
+ break;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name));
+ }
+ if (offset > 0L && offset < dataInputStream.mLength) {
+ dataInputStream.seek(offset);
+ readImageFileDirectory(dataInputStream, innerIfdHint);
+ } else {
+ Log.w(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
+ }
+
+ dataInputStream.seek(nextEntryOffset);
+ continue;
+ }
+
+ byte[] bytes = new byte[numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat]];
+ dataInputStream.readFully(bytes);
+ mAttributes[hint].put(
+ tag.name, new ExifAttribute(dataFormat, numberOfComponents, bytes));
+ if (dataInputStream.peek() != nextEntryOffset) {
+ dataInputStream.seek(nextEntryOffset);
+ }
+ }
+
+ if (dataInputStream.peek() + 4 <= dataInputStream.mLength) {
+ long nextIfdOffset = dataInputStream.readUnsignedInt();
+ if (DEBUG) {
+ Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
+ }
+ // The next IFD offset needs to be bigger than 8
+ // since the first IFD offset is at least 8.
+ if (nextIfdOffset > 8 && nextIfdOffset < dataInputStream.mLength) {
+ dataInputStream.seek(nextIfdOffset);
+ readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT);
+ }
+ }
+ }
+
+ // Gets the corresponding IFD group index of the given tag number for writing Exif Tags.
+ private static int getIfdHintFromTagNumber(int tagNumber) {
+ for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) {
+ if (IFD_POINTER_TAGS[i].number == tagNumber) {
+ return IFD_POINTER_TAG_HINTS[i];
+ }
+ }
+ return -1;
+ }
+
+ // Writes an Exif segment into the given output stream.
+ private int writeExifSegment(ByteOrderAwarenessDataOutputStream dataOutputStream,
+ int exifOffsetFromBeginning) throws IOException {
+ // The following variables are for calculating each IFD tag group size in bytes.
+ int[] ifdOffsets = new int[EXIF_TAGS.length];
+ int[] ifdDataSizes = new int[EXIF_TAGS.length];
+
+ // Remove IFD pointer tags (we'll re-add it later.)
+ for (ExifTag tag : IFD_POINTER_TAGS) {
+ removeAttribute(tag.name);
+ }
+ // Remove old thumbnail data
+ removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
+ removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
+
+ // Remove null value tags.
+ for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
+ for (Object obj : mAttributes[hint].entrySet().toArray()) {
+ final Map.Entry entry = (Map.Entry) obj;
+ if (entry.getValue() == null) {
+ mAttributes[hint].remove(entry.getKey());
+ }
+ }
+ }
+
+ // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
+ // offset when there is one or more tags in the thumbnail IFD.
+ if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
+ mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ }
+ if (mHasThumbnail) {
+ mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
+ ExifAttribute.createULong(0, mExifByteOrder));
+ mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
+ ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
+ }
+
+ // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
+ // value which has a bigger size than 4 bytes.
+ for (int i = 0; i < EXIF_TAGS.length; ++i) {
+ int sum = 0;
+ for (Map.Entry entry : (Set) mAttributes[i].entrySet()) {
+ final ExifAttribute exifAttribute = (ExifAttribute) entry.getValue();
+ final int size = exifAttribute.size();
+ if (size > 4) {
+ sum += size;
+ }
+ }
+ ifdDataSizes[i] += sum;
+ }
+
+ // Calculate IFD offsets.
+ int position = 8;
+ for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
+ if (!mAttributes[hint].isEmpty()) {
+ ifdOffsets[hint] = position;
+ position += 2 + mAttributes[hint].size() * 12 + 4 + ifdDataSizes[hint];
+ }
+ }
+ if (mHasThumbnail) {
+ int thumbnailOffset = position;
+ mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
+ ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
+ mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
+ position += mThumbnailLength;
+ }
+
+ // Calculate the total size
+ int totalSize = position + 8; // eight bytes is for header part.
+ if (DEBUG) {
+ Log.d(TAG, "totalSize length: " + totalSize);
+ for (int i = 0; i < EXIF_TAGS.length; ++i) {
+ Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d",
+ i, ifdOffsets[i], mAttributes[i].size(), ifdDataSizes[i]));
+ }
+ }
- private native long[] getThumbnailRangeNative(String fileName);
+ // Update IFD pointer tags with the calculated offsets.
+ if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name,
+ ExifAttribute.createULong(ifdOffsets[IFD_EXIF_HINT], mExifByteOrder));
+ }
+ if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
+ mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
+ ExifAttribute.createULong(ifdOffsets[IFD_GPS_HINT], mExifByteOrder));
+ }
+ if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
+ mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, ExifAttribute.createULong(
+ ifdOffsets[IFD_INTEROPERABILITY_HINT], mExifByteOrder));
+ }
+
+ // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10.
+ dataOutputStream.writeUnsignedShort(totalSize);
+ dataOutputStream.write(IDENTIFIER_EXIF_APP1);
+ dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN
+ ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
+ dataOutputStream.setByteOrder(mExifByteOrder);
+ dataOutputStream.writeUnsignedShort(0x2a);
+ dataOutputStream.writeUnsignedInt(8);
+
+ // Write IFD groups. See JEITA CP-3451C Figure 7. page 12.
+ for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
+ if (!mAttributes[hint].isEmpty()) {
+ // See JEITA CP-3451C 4.6.2 IFD structure. page 13.
+ // Write entry count
+ dataOutputStream.writeUnsignedShort(mAttributes[hint].size());
+
+ // Write entry info
+ int dataOffset = ifdOffsets[hint] + 2 + mAttributes[hint].size() * 12 + 4;
+ for (Map.Entry entry : (Set) mAttributes[hint].entrySet()) {
+ // Convert tag name to tag number.
+ final ExifTag tag = (ExifTag) sExifTagMapsForWriting[hint].get(entry.getKey());
+ final int tagNumber = tag.number;
+ final ExifAttribute attribute = (ExifAttribute) entry.getValue();
+ final int size = attribute.size();
+
+ dataOutputStream.writeUnsignedShort(tagNumber);
+ dataOutputStream.writeUnsignedShort(attribute.format);
+ dataOutputStream.writeInt(attribute.numberOfComponents);
+ if (size > 4) {
+ dataOutputStream.writeUnsignedInt(dataOffset);
+ dataOffset += size;
+ } else {
+ dataOutputStream.write(attribute.bytes);
+ // Fill zero up to 4 bytes
+ if (size < 4) {
+ for (int i = size; i < 4; ++i) {
+ dataOutputStream.writeByte(0);
+ }
+ }
+ }
+ }
+
+ // Write the next offset. It writes the offset of thumbnail IFD if there is one or
+ // more tags in the thumbnail IFD when the current IFD is the primary image TIFF
+ // IFD; Otherwise 0.
+ if (hint == 0 && !mAttributes[IFD_THUMBNAIL_HINT].isEmpty()) {
+ dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_THUMBNAIL_HINT]);
+ } else {
+ dataOutputStream.writeUnsignedInt(0);
+ }
+
+ // Write values of data field exceeding 4 bytes after the next offset.
+ for (Map.Entry entry : (Set) mAttributes[hint].entrySet()) {
+ ExifAttribute attribute = (ExifAttribute) entry.getValue();
+
+ if (attribute.bytes.length > 4) {
+ dataOutputStream.write(attribute.bytes, 0, attribute.bytes.length);
+ }
+ }
+ }
+ }
+
+ // Write thumbnail
+ if (mHasThumbnail) {
+ dataOutputStream.write(getThumbnail());
+ }
+
+ // Reset the byte order to big endian in order to write remaining parts of the JPEG file.
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+
+ return totalSize;
+ }
+
+ /**
+ * Determines the data format of EXIF entry value.
+ *
+ * @param entryValue The value to be determined.
+ * @return Returns two data formats gussed as a pair in integer. If there is no two candidate
+ data formats for the given entry value, returns {@code -1} in the second of the pair.
+ */
+ private static Pair guessDataFormat(String entryValue) {
+ // See TIFF 6.0 spec Types. page 15.
+ // Take the first component if there are more than one component.
+ if (entryValue.contains(",")) {
+ String[] entryValues = entryValue.split(",");
+ Pair dataFormat = guessDataFormat(entryValues[0]);
+ if (dataFormat.first == IFD_FORMAT_STRING) {
+ return dataFormat;
+ }
+ for (int i = 1; i < entryValues.length; ++i) {
+ final Pair guessDataFormat = guessDataFormat(entryValues[i]);
+ int first = -1, second = -1;
+ if (guessDataFormat.first == dataFormat.first
+ || guessDataFormat.second == dataFormat.first) {
+ first = dataFormat.first;
+ }
+ if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
+ || guessDataFormat.second == dataFormat.second)) {
+ second = dataFormat.second;
+ }
+ if (first == -1 && second == -1) {
+ return new Pair(IFD_FORMAT_STRING, -1);
+ }
+ if (first == -1) {
+ dataFormat = new Pair(second, -1);
+ continue;
+ }
+ if (second == -1) {
+ dataFormat = new Pair(first, -1);
+ continue;
+ }
+ }
+ return dataFormat;
+ }
+
+ if (entryValue.contains("/")) {
+ String[] rationalNumber = entryValue.split("/");
+ if (rationalNumber.length == 2) {
+ try {
+ long numerator = Long.parseLong(rationalNumber[0]);
+ long denominator = Long.parseLong(rationalNumber[1]);
+ if (numerator < 0L || denominator < 0L) {
+ return new Pair(IFD_FORMAT_SRATIONAL, - 1);
+ }
+ if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) {
+ return new Pair(IFD_FORMAT_URATIONAL, -1);
+ }
+ return new Pair(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL);
+ } catch (NumberFormatException e) {
+ // Ignored
+ }
+ }
+ return new Pair(IFD_FORMAT_STRING, -1);
+ }
+ try {
+ Long longValue = Long.parseLong(entryValue);
+ if (longValue >= 0 && longValue <= 65535) {
+ return new Pair(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG);
+ }
+ if (longValue < 0) {
+ return new Pair(IFD_FORMAT_SLONG, -1);
+ }
+ return new Pair(IFD_FORMAT_ULONG, -1);
+ } catch (NumberFormatException e) {
+ // Ignored
+ }
+ try {
+ Double.parseDouble(entryValue);
+ return new Pair(IFD_FORMAT_DOUBLE, -1);
+ } catch (NumberFormatException e) {
+ // Ignored
+ }
+ return new Pair(IFD_FORMAT_STRING, -1);
+ }
+
+ // An input stream to parse EXIF data area, which can be written in either little or big endian
+ // order.
+ private static class ByteOrderAwarenessDataInputStream extends ByteArrayInputStream {
+ private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
+ private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
+
+ private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
+ private final long mLength;
+ private long mPosition;
+
+ public ByteOrderAwarenessDataInputStream(byte[] bytes) {
+ super(bytes);
+ mLength = bytes.length;
+ mPosition = 0L;
+ }
+
+ public void setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ public void seek(long byteCount) throws IOException {
+ mPosition = 0L;
+ reset();
+ if (skip(byteCount) != byteCount) {
+ throw new IOException("Couldn't seek up to the byteCount");
+ }
+ }
+
+ public long peek() {
+ return mPosition;
+ }
+
+ public void readFully(byte[] buffer) throws IOException {
+ mPosition += buffer.length;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ if (super.read(buffer, 0, buffer.length) != buffer.length) {
+ throw new IOException("Couldn't read up to the length of buffer");
+ }
+ }
+
+ public byte readByte() throws IOException {
+ ++mPosition;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ int ch = super.read();
+ if (ch < 0) {
+ throw new EOFException();
+ }
+ return (byte) ch;
+ }
+
+ public short readShort() throws IOException {
+ mPosition += 2;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ int ch1 = super.read();
+ int ch2 = super.read();
+ if ((ch1 | ch2) < 0) {
+ throw new EOFException();
+ }
+ if (mByteOrder == LITTLE_ENDIAN) {
+ return (short) ((ch2 << 8) + (ch1));
+ } else if (mByteOrder == BIG_ENDIAN) {
+ return (short) ((ch1 << 8) + (ch2));
+ }
+ throw new IOException("Invalid byte order: " + mByteOrder);
+ }
+
+ public int readInt() throws IOException {
+ mPosition += 4;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ int ch1 = super.read();
+ int ch2 = super.read();
+ int ch3 = super.read();
+ int ch4 = super.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0) {
+ throw new EOFException();
+ }
+ if (mByteOrder == LITTLE_ENDIAN) {
+ return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
+ } else if (mByteOrder == BIG_ENDIAN) {
+ return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
+ }
+ throw new IOException("Invalid byte order: " + mByteOrder);
+ }
+
+ @Override
+ public long skip(long byteCount) {
+ long skipped = super.skip(Math.min(byteCount, mLength - mPosition));
+ mPosition += skipped;
+ return skipped;
+ }
+
+ public int readUnsignedShort() throws IOException {
+ mPosition += 2;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ int ch1 = super.read();
+ int ch2 = super.read();
+ if ((ch1 | ch2) < 0) {
+ throw new EOFException();
+ }
+ if (mByteOrder == LITTLE_ENDIAN) {
+ return ((ch2 << 8) + (ch1));
+ } else if (mByteOrder == BIG_ENDIAN) {
+ return ((ch1 << 8) + (ch2));
+ }
+ throw new IOException("Invalid byte order: " + mByteOrder);
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public long readLong() throws IOException {
+ mPosition += 8;
+ if (mPosition > mLength) {
+ throw new EOFException();
+ }
+ int ch1 = super.read();
+ int ch2 = super.read();
+ int ch3 = super.read();
+ int ch4 = super.read();
+ int ch5 = super.read();
+ int ch6 = super.read();
+ int ch7 = super.read();
+ int ch8 = super.read();
+ if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) {
+ throw new EOFException();
+ }
+ if (mByteOrder == LITTLE_ENDIAN) {
+ return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
+ + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
+ + ((long) ch2 << 8) + ch1);
+ } else if (mByteOrder == BIG_ENDIAN) {
+ return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
+ + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
+ + ((long) ch7 << 8) + ch8);
+ }
+ throw new IOException("Invalid byte order: " + mByteOrder);
+ }
+
+ public float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+ }
+
+ // An output stream to write EXIF data area, which can be written in either little or big endian
+ // order.
+ private static class ByteOrderAwarenessDataOutputStream extends FilterOutputStream {
+ private final OutputStream mOutputStream;
+ private ByteOrder mByteOrder;
+
+ public ByteOrderAwarenessDataOutputStream(OutputStream out, ByteOrder byteOrder) {
+ super(out);
+ mOutputStream = out;
+ mByteOrder = byteOrder;
+ }
+
+ public void setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ public void write(byte[] bytes) throws IOException {
+ mOutputStream.write(bytes);
+ }
+
+ public void write(byte[] bytes, int offset, int length) throws IOException {
+ mOutputStream.write(bytes, offset, length);
+ }
+
+ public void writeByte(int val) throws IOException {
+ mOutputStream.write(val);
+ }
+
+ public void writeShort(short val) throws IOException {
+ if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
+ mOutputStream.write((val >>> 0) & 0xFF);
+ mOutputStream.write((val >>> 8) & 0xFF);
+ } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
+ mOutputStream.write((val >>> 8) & 0xFF);
+ mOutputStream.write((val >>> 0) & 0xFF);
+ }
+ }
+
+ public void writeInt(int val) throws IOException {
+ if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
+ mOutputStream.write((val >>> 0) & 0xFF);
+ mOutputStream.write((val >>> 8) & 0xFF);
+ mOutputStream.write((val >>> 16) & 0xFF);
+ mOutputStream.write((val >>> 24) & 0xFF);
+ } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
+ mOutputStream.write((val >>> 24) & 0xFF);
+ mOutputStream.write((val >>> 16) & 0xFF);
+ mOutputStream.write((val >>> 8) & 0xFF);
+ mOutputStream.write((val >>> 0) & 0xFF);
+ }
+ }
+
+ public void writeUnsignedShort(int val) throws IOException {
+ writeShort((short) val);
+ }
+
+ public void writeUnsignedInt(long val) throws IOException {
+ writeInt((int) val);
+ }
+ }
}
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 344082591f7a..cc051461192c 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -40,6 +40,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.IBinder.DeathRecipient;
@@ -756,7 +757,13 @@ private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
synchronized(mRCStack) {
if ((mMediaReceiverForCalls != null) &&
(mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
- dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ // Prevent dispatching key event to the global priority session.
+ Slog.i(TAG, "Only the system can dispatch media key event "
+ + "to the global priority session.");
+ } else {
+ dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
+ }
return;
}
}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index dea971e4cfbc..a45051c93df3 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -40,9 +40,6 @@ LOCAL_SHARED_LIBRARIES := \
libexif \
libstagefright_amrnb_common \
-LOCAL_REQUIRED_MODULES := \
- libjhead_jni
-
LOCAL_STATIC_LIBRARIES := \
libstagefright_amrnbenc
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 0119b93ede41..bb022d424a2b 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -790,28 +790,12 @@ android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz)
static jobjectArray
android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz, jint audioSession)
{
- // kDefaultNumEffects is a "reasonable" value ensuring that only one query will be enough on
- // most devices to get all active audio pre processing on a given session.
- static const uint32_t kDefaultNumEffects = 5;
-
- effect_descriptor_t *descriptors = new effect_descriptor_t[kDefaultNumEffects];
- uint32_t numEffects = kDefaultNumEffects;
+ effect_descriptor_t *descriptors = new effect_descriptor_t[AudioEffect::kMaxPreProcessing];
+ uint32_t numEffects = AudioEffect::kMaxPreProcessing;
status_t status = AudioEffect::queryDefaultPreProcessing(audioSession,
descriptors,
&numEffects);
- if ((status != NO_ERROR && status != NO_MEMORY) ||
- numEffects == 0) {
- delete[] descriptors;
- return NULL;
- }
- if (status == NO_MEMORY) {
- delete [] descriptors;
- descriptors = new effect_descriptor_t[numEffects];
- status = AudioEffect::queryDefaultPreProcessing(audioSession,
- descriptors,
- &numEffects);
- }
if (status != NO_ERROR || numEffects == 0) {
delete[] descriptors;
return NULL;
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 559e05206eaa..4f096bc58a0d 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -34,6 +34,7 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
+import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -148,6 +149,9 @@ private void updateVolumesLocked() {
root.title = getContext().getString(R.string.root_internal_storage);
} else {
root.title = volume.getUserLabel();
+ if (TextUtils.isEmpty(root.title)) {
+ root.title = volume.getUuid();
+ }
}
root.docId = getDocIdForFile(path);
mRoots.add(root);
diff --git a/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_target_activated.png b/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_target_activated.png
new file mode 100644
index 000000000000..c5810bf39240
Binary files /dev/null and b/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_target_activated.png differ
diff --git a/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_target_activated.png b/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_target_activated.png
new file mode 100644
index 000000000000..f2a3109477f7
Binary files /dev/null and b/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_target_activated.png differ
diff --git a/packages/Keyguard/res/values-land/dimens.xml b/packages/Keyguard/res/values-land/dimens.xml
index bf30332f1ef5..02574f1c6b0a 100644
--- a/packages/Keyguard/res/values-land/dimens.xml
+++ b/packages/Keyguard/res/values-land/dimens.xml
@@ -19,6 +19,9 @@
-->
+
+ -40dp
+
30dp
diff --git a/packages/Keyguard/res/values-sw360dp-land/dimens.xml b/packages/Keyguard/res/values-sw360dp-land/dimens.xml
new file mode 100644
index 000000000000..7fa4b8118952
--- /dev/null
+++ b/packages/Keyguard/res/values-sw360dp-land/dimens.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ -30dp
+
+
diff --git a/packages/Keyguard/res/values-sw360dp-land/styles.xml b/packages/Keyguard/res/values-sw360dp-land/styles.xml
new file mode 100644
index 000000000000..3a2d218761ac
--- /dev/null
+++ b/packages/Keyguard/res/values-sw360dp-land/styles.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index ab45d99dc857..e38688dae0eb 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -7,4 +7,6 @@
public void setGlowScale(float);
}
+-keep class com.android.systemui.statusbar.BaseStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
+-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_dc.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_dc.png
new file mode 100644
index 000000000000..04ad347b6dfb
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_dc.png differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_dc.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_dc.png
new file mode 100644
index 000000000000..29691ac54ad7
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_dc.png differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_dc.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_dc.png
new file mode 100644
index 000000000000..218a0a16ae48
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_dc.png differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_dc.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_dc.png
new file mode 100644
index 000000000000..e9aec6ad8934
Binary files /dev/null and b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_dc.png differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_dc.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_dc.png
new file mode 100644
index 000000000000..192f89e275b2
Binary files /dev/null and b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_dc.png differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_dc.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_dc.png
new file mode 100644
index 000000000000..e494ce7bbe9e
Binary files /dev/null and b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_dc.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_dc.png
new file mode 100644
index 000000000000..e9b8243c8f21
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_dc.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_dc.png
new file mode 100644
index 000000000000..f5d8fde98e12
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_fully_connected_dc.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_fully_connected_dc.png
new file mode 100644
index 000000000000..037c1bd80fd4
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_fully_connected_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_dc.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_dc.png
new file mode 100644
index 000000000000..53dfb394f998
Binary files /dev/null and b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_dc.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_dc.png
new file mode 100644
index 000000000000..2c28328ed08d
Binary files /dev/null and b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_dc.png differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_fully_connected_dc.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_fully_connected_dc.png
new file mode 100644
index 000000000000..f78c62a188e1
Binary files /dev/null and b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_fully_connected_dc.png differ
diff --git a/packages/SystemUI/res/values/slim_strings.xml b/packages/SystemUI/res/values/slim_strings.xml
index 441a9acccc46..e42d05f7bafe 100644
--- a/packages/SystemUI/res/values/slim_strings.xml
+++ b/packages/SystemUI/res/values/slim_strings.xml
@@ -32,6 +32,9 @@
HSPA+
+
+ DC-HSPA+
+
Cancel
Ok
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e36ca8e6ae7c..5cba353531db 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -507,4 +507,9 @@
linebreak to position it correctly. [CHAR LIMIT=45] -->
Network may\nbe monitored
+
+ Because an app is obscuring a permission request, Settings
+ can’t verify your response.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5bb6b64c758d..fcfb61d079b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -223,6 +223,9 @@ private final void updateSimState(Intent intent) {
if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
mSimState = IccCardConstants.State.ABSENT;
}
+ else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ mSimState = IccCardConstants.State.CARD_IO_ERROR;
+ }
else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
mSimState = IccCardConstants.State.READY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 41de93bb9cb7..f74c20a5320c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -638,6 +638,13 @@ private final void updateDataNetType() {
mContentDescriptionDataType = mContext.getString(
R.string.accessibility_data_connection_HP);
break;
+ case TelephonyManager.NETWORK_TYPE_DCHSPAP:
+ mDataIconList = TelephonyIcons.DATA_DC[mInetCondition];
+ mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_dc;
+ mQSDataTypeIconId = TelephonyIcons.QS_DATA_DC[mInetCondition];
+ mContentDescriptionDataType = mContext.getString(
+ R.string.accessibility_data_connection_DC);
+ break;
case TelephonyManager.NETWORK_TYPE_CDMA:
if (!mShowAtLeastThreeGees) {
// display 1xRTT for IS95A/B
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index e6bf9237327b..d9d944297fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -154,6 +154,24 @@ class TelephonyIcons {
};
+ //DC-HPSPA+
+ static final int[][] DATA_DC = {
+ { R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc },
+ { R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc,
+ R.drawable.stat_sys_data_fully_connected_dc }
+ };
+
+ static final int[] QS_DATA_DC = {
+ R.drawable.ic_qs_signal_dc,
+ R.drawable.ic_qs_signal_full_dc
+
+ };
+
//CDMA
// Use 3G icons for EVDO data and 1x icons for 1XRTT data
static final int[][] DATA_1X = {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index 7abfc88c8552..b3d80efedcc9 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -31,8 +31,12 @@
import android.os.SystemProperties;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
import android.widget.CheckBox;
+import android.widget.Toast;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
@@ -48,6 +52,10 @@ public class UsbDebuggingActivity extends AlertActivity
@Override
public void onCreate(Bundle icicle) {
+ Window window = getWindow();
+ window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
super.onCreate(icicle);
if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
@@ -80,6 +88,24 @@ public void onCreate(Bundle icicle) {
ap.mView = checkbox;
setupAlert();
+
+ // adding touch listener on affirmative button - checks if window is obscured
+ // if obscured, do not let user give permissions (could be tapjacking involved)
+ final View.OnTouchListener filterTouchListener = new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ // Filter obscured touches by consuming them.
+ if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ Toast.makeText(v.getContext(),
+ R.string.touch_filtered_warning,
+ Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ mAlert.getButton(BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
}
private class UsbDisconnectedReceiver extends BroadcastReceiver {
diff --git a/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java b/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java
index 96e037797b98..e9703670008d 100644
--- a/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java
+++ b/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java
@@ -117,14 +117,18 @@ protected class queryData {
*/
protected queryData queryLastApp(SQLiteDatabase db,
String app_id, String content_type) {
- String sql = "select install_order, package_name, class_name, "
- + " app_type, need_signature, further_processing"
- + " from " + APPID_TABLE_NAME
- + " where x_wap_application=\'" + app_id + "\'"
- + " and content_type=\'" + content_type + "\'"
- + " order by install_order desc";
- if (DEBUG_SQL) Log.v(LOG_TAG, "sql: " + sql);
- Cursor cur = db.rawQuery(sql, null);
+ if (LOCAL_LOGV) Log.v(LOG_TAG, "queryLastApp app_id: " + app_id
+ + " content_type: " + content_type);
+
+ Cursor cur = db.query(APPID_TABLE_NAME,
+ new String[] {"install_order", "package_name", "class_name",
+ "app_type", "need_signature", "further_processing"},
+ "x_wap_application=? and content_type=?",
+ new String[] {app_id, content_type},
+ null /* groupBy */,
+ null /* having */,
+ "install_order desc" /* orderBy */);
+
queryData ret = null;
if (cur.moveToNext()) {
@@ -392,10 +396,20 @@ public boolean verifyData(String x_app_id, String content_type,
SQLiteDatabase db = dbh.getReadableDatabase();
WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
+ if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData app id: " + x_app_id + " content type: " +
+ content_type + " lastapp: " + lastapp);
+
db.close();
if (lastapp == null) return false;
+ if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData lastapp.packageName: " + lastapp.packageName +
+ " lastapp.className: " + lastapp.className +
+ " lastapp.appType: " + lastapp.appType +
+ " lastapp.needSignature: " + lastapp.needSignature +
+ " lastapp.furtherProcessing: " + lastapp.furtherProcessing);
+
+
if (lastapp.packageName.equals(package_name)
&& lastapp.className.equals(class_name)
&& lastapp.appType == app_type
diff --git a/packages/WAPPushManager/tests/src/com/android/smspush/unitTests/WapPushTest.java b/packages/WAPPushManager/tests/src/com/android/smspush/unitTests/WapPushTest.java
index 305ee3788303..f7afc57f3199 100644
--- a/packages/WAPPushManager/tests/src/com/android/smspush/unitTests/WapPushTest.java
+++ b/packages/WAPPushManager/tests/src/com/android/smspush/unitTests/WapPushTest.java
@@ -551,6 +551,25 @@ public void testAddPackage1() {
mContentTypeValue = originalContentTypeValue;
}
+ /**
+ * Add sqlite injection test
+ */
+ public void testAddPackage0() {
+ String inject = "' union select 0,'com.android.settings','com.android.settings.Settings',0,0,0--";
+
+ // insert new data
+ IWapPushManager iwapman = getInterface();
+ try {
+ assertFalse(iwapman.addPackage(
+ inject,
+ Integer.toString(mContentTypeValue),
+ mPackageName, mClassName,
+ WapPushManagerParams.APP_TYPE_SERVICE, true, true));
+ } catch (RemoteException e) {
+ assertTrue(false);
+ }
+ }
+
/**
* Add duprecated package test.
*/
@@ -1477,7 +1496,7 @@ protected byte[] createPDU(int testNum) {
System.arraycopy(mWspHeader, 0, array,
mGsmHeader.length + mUserDataHeader.length, mWspHeader.length);
System.arraycopy(mMessageBody, 0, array,
- mGsmHeader.length + mUserDataHeader.length + mWspHeader.length,
+ mGsmHeader.length + mUserDataHeader.length + mWspHeader.length,
mMessageBody.length);
return array;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 8c3eb42a6d48..cb2ae0bd997b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -290,6 +290,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mUiMode;
int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
int mLidOpenRotation;
+ boolean mHasRemovableLid;
int mCarDockRotation;
int mDeskDockRotation;
int mUndockedHdmiRotation;
@@ -1079,6 +1080,8 @@ public void init(Context context, IWindowManager windowManager,
com.android.internal.R.bool.config_lidControlsSleep);
mTranslucentDecorEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableTranslucentDecor);
+ mHasRemovableLid = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hasRemovableLid);
// register for dock events
IntentFilter filter = new IntentFilter();
@@ -4804,6 +4807,18 @@ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean i
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ ITelephony telephonyService = getTelephonyService();
+ if (telephonyService != null) {
+ try {
+ if (!telephonyService.isIdle()) {
+ // When phone is ringing or in-call, pass all media keys to it.
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "ITelephony threw RemoteException", ex);
+ }
+ }
+
if ((result & ACTION_PASS_TO_USER) == 0) {
// Only do this if we would otherwise not pass it to the user. In that
// case, the PhoneWindow class will do the same thing, except it will
@@ -5229,8 +5244,11 @@ public int rotationForOrientationLw(int orientation, int lastRotation) {
}
final int preferredRotation;
- if (mLidState == LID_OPEN && mLidOpenRotation >= 0) {
- // Ignore sensor when lid switch is open and rotation is forced.
+ if ((mLidState == LID_OPEN && mLidOpenRotation >= 0)
+ && !(mHasRemovableLid
+ && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED)) {
+ // Ignore sensor when lid switch is open and rotation is forced
+ // and a removable lid was not undocked.
preferredRotation = mLidOpenRotation;
} else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR
&& (mCarDockEnablesAccelerometer || mCarDockRotation >= 0)) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 26fd0a412827..e32a7c77a60a 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -1780,7 +1780,8 @@ IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
mConnecting = true;
mConnectedAgent = null;
try {
- if (mActivityManager.bindBackupAgent(app, mode)) {
+ if (mActivityManager.bindBackupAgent(app.packageName, mode,
+ UserHandle.USER_OWNER)) {
Slog.d(TAG, "awaiting agent for " + app);
// success; wait for the agent to arrive
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index da1b2548238f..bce85cee338b 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -120,6 +120,8 @@ private void logBootEvents(Context ctx) throws IOException {
// Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
-LOG_SIZE, "SYSTEM_LAST_KMSG");
+ addFileToDropBox(db, prefs, headers, "/sys/fs/pstore/console-ramoops",
+ -LOG_SIZE, "SYSTEM_LAST_KMSG");
addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
-LOG_SIZE, "SYSTEM_RECOVERY_LOG");
addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
@@ -184,6 +186,11 @@ private static void addAuditErrorsToDropBox(DropBoxManager db, SharedPreference
File file = new File("/proc/last_kmsg");
long fileTime = file.lastModified();
+ if (fileTime <= 0) {
+ file = new File("/sys/fs/pstore/console-ramoops");
+ fileTime = file.lastModified();
+ }
+
if (fileTime <= 0) return; // File does not exist
if (prefs != null) {
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 069ae23f5596..3ce9a1fb4d2e 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -20,6 +20,8 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
+import android.app.KeyguardManager;
+import android.os.PowerManager;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -191,8 +193,8 @@ public void setPrimaryClip(ClipData clip, String callingPackage) {
public ClipData getPrimaryClip(String pkg) {
synchronized (this) {
- if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- pkg) != AppOpsManager.MODE_ALLOWED) {
+ if ((mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ pkg) != AppOpsManager.MODE_ALLOWED) || isDeviceLocked()) {
return null;
}
addActiveOwnerLocked(Binder.getCallingUid(), pkg);
@@ -202,8 +204,8 @@ public ClipData getPrimaryClip(String pkg) {
public ClipDescription getPrimaryClipDescription(String callingPackage) {
synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ if ((mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) || isDeviceLocked()) {
return null;
}
PerUserClipboard clipboard = getClipboard();
@@ -213,8 +215,8 @@ public ClipDescription getPrimaryClipDescription(String callingPackage) {
public boolean hasPrimaryClip(String callingPackage) {
synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ if ((mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) || isDeviceLocked()) {
return false;
}
return getClipboard().primaryClip != null;
@@ -237,8 +239,8 @@ public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener liste
public boolean hasClipboardText(String callingPackage) {
synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ if ((mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) || isDeviceLocked()) {
return false;
}
PerUserClipboard clipboard = getClipboard();
@@ -250,6 +252,26 @@ public boolean hasClipboardText(String callingPackage) {
}
}
+ private boolean isDeviceLocked() {
+ boolean isLocked = false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final KeyguardManager keyguardManager = (KeyguardManager) mContext.getSystemService(
+ Context.KEYGUARD_SERVICE);
+ boolean inKeyguardRestrictedInputMode = keyguardManager.inKeyguardRestrictedInputMode();
+ if (inKeyguardRestrictedInputMode) {
+ isLocked = true;
+ } else {
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(
+ Context.POWER_SERVICE);
+ isLocked = !powerManager.isScreenOn();
+ }
+ return isLocked;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private final void checkUriOwnerLocked(Uri uri, int uid) {
if (!"content".equals(uri.getScheme())) {
return;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index e045640e3085..ae1f3185b74b 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -2558,10 +2558,8 @@ void buildInputMethodListLocked(ArrayList list,
Slog.d(TAG, "Found an input method " + p);
}
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Unable to load input method " + compName, e);
- } catch (IOException e) {
- Slog.w(TAG, "Unable to load input method " + compName, e);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to load input method " + compName, e);
}
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index ad9a552293e7..3d8f509dab58 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -58,6 +58,8 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import com.android.internal.content.PackageMonitor;
@@ -2376,9 +2378,22 @@ public void setTestProviderLocation(String provider, Location loc) {
if (mockProvider == null) {
throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
}
+
+ // Ensure that the location is marked as being mock. There's some logic to do this in
+ // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107).
+ Location mock = new Location(loc);
+ mock.setIsFromMockProvider(true);
+
+ if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) {
+ // The location has an explicit provider that is different from the mock provider
+ // name. The caller may be trying to fool us via bug 33091107.
+ EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+ provider + "!=" + loc.getProvider());
+ }
+
// clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
long identity = Binder.clearCallingIdentity();
- mockProvider.setLocation(loc);
+ mockProvider.setLocation(mock);
Binder.restoreCallingIdentity(identity);
}
}
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index e3be65514b5b..2f0984bfdd51 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -2189,6 +2189,9 @@ public void onResult(Bundle result) {
Intent intent = null;
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+ intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
/*
* The Authenticator API allows third party authenticators to
* supply arbitrary intents to other apps that they can run,
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 558a41b8b517..38a17003c395 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -788,6 +788,7 @@ private class Identity {
* For example, references to the commonly used services.
*/
HashMap mAppBindArgs;
+ HashMap mIsolatedAppBindArgs;
/**
* Temporary to avoid allocations. Protected by main lock.
@@ -2281,7 +2282,17 @@ public void batteryPowerChanged(boolean onBattery) {
* process when the bindApplication() IPC is sent to the process. They're
* lazily setup to make sure the services are running when they're asked for.
*/
- private HashMap getCommonServicesLocked() {
+ private HashMap getCommonServicesLocked(boolean isolated) {
+ // Isolated processes won't get this optimization, so that we don't
+ // violate the rules about which services they have access to.
+ if (isolated) {
+ if (mIsolatedAppBindArgs == null) {
+ mIsolatedAppBindArgs = new HashMap();
+ mIsolatedAppBindArgs.put("package", ServiceManager.getService("package"));
+ }
+ return mIsolatedAppBindArgs;
+ }
+
if (mAppBindArgs == null) {
mAppBindArgs = new HashMap();
@@ -2889,6 +2900,19 @@ private final void startProcessLocked(ProcessRecord app,
app.setPid(startResult.pid);
app.usingWrapper = startResult.usingWrapper;
app.removed = false;
+ app.killedByAm = false;
+ ProcessRecord oldApp;
+ synchronized (mPidsSelfLocked) {
+ oldApp = mPidsSelfLocked.get(startResult.pid);
+ }
+ // If there is already an app occupying that pid that hasn't been cleaned up
+ if (oldApp != null && !app.isolated) {
+ // Clean up anything relating to this pid first
+ Slog.w(TAG, "Reusing pid " + startResult.pid
+ + " while app is still mapped to it");
+ cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+ true /*replacingPid*/);
+ }
synchronized (mPidsSelfLocked) {
this.mPidsSelfLocked.put(startResult.pid, app);
Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
@@ -3679,7 +3703,8 @@ public void overridePendingTransition(IBinder token, String packageName,
*/
private final void handleAppDiedLocked(ProcessRecord app,
boolean restarting, boolean allowRestart) {
- cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
+ cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,
+ false /*replacingPid*/);
if (!restarting) {
removeLruProcessLocked(app);
}
@@ -5048,7 +5073,8 @@ private final boolean attachApplicationLocked(IApplicationThread thread,
app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
- new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
+ new Configuration(mConfiguration), app.compat,
+ getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, null);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
@@ -6041,6 +6067,19 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
return -1;
}
+ // Bail early if system is trying to hand out permissions directly; it
+ // must always grant permissions on behalf of someone explicit.
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ if (callingAppId == Process.SYSTEM_UID) {
+ if ("com.android.settings.files".equals(uri.getAuthority())) {
+ // Exempted authority for cropping user photos in Settings app
+ } else {
+ Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission" +
+ " grant to " + uri.getAuthority() + "; use startActivityAsCaller() instead");
+ return -1;
+ }
+ }
+
final String authority = uri.getAuthority();
final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid));
if (pi == null) {
@@ -6062,13 +6101,26 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
}
}
+ // Figure out the value returned when access is allowed
+ final int allowedResult;
+ if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) {
+ // If we're extending a persistable grant, then we need to return
+ // "targetUid" so that we always create a grant data structure to
+ // support take/release APIs
+ allowedResult = targetUid;
+ } else {
+ // Otherwise, we can return "-1" to indicate that no grant data
+ // structures need to be created
+ allowedResult = -1;
+ }
+
if (targetUid >= 0) {
// First... does the target actually need this permission?
if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
// No need to grant the target this permission.
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Target " + targetPkg + " already has full permission to " + uri);
- return -1;
+ return allowedResult;
}
} else {
// First... there is no target package, so can anyone access it?
@@ -6084,7 +6136,7 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
}
}
if (allowed) {
- return -1;
+ return allowedResult;
}
}
@@ -12494,7 +12546,8 @@ private final boolean removeDyingProviderLocked(ProcessRecord proc,
* a process when running in single process mode.
*/
private final void cleanUpApplicationRecordLocked(ProcessRecord app,
- boolean restarting, boolean allowRestart, int index) {
+ boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
+ Slog.d(TAG, "cleanUpApplicationRecordLocked -- " + app.pid);
if (index >= 0) {
removeLruProcessLocked(app);
}
@@ -12618,8 +12671,10 @@ private final void cleanUpApplicationRecordLocked(ProcessRecord app,
if (!app.persistent || app.isolated) {
if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG,
"Removing non-persistent process during cleanup: " + app);
- mProcessNames.remove(app.processName, app.uid);
- mIsolatedProcesses.remove(app.uid);
+ if (!replacingPid) {
+ mProcessNames.remove(app.processName, app.uid);
+ mIsolatedProcesses.remove(app.uid);
+ }
if (mHeavyWeightProcess == app) {
mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
mHeavyWeightProcess.userId, 0));
@@ -12922,10 +12977,22 @@ public void serviceDoneExecuting(IBinder token, int type, int startId, int res)
// Cause the target app to be launched if necessary and its backup agent
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
- public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
- if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
+ public boolean bindBackupAgent(String packageName, int backupMode, int userId) {
+ if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode);
enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent");
+ IPackageManager pm = AppGlobals.getPackageManager();
+ ApplicationInfo app = null;
+ try {
+ app = pm.getApplicationInfo(packageName, 0, userId);
+ } catch (RemoteException e) {
+ // can't happen; package manager is process-local
+ }
+ if (app == null) {
+ Slog.w(TAG, "Unable to bind backup agent for " + packageName);
+ return false;
+ }
+
synchronized(this) {
// !!! TODO: currently no check here that we're already bound
BatteryStatsImpl.Uid.Pkg.Serv ss = null;
@@ -15905,7 +15972,7 @@ final void trimApplications() {
// Ignore exceptions.
}
}
- cleanUpApplicationRecordLocked(app, false, true, -1);
+ cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
mRemovedProcesses.remove(i);
if (app.persistent) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index d87828ed0d53..33ae0c8e1fdd 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -260,6 +260,11 @@ public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
final BroadcastRecord br = mPendingBroadcast;
if (br != null && br.curApp.pid == app.pid) {
+ if (br.curApp != app) {
+ Slog.e(TAG, "App mismatch when sending pending broadcast to "
+ + app.processName + ", intended target is " + br.curApp.processName);
+ return false;
+ }
try {
mPendingBroadcast = null;
processCurBroadcastLocked(br, app);
diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java
index 7786fe6e4254..b740674568a0 100644
--- a/services/java/com/android/server/connectivity/PacManager.java
+++ b/services/java/com/android/server/connectivity/PacManager.java
@@ -28,6 +28,7 @@
import android.net.ProxyProperties;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -42,10 +43,10 @@
import com.android.net.IProxyCallback;
import com.android.net.IProxyPortListener;
import com.android.net.IProxyService;
-import com.android.server.IoThread;
import libcore.io.Streams;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
@@ -69,6 +70,7 @@ public class PacManager {
private static final int DELAY_1 = 0;
private static final int DELAY_4 = 3;
private static final int DELAY_LONG = 4;
+ private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
/** Keep these values up-to-date with ProxyService.java */
public static final String KEY_PROXY = "keyProxy";
@@ -126,15 +128,21 @@ public void run() {
}
};
+ private final HandlerThread mNetThread = new HandlerThread("android.pacmanager",
+ android.os.Process.THREAD_PRIORITY_DEFAULT);
+ private final Handler mNetThreadHandler;
+
class PacRefreshIntentReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
- IoThread.getHandler().post(mPacDownloader);
+ mNetThreadHandler.post(mPacDownloader);
}
}
public PacManager(Context context, Handler handler, int proxyMessage) {
mContext = context;
mLastPort = -1;
+ mNetThread.start();
+ mNetThreadHandler = new Handler(mNetThread.getLooper());
mPacRefreshIntent = PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_PAC_REFRESH), 0);
@@ -202,7 +210,25 @@ public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) {
private static String get(String urlString) throws IOException {
URL url = new URL(urlString);
URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
- return new String(Streams.readFully(urlConnection.getInputStream()));
+ long contentLength = -1;
+ try {
+ contentLength = Long.parseLong(urlConnection.getHeaderField("Content-Length"));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ if (contentLength > MAX_PAC_SIZE) {
+ throw new IOException("PAC too big: " + contentLength + " bytes");
+ }
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = urlConnection.getInputStream().read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ if (bytes.size() > MAX_PAC_SIZE) {
+ throw new IOException("PAC too big");
+ }
+ }
+ return bytes.toString();
}
private int getNextDelay(int currentDelay) {
@@ -305,7 +331,7 @@ public void onServiceConnected(ComponentName component, IBinder binder) {
} catch (RemoteException e) {
Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
}
- IoThread.getHandler().post(mPacDownloader);
+ mNetThreadHandler.post(mPacDownloader);
}
}
}
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index a55b3ef8c595..2851e4cc531f 100755
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -28,6 +28,7 @@
import android.content.SyncInfo;
import android.content.SyncRequest;
import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
import android.database.IContentObserver;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
@@ -653,13 +654,26 @@ public boolean isSyncActive(Account account, String authority) {
}
public List getCurrentSyncs() {
+ return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ public List getCurrentSyncsAsUser(int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync settings for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
+ final boolean canAccessAccounts =
+ mContext.checkCallingOrSelfPermission(Manifest.permission.GET_ACCOUNTS)
+ == PackageManager.PERMISSION_GRANTED;
long identityToken = clearCallingIdentity();
try {
- return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
+ return getSyncManager().getSyncStorageEngine()
+ .getCurrentSyncsCopy(userId, canAccessAccounts);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -733,6 +747,21 @@ public static ContentService main(Context context, boolean factoryTest) {
return service;
}
+ /**
+ * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
+ * permission, if the userHandle is not for the caller.
+ *
+ * @param userHandle the user handle of the user we want to act on behalf of.
+ * @param message the message to log on security exception.
+ */
+ private void enforceCrossUserPermission(int userHandle, String message) {
+ final int callingUser = UserHandle.getCallingUserId();
+ if (callingUser != userHandle) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ }
+ }
+
/**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index 178c3723817c..e3a35887f250 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -41,6 +41,7 @@
import android.util.Pair;
import android.util.SparseArray;
import android.util.Xml;
+import android.util.EventLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -1306,15 +1307,23 @@ private List getCurrentSyncs(int userId) {
}
/**
- * @return a copy of the current syncs data structure. Will not return
- * null.
+ * @param userId Id of user to return current sync info.
+ * @param canAccessAccounts Determines whether to redact Account information from the result.
+ * @return a copy of the current syncs data structure. Will not return null.
*/
- public List getCurrentSyncsCopy(int userId) {
+ public List getCurrentSyncsCopy(int userId, boolean canAccessAccounts) {
synchronized (mAuthorities) {
final List syncs = getCurrentSyncsLocked(userId);
final List syncsCopy = new ArrayList();
for (SyncInfo sync : syncs) {
- syncsCopy.add(new SyncInfo(sync));
+ SyncInfo copy;
+ if (!canAccessAccounts) {
+ copy = SyncInfo.createAccountRedacted(
+ sync.authorityId, sync.authority, sync.startTime);
+ } else {
+ copy = new SyncInfo(sync);
+ }
+ syncsCopy.add(copy);
}
return syncsCopy;
}
@@ -1744,8 +1753,13 @@ private void readAccountInfoLocked() {
if ("authority".equals(tagName)) {
authority = parseAuthority(parser, version);
periodicSync = null;
- if (authority.ident > highestAuthorityId) {
- highestAuthorityId = authority.ident;
+ if (authority != null) {
+ if (authority.ident > highestAuthorityId) {
+ highestAuthorityId = authority.ident;
+ }
+ } else {
+ EventLog.writeEvent(0x534e4554, "26513719", -1,
+ "Malformed authority");
}
} else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
parseListenForTickles(parser);
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 3f97f15fa0fb..18f281c903a8 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -143,7 +143,6 @@ public class InputManagerService extends IInputManager.Stub
// State for the currently installed input filter.
final Object mInputFilterLock = new Object();
- IInputFilter mInputFilter; // guarded by mInputFilterLock
ChainedInputFilterHost mInputFilterHost; // guarded by mInputFilterLock
ArrayList mInputFilterChain =
new ArrayList(); // guarded by mInputFilterLock
@@ -481,20 +480,10 @@ public void unregisterInputChannel(InputChannel inputChannel) {
*/
public void setInputFilter(IInputFilter filter) {
synchronized (mInputFilterLock) {
- final IInputFilter oldFilter = mInputFilter;
- if (oldFilter == filter) {
- return; // nothing to do
- }
-
- if (oldFilter != null) {
+ if (mInputFilterHost != null) {
mInputFilterHost.disconnectLocked();
mInputFilterChain.remove(mInputFilterHost);
mInputFilterHost = null;
- try {
- oldFilter.uninstall();
- } catch (RemoteException re) {
- /* ignore */
- }
}
if (filter != null) {
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index a4447201e34b..5242957e6f4f 100644
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -135,7 +135,6 @@ public class GpsLocationProvider implements LocationProviderInterface {
private static final int LOCATION_HAS_ACCURACY = 16;
// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h
-// and gps_extended_c.h
private static final int GPS_DELETE_EPHEMERIS = 0x00000001;
private static final int GPS_DELETE_ALMANAC = 0x00000002;
private static final int GPS_DELETE_POSITION = 0x00000004;
@@ -150,19 +149,19 @@ public class GpsLocationProvider implements LocationProviderInterface {
private static final int GPS_DELETE_CELLDB_INFO = 0x00000800;
private static final int GPS_DELETE_ALMANAC_CORR = 0x00001000;
private static final int GPS_DELETE_FREQ_BIAS_EST = 0x00002000;
- private static final int GPS_DELETE_EPHEMERIS_GLO = 0x00004000;
- private static final int GPS_DELETE_ALMANAC_GLO = 0x00008000;
- private static final int GPS_DELETE_SVDIR_GLO = 0x00010000;
- private static final int GPS_DELETE_SVSTEER_GLO = 0x00020000;
- private static final int GPS_DELETE_ALMANAC_CORR_GLO = 0x00040000;
+ private static final int GLO_DELETE_EPHEMERIS = 0x00004000;
+ private static final int GLO_DELETE_ALMANAC = 0x00008000;
+ private static final int GLO_DELETE_SVDIR = 0x00010000;
+ private static final int GLO_DELETE_SVSTEER = 0x00020000;
+ private static final int GLO_DELETE_ALMANAC_CORR = 0x00040000;
private static final int GPS_DELETE_TIME_GPS = 0x00080000;
- private static final int GPS_DELETE_TIME_GLO = 0x00100000;
- private static final int GPS_DELETE_SVDIR_BDS = 0X00200000;
- private static final int GPS_DELETE_SVSTEER_BDS = 0X00400000;
- private static final int GPS_DELETE_TIME_BDS = 0X00800000;
- private static final int GPS_DELETE_ALMANAC_CORR_BDS = 0X01000000;
- private static final int GPS_DELETE_EPHEMERIS_BDS = 0X02000000;
- private static final int GPS_DELETE_ALMANAC_BDS = 0X04000000;
+ private static final int GLO_DELETE_TIME = 0x00100000;
+ private static final int BDS_DELETE_SVDIR = 0X00200000;
+ private static final int BDS_DELETE_SVSTEER = 0X00400000;
+ private static final int BDS_DELETE_TIME = 0X00800000;
+ private static final int BDS_DELETE_ALMANAC_CORR = 0X01000000;
+ private static final int BDS_DELETE_EPHEMERIS = 0X02000000;
+ private static final int BDS_DELETE_ALMANAC = 0X04000000;
private static final int GPS_DELETE_ALL = 0xFFFFFFFF;
// The GPS_CAPABILITY_* flags must match the values in gps.h
@@ -1055,6 +1054,21 @@ private boolean deleteAidingData(Bundle extras) {
if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
+ if (extras.getBoolean("almanac-corr")) flags |= GPS_DELETE_ALMANAC_CORR;
+ if (extras.getBoolean("freq-bias-est")) flags |= GPS_DELETE_FREQ_BIAS_EST;
+ if (extras.getBoolean("ephemeris-GLO")) flags |= GLO_DELETE_EPHEMERIS;
+ if (extras.getBoolean("almanac-GLO")) flags |= GLO_DELETE_ALMANAC;
+ if (extras.getBoolean("svdir-GLO")) flags |= GLO_DELETE_SVDIR;
+ if (extras.getBoolean("svsteer-GLO")) flags |= GLO_DELETE_SVSTEER;
+ if (extras.getBoolean("almanac-corr-GLO")) flags |= GLO_DELETE_ALMANAC_CORR;
+ if (extras.getBoolean("time-gps")) flags |= GPS_DELETE_TIME_GPS;
+ if (extras.getBoolean("time-GLO")) flags |= GLO_DELETE_TIME;
+ if (extras.getBoolean("ephemeris-BDS")) flags |= BDS_DELETE_EPHEMERIS;
+ if (extras.getBoolean("almanac-BDS")) flags |= BDS_DELETE_ALMANAC;
+ if (extras.getBoolean("svdir-BDS")) flags |= BDS_DELETE_SVDIR;
+ if (extras.getBoolean("svsteer-BDS")) flags |= BDS_DELETE_SVSTEER;
+ if (extras.getBoolean("almanac-corr-BDS")) flags |= BDS_DELETE_ALMANAC_CORR;
+ if (extras.getBoolean("time-BDS")) flags |= BDS_DELETE_TIME;
if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
}
@@ -1770,7 +1784,8 @@ private void requestRefLocation(int flags) {
|| networkType == TelephonyManager.NETWORK_TYPE_HSDPA
|| networkType == TelephonyManager.NETWORK_TYPE_HSUPA
|| networkType == TelephonyManager.NETWORK_TYPE_HSPA
- || networkType == TelephonyManager.NETWORK_TYPE_HSPAP) {
+ || networkType == TelephonyManager.NETWORK_TYPE_HSPAP
+ || networkType == TelephonyManager.NETWORK_TYPE_DCHSPAP) {
type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
} else {
type = AGPS_REF_LOCATION_TYPE_GSM_CELLID;
diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java
index e4200736fd8e..fdd9c491fd8f 100644
--- a/services/java/com/android/server/location/GpsXtraDownloader.java
+++ b/services/java/com/android/server/location/GpsXtraDownloader.java
@@ -44,6 +44,7 @@ public class GpsXtraDownloader {
private static final String TAG = "GpsXtraDownloader";
static final boolean DEBUG = false;
+ private static final long MAXIMUM_CONTENT_LENGTH_BYTES = 1000000; // 1MB.
private Context mContext;
private String[] mXtraServers;
@@ -138,8 +139,9 @@ protected static byte[] doDownload(String url, boolean isProxySet,
byte[] body = null;
if (entity != null) {
try {
- if (entity.getContentLength() > 0) {
- body = new byte[(int) entity.getContentLength()];
+ long contentLength = entity.getContentLength();
+ if (contentLength > 0 && contentLength <= MAXIMUM_CONTENT_LENGTH_BYTES) {
+ body = new byte[(int) contentLength];
DataInputStream dis = new DataInputStream(entity.getContent());
try {
dis.readFully(body);
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index adc38cadbbbb..b1eb14b34e05 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -396,7 +396,7 @@ public int deleteCacheFiles(String name, int userId) {
return execute(builder.toString());
}
- public int createUserData(String name, int uid, int userId) {
+ public int createUserData(String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
builder.append(name);
@@ -404,6 +404,8 @@ public int createUserData(String name, int uid, int userId) {
builder.append(uid);
builder.append(' ');
builder.append(userId);
+ builder.append(' ');
+ builder.append(seinfo != null ? seinfo : "!");
return execute(builder.toString());
}
@@ -501,4 +503,8 @@ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath, int
return execute(builder.toString());
}
+
+ public boolean restoreconData() {
+ return (execute("restorecondata") == 0);
+ }
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 9558d67cb204..6acd4ae4abc9 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1534,6 +1534,13 @@ public void run() {
// can downgrade to reader
mSettings.writeLPr();
+ if (SELinuxMMAC.shouldRestorecon()) {
+ Slog.i(TAG, "Relabeling of /data/data and /data/user issued.");
+ if (mInstaller.restoreconData()) {
+ SELinuxMMAC.setRestoreconDone();
+ }
+ }
+
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
@@ -4102,7 +4109,7 @@ private int createDataDirsLI(String packageName, int uid, String seinfo) {
for (int user : users) {
if (user != 0) {
res = mInstaller.createUserData(packageName,
- UserHandle.getUid(user, uid), user);
+ UserHandle.getUid(user, uid), user, seinfo);
if (res < 0) {
return res;
}
diff --git a/services/java/com/android/server/pm/SELinuxMMAC.java b/services/java/com/android/server/pm/SELinuxMMAC.java
index 04f43d9676f7..91de6d80ca8b 100644
--- a/services/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/java/com/android/server/pm/SELinuxMMAC.java
@@ -25,11 +25,16 @@
import com.android.internal.util.XmlUtils;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
@@ -48,12 +53,11 @@ public final class SELinuxMMAC {
private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
// Signature seinfo values read from policy.
- private static final HashMap sSigSeinfo =
- new HashMap();
+ private static HashMap sSigSeinfo =
+ new HashMap();
- // Package name seinfo values read from policy.
- private static final HashMap sPackageSeinfo =
- new HashMap();
+ // Default seinfo read from policy.
+ private static String sDefaultSeinfo = null;
// Locations of potential install policy files.
private static final File[] INSTALL_POLICY_FILE = {
@@ -61,9 +65,52 @@ public final class SELinuxMMAC {
new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
null};
+ // Location of seapp_contexts policy file.
+ private static final String SEAPP_CONTEXTS_FILE = "/seapp_contexts";
+
+ // Stores the hash of the last used seapp_contexts file.
+ private static final String SEAPP_HASH_FILE =
+ Environment.getDataDirectory().toString() + "/system/seapp_hash";
+
+ // Signature policy stanzas
+ static class Policy {
+ private String seinfo;
+ private final HashMap pkgMap;
+
+ Policy() {
+ seinfo = null;
+ pkgMap = new HashMap();
+ }
+
+ void putSeinfo(String seinfoValue) {
+ seinfo = seinfoValue;
+ }
+
+ void putPkg(String pkg, String seinfoValue) {
+ pkgMap.put(pkg, seinfoValue);
+ }
+
+ // Valid policy stanza means there exists a global
+ // seinfo value or at least one package policy.
+ boolean isValid() {
+ return (seinfo != null) || (!pkgMap.isEmpty());
+ }
+
+ String checkPolicy(String pkgName) {
+ // Check for package name seinfo value first.
+ String seinfoValue = pkgMap.get(pkgName);
+ if (seinfoValue != null) {
+ return seinfoValue;
+ }
+
+ // Return the global seinfo value.
+ return seinfo;
+ }
+ }
+
private static void flushInstallPolicy() {
sSigSeinfo.clear();
- sPackageSeinfo.clear();
+ sDefaultSeinfo = null;
}
/**
@@ -87,6 +134,10 @@ public static boolean readInstallPolicy(File policyFile) {
}
private static boolean readInstallPolicy(File[] policyFiles) {
+ // Temp structures to hold the rules while we parse the xml file.
+ // We add all the rules together once we know there's no structural problems.
+ HashMap sigSeinfo = new HashMap();
+ String defaultSeinfo = null;
FileReader policyFile = null;
int i = 0;
@@ -107,8 +158,6 @@ private static boolean readInstallPolicy(File[] policyFiles) {
Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
- flushInstallPolicy();
-
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(policyFile);
@@ -138,63 +187,49 @@ private static boolean readInstallPolicy(File[] policyFiles) {
XmlUtils.skipCurrentTag(parser);
continue;
}
- String seinfo = readSeinfoTag(parser);
- if (seinfo != null) {
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, " tag: (" + cert + ") assigned seinfo="
- + seinfo);
-
- sSigSeinfo.put(signature, seinfo);
+ Policy policy = readPolicyTags(parser);
+ if (policy.isValid()) {
+ sigSeinfo.put(signature, policy);
}
} else if ("default".equals(tagName)) {
- String seinfo = readSeinfoTag(parser);
- if (seinfo != null) {
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, " tag assigned seinfo=" + seinfo);
-
- // The 'null' signature is the default seinfo value
- sSigSeinfo.put(null, seinfo);
- }
- } else if ("package".equals(tagName)) {
- String pkgName = parser.getAttributeValue(null, "name");
- if (pkgName == null) {
- Slog.w(TAG, " without name at "
- + parser.getPositionDescription());
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String seinfo = readSeinfoTag(parser);
- if (seinfo != null) {
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, " tag: (" + pkgName +
- ") assigned seinfo=" + seinfo);
+ // Value is null if default tag is absent or seinfo tag is malformed.
+ defaultSeinfo = readSeinfoTag(parser);
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, " tag assigned seinfo=" + defaultSeinfo);
- sPackageSeinfo.put(pkgName, seinfo);
- }
} else {
XmlUtils.skipCurrentTag(parser);
- continue;
}
}
} catch (XmlPullParserException e) {
- Slog.w(TAG, "Got execption parsing ", e);
- } catch (IOException e) {
- Slog.w(TAG, "Got execption parsing ", e);
- }
- try {
- policyFile.close();
+ // An error outside of a stanza means a structural problem
+ // with the xml file. So ignore it.
+ Slog.w(TAG, "Got exception parsing ", e);
+ return false;
} catch (IOException e) {
- //omit
+ Slog.w(TAG, "Got exception parsing ", e);
+ return false;
+ } finally {
+ try {
+ policyFile.close();
+ } catch (IOException e) {
+ //omit
+ }
}
+
+ flushInstallPolicy();
+ sSigSeinfo = sigSeinfo;
+ sDefaultSeinfo = defaultSeinfo;
+
return true;
}
- private static String readSeinfoTag(XmlPullParser parser) throws
+ private static Policy readPolicyTags(XmlPullParser parser) throws
IOException, XmlPullParserException {
int type;
int outerDepth = parser.getDepth();
- String seinfo = null;
+ Policy policy = new Policy();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
@@ -205,19 +240,98 @@ private static String readSeinfoTag(XmlPullParser parser) throws
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
- String seinfoValue = parser.getAttributeValue(null, "value");
- if (validateValue(seinfoValue)) {
- seinfo = seinfoValue;
- } else {
- Slog.w(TAG, " without valid value at "
+ String seinfo = parseSeinfo(parser);
+ if (seinfo != null) {
+ policy.putSeinfo(seinfo);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } else if ("package".equals(tagName)) {
+ String pkg = parser.getAttributeValue(null, "name");
+ if (!validatePackageName(pkg)) {
+ Slog.w(TAG, " without valid name at "
+ parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
}
+
+ String seinfo = readSeinfoTag(parser);
+ if (seinfo != null) {
+ policy.putPkg(pkg, seinfo);
+ }
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return policy;
+ }
+
+ private static String readSeinfoTag(XmlPullParser parser) throws
+ IOException, XmlPullParserException {
+
+ int type;
+ int outerDepth = parser.getDepth();
+ String seinfo = null;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if ("seinfo".equals(tagName)) {
+ seinfo = parseSeinfo(parser);
}
XmlUtils.skipCurrentTag(parser);
}
return seinfo;
}
+ private static String parseSeinfo(XmlPullParser parser) {
+
+ String seinfoValue = parser.getAttributeValue(null, "value");
+ if (!validateValue(seinfoValue)) {
+ Slog.w(TAG, " without valid value at "
+ + parser.getPositionDescription());
+ seinfoValue = null;
+ }
+ return seinfoValue;
+ }
+
+ /**
+ * General validation routine for package names.
+ * Returns a boolean indicating if the passed string
+ * is a valid android package name.
+ */
+ private static boolean validatePackageName(String name) {
+ if (name == null)
+ return false;
+
+ final int N = name.length();
+ boolean hasSep = false;
+ boolean front = true;
+ for (int i=0; i= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ front = false;
+ continue;
+ }
+ if (!front) {
+ if ((c >= '0' && c <= '9') || c == '_') {
+ continue;
+ }
+ }
+ if (c == '.') {
+ hasSep = true;
+ front = true;
+ continue;
+ }
+ return false;
+ }
+ return hasSep;
+ }
+
/**
* General validation routine for tag values.
* Returns a boolean indicating if the passed string
@@ -245,10 +359,11 @@ private static boolean validateValue(String name) {
* The label is attached to the ApplicationInfo instance of the package.
* @param PackageParser.Package object representing the package
* to labeled.
- * @return String holding the value of the seinfo label that was assigned.
- * Value may be null which indicates no seinfo label was assigned.
+ * @return boolean which determines whether a non null seinfo label
+ * was assigned to the package. A null value simply meaning that
+ * no policy matched.
*/
- public static void assignSeinfoValue(PackageParser.Package pkg) {
+ public static boolean assignSeinfoValue(PackageParser.Package pkg) {
/*
* Non system installed apps should be treated the same. This
@@ -264,31 +379,113 @@ public static void assignSeinfoValue(PackageParser.Package pkg) {
if (s == null)
continue;
- if (sSigSeinfo.containsKey(s)) {
- String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(s);
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "package (" + pkg.packageName +
- ") labeled with seinfo=" + seinfo);
+ Policy policy = sSigSeinfo.get(s);
+ if (policy != null) {
+ String seinfo = policy.checkPolicy(pkg.packageName);
+ if (seinfo != null) {
+ pkg.applicationInfo.seinfo = seinfo;
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "package (" + pkg.packageName +
+ ") labeled with seinfo=" + seinfo);
- return;
+ return true;
+ }
}
}
-
- // Check for seinfo labeled by package.
- if (sPackageSeinfo.containsKey(pkg.packageName)) {
- String seinfo = pkg.applicationInfo.seinfo = sPackageSeinfo.get(pkg.packageName);
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "package (" + pkg.packageName +
- ") labeled with seinfo=" + seinfo);
- return;
- }
}
// If we have a default seinfo value then great, otherwise
// we set a null object and that is what we started with.
- String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(null);
+ pkg.applicationInfo.seinfo = sDefaultSeinfo;
if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "package (" + pkg.packageName +
- ") labeled with seinfo=" + (seinfo == null ? "null" : seinfo));
+ Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
+ + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
+
+ return (sDefaultSeinfo != null);
+ }
+
+ /**
+ * Determines if a recursive restorecon on /data/data and /data/user is needed.
+ * It does this by comparing the SHA-1 of the seapp_contexts file against the
+ * stored hash at /data/system/seapp_hash.
+ *
+ * @return Returns true if the restorecon should occur or false otherwise.
+ */
+ public static boolean shouldRestorecon() {
+ // Any error with the seapp_contexts file should be fatal
+ byte[] currentHash = null;
+ try {
+ currentHash = returnHash(SEAPP_CONTEXTS_FILE);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error with hashing seapp_contexts.", ioe);
+ return false;
+ }
+
+ // Push past any error with the stored hash file
+ byte[] storedHash = null;
+ try {
+ storedHash = IoUtils.readFileAsByteArray(SEAPP_HASH_FILE);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error opening " + SEAPP_HASH_FILE + ". Assuming first boot.", ioe);
+ }
+
+ return (storedHash == null || !MessageDigest.isEqual(storedHash, currentHash));
+ }
+
+ /**
+ * Stores the SHA-1 of the seapp_contexts to /data/system/seapp_hash.
+ */
+ public static void setRestoreconDone() {
+ try {
+ final byte[] currentHash = returnHash(SEAPP_CONTEXTS_FILE);
+ dumpHash(new File(SEAPP_HASH_FILE), currentHash);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe);
+ }
+ }
+
+ /**
+ * Dump the contents of a byte array to a specified file.
+ *
+ * @param file The file that receives the byte array content.
+ * @param content A byte array that will be written to the specified file.
+ * @throws IOException if any failed I/O operation occured.
+ * Included is the failure to atomically rename the tmp
+ * file used in the process.
+ */
+ private static void dumpHash(File file, byte[] content) throws IOException {
+ FileOutputStream fos = null;
+ File tmp = null;
+ try {
+ tmp = File.createTempFile("seapp_hash", ".journal", file.getParentFile());
+ tmp.setReadable(true);
+ fos = new FileOutputStream(tmp);
+ fos.write(content);
+ fos.getFD().sync();
+ if (!tmp.renameTo(file)) {
+ throw new IOException("Failure renaming " + file.getCanonicalPath());
+ }
+ } finally {
+ if (tmp != null) {
+ tmp.delete();
+ }
+ IoUtils.closeQuietly(fos);
+ }
+ }
+
+ /**
+ * Return the SHA-1 of a file.
+ *
+ * @param file The path to the file given as a string.
+ * @return Returns the SHA-1 of the file as a byte array.
+ * @throws IOException if any failed I/O operations occured.
+ */
+ private static byte[] returnHash(String file) throws IOException {
+ try {
+ final byte[] contents = IoUtils.readFileAsByteArray(file);
+ return MessageDigest.getInstance("SHA-1").digest(contents);
+ } catch (NoSuchAlgorithmException nsae) {
+ throw new RuntimeException(nsae); // impossible
+ }
}
}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 7a7248d08c15..ab299f07c3c7 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -2689,7 +2689,8 @@ void createNewUserLILPw(PackageManagerService service, Installer installer,
ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
// Need to create a data directory for all apps under this user.
installer.createUserData(ps.name,
- UserHandle.getUid(userHandle, ps.appId), userHandle);
+ UserHandle.getUid(userHandle, ps.appId), userHandle,
+ ps.pkg.applicationInfo.seinfo);
}
readDefaultPreferredAppsLPw(service, userHandle);
writePackageRestrictionsLPr(userHandle);
diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java
index 87cabc9a510b..e0189b3d017b 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.view.IWindowId;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -59,6 +62,8 @@ final class Session extends IWindowSession.Stub
final int mUid;
final int mPid;
final String mStringName;
+ final boolean mCanAddInternalSystemWindow;
+ final boolean mCanHideNonSystemOverlayWindows;
SurfaceSession mSurfaceSession;
int mNumWindow = 0;
boolean mClientDead = false;
@@ -70,6 +75,10 @@ public Session(WindowManagerService service, IInputMethodClient client,
mInputContext = inputContext;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
+ mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
+ INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
+ mCanHideNonSystemOverlayWindows = service.mContext.checkCallingOrSelfPermission(
+ HIDE_NON_SYSTEM_OVERLAY_WINDOWS) == PERMISSION_GRANTED;
StringBuilder sb = new StringBuilder();
sb.append("Session{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 8b5aafb32eb0..973bb51a4802 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -392,6 +392,9 @@ public void onReceive(Context context, Intent intent) {
*/
ArrayList mForceRemoves;
+ /** List of window currently causing non-system overlay windows to be hidden. */
+ private ArrayList mHidingNonSystemOverlayWindows = new ArrayList();
+
/**
* Windows that clients are waiting to have drawn.
*/
@@ -2280,6 +2283,9 @@ public int addWindow(Session session, IWindow client, int seq,
}
}
+ final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
+ win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
+
if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
token.appWindowToken.startingWindow = win;
if (DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken
@@ -2507,6 +2513,7 @@ private void removeWindowInnerLocked(Session session, WindowState win) {
windows.remove(win);
mPendingRemove.remove(win);
mResizingWindows.remove(win);
+ updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
mWindowsChanged = true;
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
@@ -10960,4 +10967,33 @@ public int getSystemUIVisibility() {
return mLastStatusBarVisibility;
}
+ void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
+ if (!win.hideNonSystemOverlayWindowsWhenVisible()) {
+ return;
+ }
+ final boolean systemAlertWindowsHidden = !mHidingNonSystemOverlayWindows.isEmpty();
+ if (surfaceShown) {
+ if (!mHidingNonSystemOverlayWindows.contains(win)) {
+ mHidingNonSystemOverlayWindows.add(win);
+ }
+ } else {
+ mHidingNonSystemOverlayWindows.remove(win);
+ }
+
+ final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
+
+ if (systemAlertWindowsHidden == hideSystemAlertWindows) {
+ return;
+ }
+
+ final int numDisplays = mDisplayContents.size();
+ for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+ final int numWindows = windows.size();
+ for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+ final WindowState w = windows.get(winNdx);
+ w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 4d53cea4370d..f5cc9d639a6e 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -21,11 +21,14 @@
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.app.AppOpsManager;
@@ -76,6 +79,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final int mAppOp;
// UserId and appId of the owner. Don't display windows of non-current user.
final int mOwnerUid;
+ final boolean mOwnerCanAddInternalSystemWindow;
final IWindowId mWindowId;
WindowToken mToken;
WindowToken mRootToken;
@@ -101,6 +105,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
boolean mPolicyVisibility = true;
boolean mPolicyVisibilityAfterAnim = true;
boolean mAppOpVisibility = true;
+ // This is a non-system overlay window that is currently force hidden.
+ private boolean mForceHideNonSystemOverlayWindow;
boolean mAppFreezing;
boolean mAttachedHidden; // is our parent window hidden?
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
@@ -312,6 +318,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mAppOp = appOp;
mToken = token;
mOwnerUid = s.mUid;
+ mOwnerCanAddInternalSystemWindow = s.mCanAddInternalSystemWindow;
mWindowId = new IWindowId.Stub() {
@Override
public void registerFocusObserver(IWindowFocusObserver observer) {
@@ -1089,6 +1096,10 @@ boolean showLw(boolean doAnimation, boolean requestAnim) {
// Being hidden due to app op request.
return false;
}
+ if (mForceHideNonSystemOverlayWindow) {
+ // This is an alert window that is currently force hidden.
+ return false;
+ }
if (mPolicyVisibility && mPolicyVisibilityAfterAnim) {
// Already showing.
return false;
@@ -1162,6 +1173,22 @@ boolean hideLw(boolean doAnimation, boolean requestAnim) {
return true;
}
+ void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
+ if (mOwnerCanAddInternalSystemWindow
+ || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
+ return;
+ }
+ if (mForceHideNonSystemOverlayWindow == forceHide) {
+ return;
+ }
+ mForceHideNonSystemOverlayWindow = forceHide;
+ if (forceHide) {
+ hideLw(true /* doAnimation */, true /* requestAnim */);
+ } else {
+ showLw(true /* doAnimation */, true /* requestAnim */);
+ }
+ }
+
public void setAppOpVisibilityLw(boolean state) {
if (mAppOpVisibility != state) {
mAppOpVisibility = state;
@@ -1458,6 +1485,17 @@ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
}
}
+ /**
+ * Returns true if any window added by an application process that if of type
+ * {@link android.view.WindowManager.LayoutParams#TYPE_TOAST} or that requires that requires
+ * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
+ * this window is visible.
+ */
+ boolean hideNonSystemOverlayWindowsWhenVisible() {
+ return (mAttrs.privateFlags & PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0
+ && mSession.mCanHideNonSystemOverlayWindows;
+ }
+
String makeInputChannelName() {
return Integer.toHexString(System.identityHashCode(this))
+ " " + mAttrs.getTitle();
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 72ed71cb9eec..dea3d2e425de 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -425,6 +425,7 @@ void hide() {
"HIDE (performLayout)", null);
if (mSurfaceControl != null) {
mSurfaceShown = false;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, false);
try {
mSurfaceControl.hide();
} catch (RuntimeException e) {
@@ -1490,6 +1491,7 @@ boolean showSurfaceRobustlyLocked() {
if (mSurfaceControl != null) {
mSurfaceShown = true;
mSurfaceControl.show();
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, true);
if (mWin.mTurnOnScreen) {
if (DEBUG_VISIBILITY) Slog.v(TAG,
"Show surface turning screen on: " + mWin);
diff --git a/telephony/java/android/telephony/NeighboringCellInfo.java b/telephony/java/android/telephony/NeighboringCellInfo.java
index 51e1e95245f9..0e04c9ecaf0d 100644
--- a/telephony/java/android/telephony/NeighboringCellInfo.java
+++ b/telephony/java/android/telephony/NeighboringCellInfo.java
@@ -26,6 +26,7 @@
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSUPA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPAP;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_DCHSPAP;
/**
* Represents the neighboring cell information, including
@@ -108,8 +109,10 @@ public NeighboringCellInfo(int rssi, int cid) {
* {@link TelephonyManager#NETWORK_TYPE_HSDPA TelephonyManager.NETWORK_TYPE_HSDPA},
* {@link TelephonyManager#NETWORK_TYPE_HSUPA TelephonyManager.NETWORK_TYPE_HSUPA},
* {@link TelephonyManager#NETWORK_TYPE_HSPA TelephonyManager.NETWORK_TYPE_HSPA},
- * and {@link TelephonyManager#NETWORK_TYPE_HSPAP TelephonyManager.NETWORK_TYPE_HSPAP}.
+ * {@link TelephonyManager#NETWORK_TYPE_HSPAP TelephonyManager.NETWORK_TYPE_HSPAP},
+ * and {@link TelephonyManager#NETWORK_TYPE_DCHSPAP TelephonyManager.NETWORK_TYPE_DCHSPAP}.
*/
+
public NeighboringCellInfo(int rssi, String location, int radioType) {
// set default value
mRssi = rssi;
@@ -144,6 +147,7 @@ public NeighboringCellInfo(int rssi, String location, int radioType) {
case NETWORK_TYPE_HSUPA:
case NETWORK_TYPE_HSPA:
case NETWORK_TYPE_HSPAP:
+ case NETWORK_TYPE_DCHSPAP:
mNetworkType = radioType;
mPsc = Integer.valueOf(location, 16);
break;
@@ -220,7 +224,8 @@ public int getPsc() {
* {@link TelephonyManager#NETWORK_TYPE_HSDPA TelephonyManager.NETWORK_TYPE_HSDPA},
* {@link TelephonyManager#NETWORK_TYPE_HSUPA TelephonyManager.NETWORK_TYPE_HSUPA},
* {@link TelephonyManager#NETWORK_TYPE_HSPA TelephonyManager.NETWORK_TYPE_HSPA},
- * or {@link TelephonyManager#NETWORK_TYPE_HSPAP TelephonyManager.NETWORK_TYPE_HSPAP}
+ * {@link TelephonyManager#NETWORK_TYPE_HSPAP TelephonyManager.NETWORK_TYPE_HSPAP},
+ * or {@link TelephonyManager#NETWORK_TYPE_DCHSPAP TelephonyManager.NETWORK_TYPE_DCHSPAP}.
* means that Neighboring Cell information is stored for UMTS network, in
* which {@link NeighboringCellInfo#getPsc NeighboringCellInfo.getPsc}
* should be called to access location.
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5150b709e796..1b05bf5ab125 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -153,7 +153,11 @@ public class ServiceState implements Parcelable {
* @hide
*/
public static final int RIL_RADIO_TECHNOLOGY_TD_SCDMA = 17;
-
+ /**
+ * IWLAN
+ * @hide
+ */
+ public static final int RIL_RADIO_TECHNOLOGY_IWLAN = 18;
/**
* Available registration states for GSM, UMTS and CDMA.
*/
@@ -535,8 +539,10 @@ public static String rilRadioTechnologyToString(int rt) {
rtString = "LTE";
break;
case RIL_RADIO_TECHNOLOGY_HSPAP:
+ rtString = "HSPA+";
+ break;
case RIL_RADIO_TECHNOLOGY_DCHSPAP:
- rtString = "HSPAP";
+ rtString = "DC-HSPA+";
break;
case RIL_RADIO_TECHNOLOGY_GSM:
rtString = "GSM";
@@ -544,6 +550,9 @@ public static String rilRadioTechnologyToString(int rt) {
case RIL_RADIO_TECHNOLOGY_TD_SCDMA:
rtString = "TD-SCDMA";
break;
+ case RIL_RADIO_TECHNOLOGY_IWLAN:
+ rtString = "IWLAN";
+ break;
default:
rtString = "Unexpected";
Rlog.w(LOG_TAG, "Unexpected radioTechnology=" + rt);
@@ -807,12 +816,13 @@ private int rilRadioTechnologyToNetworkType(int rt) {
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
return TelephonyManager.NETWORK_TYPE_LTE;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
- case ServiceState.RIL_RADIO_TECHNOLOGY_DCHSPAP:
return TelephonyManager.NETWORK_TYPE_HSPAP;
case ServiceState.RIL_RADIO_TECHNOLOGY_GSM:
return TelephonyManager.NETWORK_TYPE_GSM;
case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA:
return TelephonyManager.NETWORK_TYPE_TD_SCDMA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_DCHSPAP:
+ return TelephonyManager.NETWORK_TYPE_DCHSPAP;
default:
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
@@ -864,7 +874,8 @@ public static boolean isGsm(int radioTechnology) {
|| radioTechnology == RIL_RADIO_TECHNOLOGY_HSPAP
|| radioTechnology == RIL_RADIO_TECHNOLOGY_DCHSPAP
|| radioTechnology == RIL_RADIO_TECHNOLOGY_GSM
- || radioTechnology == RIL_RADIO_TECHNOLOGY_TD_SCDMA;
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_TD_SCDMA
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_IWLAN;
}
/** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c05686aa882f..b4c717759ca6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -673,7 +673,13 @@ public String getNetworkCountryIso() {
public static final int NETWORK_TYPE_GSM = 16;
/** Current network is TD_SCDMA {@hide} */
public static final int NETWORK_TYPE_TD_SCDMA = 17;
+ /** Current network is IWLAN {@hide} */
+ public static final int NETWORK_TYPE_IWLAN = 18;
+ /** Current network is DC-HSPAP
+ * @hide
+ */
+ public static final int NETWORK_TYPE_DCHSPAP = 30;
/**
* @return the NETWORK_TYPE_xxxx for current data connection.
@@ -704,7 +710,7 @@ public int getNetworkType() {
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
* @see #NETWORK_TYPE_TD_SCDMA
- *
+ * @see #NETWORK_TYPE_DCHSPAP
* @hide
*/
public int getDataNetworkType() {
@@ -793,8 +799,10 @@ public static int getNetworkClass(int networkType) {
case NETWORK_TYPE_EHRPD:
case NETWORK_TYPE_HSPAP:
case NETWORK_TYPE_TD_SCDMA:
+ case NETWORK_TYPE_DCHSPAP:
return NETWORK_CLASS_3_G;
case NETWORK_TYPE_LTE:
+ case NETWORK_TYPE_IWLAN:
return NETWORK_CLASS_4_G;
default:
return NETWORK_CLASS_UNKNOWN;
@@ -848,7 +856,11 @@ public static String getNetworkTypeName(int type) {
case NETWORK_TYPE_GSM:
return "GSM";
case NETWORK_TYPE_TD_SCDMA:
- return "TD_SCDMA";
+ return "TD-SCDMA";
+ case NETWORK_TYPE_IWLAN:
+ return "IWLAN";
+ case NETWORK_TYPE_DCHSPAP:
+ return "DC-HSPA+";
default:
return "UNKNOWN";
}
@@ -875,6 +887,10 @@ public static String getNetworkTypeName(int type) {
public static final int SIM_STATE_NETWORK_LOCKED = 4;
/** SIM card state: Ready */
public static final int SIM_STATE_READY = 5;
+ /** SIM card state: SIM Card Error, Sim Card is present but faulty
+ *@hide
+ */
+ public static final int SIM_STATE_CARD_IO_ERROR = 6;
/**
* @return true if a ICC card is present
@@ -901,6 +917,7 @@ public boolean hasIccCard() {
* @see #SIM_STATE_PUK_REQUIRED
* @see #SIM_STATE_NETWORK_LOCKED
* @see #SIM_STATE_READY
+ * @see #SIM_STATE_CARD_IO_ERROR
*/
public int getSimState() {
String prop = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_STATE);
@@ -919,6 +936,9 @@ else if ("NETWORK_LOCKED".equals(prop)) {
else if ("READY".equals(prop)) {
return SIM_STATE_READY;
}
+ else if ("CARD_IO_ERROR".equals(prop)) {
+ return SIM_STATE_CARD_IO_ERROR;
+ }
else {
return SIM_STATE_UNKNOWN;
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 4be11b879230..4329bf049241 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -1,5 +1,8 @@
/*
* Copyright (C) 2012 The Android Open Source Project
+ * Copyright (c) 2012-2013 The Linux Foundation. All rights reserved.
+ *
+ * Not a Contribution.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,6 +100,10 @@ public enum Activity {
public static final int CMD_ENABLE_MOBILE_PROVISIONING = BASE + 37;
public static final int CMD_IS_PROVISIONING_APN = BASE + 38;
public static final int EVENT_PROVISIONING_APN_ALARM = BASE + 39;
+ public static final int EVENT_DATA_RAT_CHANGED = BASE + 40;
+ public static final int EVENT_MODEM_DATA_PROFILE_READY= BASE + 41;
+ public static final int CMD_NET_STAT_POLL = BASE + 42;
+ public static final int EVENT_RADIO_IWLAN_AVAILABLE= BASE + 43;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index 236bb2fb5a57..7388b6251369 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -28,6 +28,8 @@ public class IccCardConstants {
public static final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
/* ABSENT means ICC is missing */
public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
+ /* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
+ public static final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
/* LOCKED means ICC is locked by pin or by network */
public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
/* READY means ICC is ready to access */
@@ -63,7 +65,8 @@ public enum State {
NETWORK_LOCKED,
READY,
NOT_READY,
- PERM_DISABLED;
+ PERM_DISABLED,
+ CARD_IO_ERROR;
public boolean isPinLocked() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
@@ -72,7 +75,7 @@ public boolean isPinLocked() {
public boolean iccCardExist() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
|| (this == NETWORK_LOCKED) || (this == READY)
- || (this == PERM_DISABLED));
+ || (this == PERM_DISABLED) || (this == CARD_IO_ERROR));
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a5d4df04496b..47cae469b4e7 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -276,6 +276,7 @@ class C */
int RIL_REQUEST_SET_INITIAL_ATTACH_APN = 111;
int RIL_REQUEST_IMS_REGISTRATION_STATE = 112;
int RIL_REQUEST_IMS_SEND_SMS = 113;
+ int RIL_REQUEST_GET_DATA_CALL_PROFILE = 114;
int RIL_UNSOL_RESPONSE_BASE = 1000;
int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index c7ebecb7e1ee..2b8ca48877d5 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -837,7 +837,9 @@ private void setFieldValue(String key, String value, String prefix) {
public String toString() {
StringBuffer sb = new StringBuffer();
for (String key : mFields.keySet()) {
- sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+ // Don't display password in toString().
+ String value = PASSWORD_KEY.equals(key) ? "" : mFields.get(key);
+ sb.append(key).append(" ").append(value).append("\n");
}
return sb.toString();
}