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(); }