From 6304b9385620ff104280c2e37bfa0a12c729a267 Mon Sep 17 00:00:00 2001 From: Darren Smith Date: Tue, 9 Dec 2014 01:22:22 -0600 Subject: [PATCH 01/86] Add missing lockscreen drawables System settings FCs when trying to add lockscreen targets. Add missing resources for LockscreenTargets.java. Graphics copied from Cyanogenmod CM-11.0 branch. Change-Id: I1a8b70c425d2069e5d432c4f46b2265c7530985d --- .../ic_lockscreen_target_activated.png | Bin 0 -> 5668 bytes .../ic_lockscreen_target_activated.png | Bin 0 -> 4756 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/Keyguard/res/drawable-hdpi/ic_lockscreen_target_activated.png create mode 100644 packages/Keyguard/res/drawable-mdpi/ic_lockscreen_target_activated.png 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 0000000000000000000000000000000000000000..c5810bf3924087f2b34f0b85c008886c90e19ac0 GIT binary patch literal 5668 zcmV+<7Tf8GP)Px#0%A)?L;(MXkIcUS000SaNLh0L01FZT01FZU(%pXi00007bV*G`2iyb~0x}2T z_NuM`02Qc7L_t(|+U=cvjAdng$3M?=&b@bLUszx@uq?VDSY+AKQdvNGX_d4ol4vo3 zMyaVK)nZ~IttCy5{GOMeV%09w@|;@6*S9Ut=~em7 z)Um*)SNV^n*EdA@3l*eT6c!Y={K=|*A6C2DY1EIw&jtRq^-ZpGR3wP1gTcu zBUR+ukg4EN6J`A|L@4pu>T??*6)0l^*J~A`2}Gls#@j@v-RC-bJ~nlW4NN%%7~TjT z1qL4aNxjw}N`TtCqfmeGO%Hi25!S!8iqr=d6X7%BQG_zoH4V-O8 z9UF28&a^y|NZt3fgVbvQVvX=6UN;-S8kxV>EkK)UY(g}V_KXAQ?@fSI>T*RuE)il; zy+A<{n{bRz!i#TBh_M!%p(F-ZKPsZQzgu1qDTgt$r;3S$A(j4?VKjTO2TT!LAvB_mXG0PK*`BPB<3 zCYSRLPO5Trwale$0Q z&>{C|}|p?&4__ib0@BugE%TFUhRck`UHroS;SA^$5H4 zDA1h4HC&7aXS}O3BcXvA?&3BMkYeeRRhsnX9mecMVpI!6D1%9@L60yM2)j(6$T*w# zaS7=P>u;E^R4Asnldp1+E;)S$?V8WL3Dyf{1O}F)j&W=nDnHI+T?WyX2PL6H6|5~Es(4t1E&BvByg(q)2_g3I{`+bie3R<82>?B_G=qsM?5G6ogK zX4*4vjA}^;YcuZBrOPCSEqsJ`9YZfTSNfAM%`JQdD42E_vr3PP!l)L2kSM9H{a}|a zJti4&CO7hyHDko>dQ>RxGtIzZ48x4WSSCEys#!6rxeKl~M5shCk$!N3K9_R? z+sY#M8oo*^d5TZ+6S^FA7zYiErp_CcD>^O`qKL^psYwHbr6=r>a}9rl#;idzZu|8T z4)G}-V1lFc9mGL3FpM$IYSLr%)|x(_$nLnYQcDEr6>^ z*FmjXj!aT|{3UN&hX}oO8vX#q%}mf(eo~Hg(HI4`Ui%kgOxqCpq{+>na6+@Z&xaPkH*Wm oX@r7OwgelAoL3WeT)esNL#RS*`aRe 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 i>zr!M`?cQ6{|^AO3sq64wyP!p0000 Date: Mon, 15 Dec 2014 11:59:43 +0100 Subject: [PATCH 02/86] Revert "SystemUI: fix logic in smart pulldown all mode" Looks like I did not understand this feature lol It was correct This reverts commit 927e314599976445cb2522d7fe9e64e33d7ea489. Change-Id: Ie0e088191aa8197ac860d1a9acbd129cc811cda1 --- .../systemui/statusbar/phone/NotificationPanelView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 17dbb5df7c0f..ae09fa2a5129 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -164,8 +164,8 @@ public boolean onTouchEvent(MotionEvent event) { flip = true; } else if (smartPulldownMode == 2 && !mStatusBar.hasVisibleNotifications()) { flip = true; - } else if (smartPulldownMode == 3 && (!mStatusBar.hasVisibleNotifications() - || !mStatusBar.hasClearableNotifications())) { + } else if (smartPulldownMode == 3 && !mStatusBar.hasVisibleNotifications() + && !mStatusBar.hasClearableNotifications()) { flip = true; } else if (quickPulldownMode == 1 && mGestureStartX > getWidth() * (1.0f - STATUS_BAR_RIGHT_PERCENTAGE)) { From da7f1282972865d528a2c62c2f53e90c19e5baed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bl=C3=A4sius?= Date: Sun, 21 Dec 2014 00:56:01 +0100 Subject: [PATCH 03/86] Automatic translation import Change-Id: I5aa654ef7e0e7d690f4e3bccd6497d4221d8e2e0 --- core/res/res/values-pt-rBR/slim_strings.xml | 57 +++++++++++++++++++ .../Keyguard/res/values-de/slim_strings.xml | 1 + .../Keyguard/res/values-fr/slim_strings.xml | 8 +++ .../res/values-pt-rBR/slim_strings.xml | 35 ++++++++++++ .../SystemUI/res/values-de/slim_strings.xml | 20 ++++--- .../SystemUI/res/values-fr/slim_strings.xml | 2 + .../res/values-pt-rBR/slim_strings.xml | 32 +++++++++++ 7 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 core/res/res/values-pt-rBR/slim_strings.xml create mode 100644 packages/Keyguard/res/values-pt-rBR/slim_strings.xml create mode 100644 packages/SystemUI/res/values-pt-rBR/slim_strings.xml diff --git a/core/res/res/values-pt-rBR/slim_strings.xml b/core/res/res/values-pt-rBR/slim_strings.xml new file mode 100644 index 000000000000..95408d8b93f9 --- /dev/null +++ b/core/res/res/values-pt-rBR/slim_strings.xml @@ -0,0 +1,57 @@ + + + + + ativar ou desativar proteção à privacidade + Permite ao aplicativo alterar se outra aplicação executa com Proteção à Privacidade. Quando um aplicativo estiver executando com Proteção à Privacidade, ele não terá acesso a dados pessoais como contatos, registros de chamadas, ou mensagens. + Proteção à Privacidade está ativada + %1$s não será capaz de acessar dados pessoais + %1$s não poderá acessar permissões personalizadas + Padrão + Reiniciar tablet + Reiniciar telefone + Capturar a tela + Reiniciar + Recuperação + Bootloader + Menu de Boot + Fastboot + Download + Reiniciando\u2026 + O seu telefone irá reiniciar. + Reiniciar + Perfil + Desligar o Wi-Fi AP + A área de trabalho expandida está ligada + A área de trabalho expandida está desligada + SlimPie está ligada + SlimPie está desligada + Você não pode desativar os controles da SlimPie pois nenhuma outra navegação está habilitada. Ative primeiro a barra de navegação para depois desativar os controles da SlimPie. + A barra de navegação está ativada + A barra de navegação está desativada + Você não pode desativar a barra de navegação. Nenhuma outra navegação está ativada. Ative primeiro a SlimPie para depois desativar a barra de navegação. + Deslize para ativar um alvo + %s não está instalado + O modo de alteração automática da luz está ativado. Não é possível a alteração manual. + ignore botão Liga/Desliga + Permite que um aplicativo ignore o botão Liga/Desliga + ativar ou desativar a função heads up + Permite que o aplicativo altere se outro aplicativo pode mostrar notificações normais como sendo notificações heads up. + diff --git a/packages/Keyguard/res/values-de/slim_strings.xml b/packages/Keyguard/res/values-de/slim_strings.xml index 8aa72b57027a..f6db4508d255 100644 --- a/packages/Keyguard/res/values-de/slim_strings.xml +++ b/packages/Keyguard/res/values-de/slim_strings.xml @@ -31,4 +31,5 @@ Verpasster Anruf Nachricht von Nachricht von %1$s + EEEEdMMMM diff --git a/packages/Keyguard/res/values-fr/slim_strings.xml b/packages/Keyguard/res/values-fr/slim_strings.xml index 350c027b8c81..36259e6f5b9c 100644 --- a/packages/Keyguard/res/values-fr/slim_strings.xml +++ b/packages/Keyguard/res/values-fr/slim_strings.xml @@ -19,8 +19,16 @@ */ --> + Recharger aléatoirement les boutons + + %d appel manqué + %d appels manqués + %d message non lu %d messages non lus + appel manqué + message de + message de %1$s diff --git a/packages/Keyguard/res/values-pt-rBR/slim_strings.xml b/packages/Keyguard/res/values-pt-rBR/slim_strings.xml new file mode 100644 index 000000000000..1bcec235de48 --- /dev/null +++ b/packages/Keyguard/res/values-pt-rBR/slim_strings.xml @@ -0,0 +1,35 @@ + + + + + Recarregar botões aleatórios + + %d chamada perdida + %d chamadas perdidas + + + %d mensagem não lida + %d mensagens não lidas + + Chamada perdida + mensagem de + mensagem de %1$s + EEEEMMMMd + diff --git a/packages/SystemUI/res/values-de/slim_strings.xml b/packages/SystemUI/res/values-de/slim_strings.xml index b76c950be667..86331a9d9ce9 100644 --- a/packages/SystemUI/res/values-de/slim_strings.xml +++ b/packages/SystemUI/res/values-de/slim_strings.xml @@ -32,7 +32,7 @@ An Umschalten Slim-Verknüpfung - Immersive-Modus + Erweiterter Desktop Ruhige Stunden (Zeitvorgaben werden ignoriert) Rotation Shake-Events @@ -58,7 +58,7 @@ Standardnamen verwenden GPS GPS aus - Bildschirm ausschalten + Standby Ton an Ton aus Vibration an @@ -66,7 +66,7 @@ An Aus Ton - Sperrbildschirm + Sperren Netzwerk-Modus Fehler melden Sync @@ -80,8 +80,10 @@ Getrennt Tethering aus Tethering - »Ruhige Stunden« an - »Ruhige Stunden« aus + WLAN-Hotspot + WLAN-Hotspot + Ruhige Std. an + Ruhige Std. aus LTE LTE aus Shake-Events @@ -98,16 +100,16 @@ Timer aktualisieren Recovery Neustart - Dunkles Theme - Helles Theme + Dunkles Design + Helles Design Gelöscht... Lange drücken Schnellaufnahme Aufnahme... Gespeichert - Wird abgespielt... + Wiedergabe ... Schließen - Aufnahme starten\nZum Löschen lange drücken + Aufnahme abspielen\nZum Löschen lange drücken Benutzerdefiniert Kontakt Erinnerung diff --git a/packages/SystemUI/res/values-fr/slim_strings.xml b/packages/SystemUI/res/values-fr/slim_strings.xml index 77a22558cd04..2407635701cf 100644 --- a/packages/SystemUI/res/values-fr/slim_strings.xml +++ b/packages/SystemUI/res/values-fr/slim_strings.xml @@ -80,6 +80,8 @@ Déconnecté Connexion non partagée Connexion partagée + Point d\'accès Wi-Fi + Point d\'accès Wi-Fi H.S. activées H.S. désactivées LTE diff --git a/packages/SystemUI/res/values-pt-rBR/slim_strings.xml b/packages/SystemUI/res/values-pt-rBR/slim_strings.xml new file mode 100644 index 000000000000..bb9deb76874f --- /dev/null +++ b/packages/SystemUI/res/values-pt-rBR/slim_strings.xml @@ -0,0 +1,32 @@ + + + + + Carregando + Descarregando + Aplicativo encerrado + Excluir + A captura de tela foi excluída + HSPA+ + Cancelar + Ok + Ação a ser executada + %d%% restantes + Modo avião ligado + From 36f5b1f8a329cfaddc78fa6cf239d436e8bc5d2a Mon Sep 17 00:00:00 2001 From: zweif Date: Wed, 12 Nov 2014 13:45:10 +0100 Subject: [PATCH 04/86] Keyguard: Fix lockscreen selection view in landscape mode Added a dimen-value for selector in landscape-layout to fit screen. Change-Id: I9501c91fed2660e947f1eae617f9a31d95766ed8 --- packages/Keyguard/res/values-land/dimens.xml | 3 +++ 1 file changed, 3 insertions(+) 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 From ad458cbd2b40f13ba0b6881a947661ce60366d50 Mon Sep 17 00:00:00 2001 From: millo1978 Date: Sat, 20 Dec 2014 17:30:52 +0100 Subject: [PATCH 05/86] Keyguard Landscape: Fix Glowpad and SIM Pin Layout for sw360dp devices Fix Layout for sw360dp devices (for example 1280x720 xhdpi like i9300) Change-Id: Ia7e2476d397cf892c210cc20b182c485f9f3cfcd --- .../res/values-sw360dp-land/dimens.xml | 25 +++++++++++ .../res/values-sw360dp-land/styles.xml | 43 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 packages/Keyguard/res/values-sw360dp-land/dimens.xml create mode 100644 packages/Keyguard/res/values-sw360dp-land/styles.xml 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 @@ + + + + + + + + From 657e6408f21b4f1ecabcb64a3eb612c842a793cb Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 18 Feb 2015 15:36:05 +0000 Subject: [PATCH 06/86] ExternalStorageProvider: Use the UUID as title when the label is empty When the label is empty the UI will show a blank space. Most operating systems will show it as the uuid, that's what this patch does as well. Change-Id: I8dc41ea70c96229cb0fccc1e19b525fbc4be1808 --- .../com/android/externalstorage/ExternalStorageProvider.java | 4 ++++ 1 file changed, 4 insertions(+) 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); From 728e70a42837ca7fd36d419ffa80017c17606b7a Mon Sep 17 00:00:00 2001 From: Todd Poynor Date: Fri, 22 Nov 2013 17:15:49 -0800 Subject: [PATCH 07/86] Add previous console on pstore to DropBox Change-Id: I23c0213fe3d52280d7338ca62cb7e79b80a16cc6 --- services/java/com/android/server/BootReceiver.java | 7 +++++++ 1 file changed, 7 insertions(+) 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) { From c0f150d7aa25a6a7c50c0810bb45452839b2997d Mon Sep 17 00:00:00 2001 From: Matt Garnes Date: Wed, 18 Mar 2015 16:26:18 -0700 Subject: [PATCH 08/86] Properly clean up when setting new InputFilter. The single InputFilter that is set in InputManagerService was replaced with a chain of filters that are all listening for InputEvents. The original field mInputFilter was replaced in this patch but not removed. When a new InputFilter is added, we check that this unused field mInputFilter != null before doing necessary teardown of the previous filter. Since this is always null, this causes the previous filter to not be disconnected when a new one is set with setInputFilter(). If the user toggles "Magnification Gestures" on and off twice in Accessibility Settings, this will send the old and new InputFilters into a loop sending and receiving touch events, locking up the device completely until reboot. Remove all references to the unused mInputFilter field. Change-Id: Id28335d150a195af8747b4862deb897ae850d2cb --- .../android/server/input/InputManagerService.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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) { From 427e9b5623ca7e9a79bba7bfb7a4a83e4f1ec59e Mon Sep 17 00:00:00 2001 From: Marcos Marado Date: Mon, 16 Mar 2015 17:22:32 +0000 Subject: [PATCH 09/86] Fix data cycle calculation when CYCLE_WEEKLY The cycleDay is referred as a "day of month" (1-31) even when we change to CYCLE_WEEKLY. The calculations are expecting a day of week (1-7). Without this fix, we can fall into the (inconvenient) result of using a completely wrong value for the cycle limit (and corresponding data calculation) or the worse infinite loop that totally breaks data usage. Change-Id: I41e0012805236b14f5e0d52ec39683fe6a96f3aa --- core/java/android/net/NetworkPolicyManager.java | 1 + 1 file changed, 1 insertion(+) 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); } From 4dd526498092d0fffafaff4a13c59632fd22c09a Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Tue, 14 Oct 2014 13:38:17 -0700 Subject: [PATCH 10/86] Externally Reported Moderate Security Issue: SQL Injection in WAPPushManager Bug 17969135 Use query (instead of rawQuery) and pass in arguments instead of building the query with a giant string. Add a unit test that fails with the old code but passes with the new code. Change-Id: Id04a1db6fb95fcd923e1f36f5ab3b94402590918 --- .../com/android/smspush/WapPushManager.java | 30 ++++++++++++++----- .../smspush/unitTests/WapPushTest.java | 21 ++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) 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; From 2f7ee78e301637f72d7a16ff3de48cafe6922687 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Wed, 5 Nov 2014 12:18:05 -0800 Subject: [PATCH 11/86] AudioEffect JNI: use new max preprocessing constant Bug: 18226810. Change-Id: Ica5677da247268306b34dfce38f25394586817fd (cherry picked from commit b27a8a5bcc40054f6d775d070bc2de6eb996d1c2) --- .../audioeffect/android_media_AudioEffect.cpp | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) 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; From 95f4b0cd3c08240014e1b30a9634049e59c7c365 Mon Sep 17 00:00:00 2001 From: Roman Birg Date: Tue, 5 Aug 2014 10:40:23 -0700 Subject: [PATCH 12/86] frameworks: allow LockPatternView to be rotated When displaying this view and rotating the screen, mLockPatternUtils has the potential to be null, but the save and restore instance state methods depend on it to be initialized. Work around this by checking if it's null. Change-Id: I351bd63fefbcb92fffe20dca6a0381a20ac796ea Signed-off-by: Roman Birg --- .../com/android/internal/widget/LockPatternView.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 531563187a37..27267ec9c807 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -1084,7 +1084,7 @@ 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), mPatternDisplayMode.ordinal(), mPatternSize, mInputEnabled, mInStealthMode, mEnableHapticFeedback); } @@ -1093,9 +1093,11 @@ protected Parcelable onSaveInstanceState() { protected void onRestoreInstanceState(Parcelable state) { final SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); - setPattern( - DisplayMode.Correct, - mLockPatternUtils.stringToPattern(ss.getSerializedPattern())); + if (mLockPatternUtils != null) { + setPattern( + DisplayMode.Correct, + mLockPatternUtils.stringToPattern(ss.getSerializedPattern())); + } mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; mPatternSize = ss.getPatternSize(); mInputEnabled = ss.isInputEnabled(); From 5ac63d6dfcb5f11af4a9d8dc0f34a2bae0933058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Rivotti=20Casimiro?= Date: Wed, 1 Apr 2015 13:53:04 +0100 Subject: [PATCH 13/86] Fix set pattern screen after rotating the device 1/2 Allows the usage of a temporary pattern size while setting a new one. Change-Id: I3e62c2da4f290986f6295da8dee6900454b49141 --- .../internal/widget/LockPatternUtils.java | 25 ++++++++++++++++--- .../internal/widget/LockPatternView.java | 7 +++--- 2 files changed, 26 insertions(+), 6 deletions(-) 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 27267ec9c807..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 == null ? "" : mLockPatternUtils.patternToString(mPattern), + mLockPatternUtils == null ? "" : mLockPatternUtils.patternToString(mPattern, + mPatternSize), mPatternDisplayMode.ordinal(), mPatternSize, mInputEnabled, mInStealthMode, mEnableHapticFeedback); } @@ -1093,13 +1094,13 @@ protected Parcelable onSaveInstanceState() { protected void onRestoreInstanceState(Parcelable state) { final SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); + mPatternSize = ss.getPatternSize(); if (mLockPatternUtils != null) { setPattern( DisplayMode.Correct, - mLockPatternUtils.stringToPattern(ss.getSerializedPattern())); + mLockPatternUtils.stringToPattern(ss.getSerializedPattern(), mPatternSize)); } mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; - mPatternSize = ss.getPatternSize(); mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); mEnableHapticFeedback = ss.isTactileFeedbackEnabled(); From 1a8d78097609225c13da4d7216e344092e311a2f Mon Sep 17 00:00:00 2001 From: Wale Ogunwale Date: Thu, 25 Jun 2015 09:29:58 -0700 Subject: [PATCH 14/86] Prevent system uid component from running in an app process Bug: 21669445 Change-Id: I792c6e676d4b6d54a51228d264130b8125075d98 --- .../com/android/server/am/ActivityManagerService.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 2f24637ab000..e1856cfafffe 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2597,9 +2597,14 @@ final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean // should never happen). SparseArray procs = mProcessNames.getMap().get(processName); if (procs == null) return null; - final int N = procs.size(); - for (int i = 0; i < N; i++) { - if (UserHandle.isSameUser(procs.keyAt(i), uid)) return procs.valueAt(i); + final int procCount = procs.size(); + for (int i = 0; i < procCount; i++) { + final int procUid = procs.keyAt(i); + if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) { + // Don't use an app process or different user process for system component. + continue; + } + return procs.valueAt(i); } } ProcessRecord proc = mProcessNames.get(processName, uid); From c9a69c33ca0d3d4571719fb0ca1b6f978bef9e07 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 7 Feb 2014 09:16:12 -0500 Subject: [PATCH 15/86] Convert all selinux_android_restorecon and _setfilecon calls to new API. libselinux selinux_android_restorecon API is changing to the more general interface with flags and dropping the older variants. Also get rid of the old, no longer used selinux_android_setfilecon API and rename selinux_android_setfilecon2 to it as it is the only API in use. Change-Id: I1e71ec398ccdc24cac4ec76f1b858d0f680f4925 Signed-off-by: Stephen Smalley --- core/jni/android_os_SELinux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From b5aec5307edf35e635d125f87f5d614a84c8745a Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Wed, 10 Jun 2015 17:02:39 -0700 Subject: [PATCH 16/86] DO NOT MERGE Change to add STK_PERMISSION for stk related commands. Bug: 21697171 Change-Id: I7649c7341428194963ac74e9ae622dfa76ea738b --- core/res/AndroidManifest.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1c2cd208f044..b1399a0fc8f0 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" /> + + + From 7233c0464fb78f1373a5650c23d4d126ef1cce8a Mon Sep 17 00:00:00 2001 From: Kra1o5 Date: Sun, 27 Sep 2015 11:17:54 +0200 Subject: [PATCH 17/86] National roaming info for new Tuenti MNC in Spain Change-Id: Ic639bd7f2aa120caad28f5b8b430a86d1d39f2e6 --- core/res/res/values-mcc214-mnc32/config.xml | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 core/res/res/values-mcc214-mnc32/config.xml 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 + + From a3e498b75461e02171e37f13bbed75e80eb998ad Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 26 May 2015 16:41:09 -0400 Subject: [PATCH 18/86] Check that the parcel contained the expected amount of region data. DO NOT MERGE bug:20883006 Change-Id: Ib47a8ec8696dbc37e958b8dbceb43fcbabf6605b --- core/jni/android/graphics/Region.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp index f0a7baf8cb04..1e0cf96f02ae 100644 --- a/core/jni/android/graphics/Region.cpp +++ b/core/jni/android/graphics/Region.cpp @@ -175,9 +175,13 @@ static SkRegion* Region_createFromParcel(JNIEnv* env, jobject clazz, jobject par android::Parcel* p = android::parcelForJavaObject(env, parcel); + const size_t size = p->readInt32(); + const void* regionData = p->readInplace(size); + if (regionData == NULL) { + return NULL; + } SkRegion* region = new SkRegion; - size_t size = p->readInt32(); - region->readFromMemory(p->readInplace(size), size); + region->readFromMemory(regionData, size); return region; } From a3080c9370dfa525048c51f530c864c9213407bc Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 29 May 2015 16:13:11 -0400 Subject: [PATCH 19/86] DO NOT MERGE: Ensure that unparcelling Region only reads the expected number of bytes bug: 20883006 Change-Id: I4f109667fb210a80fbddddf5f1bfb7ef3a02b6ce --- core/jni/android/graphics/Region.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp index 1e0cf96f02ae..d901e9ced4bc 100644 --- a/core/jni/android/graphics/Region.cpp +++ b/core/jni/android/graphics/Region.cpp @@ -181,7 +181,12 @@ static SkRegion* Region_createFromParcel(JNIEnv* env, jobject clazz, jobject par return NULL; } SkRegion* region = new SkRegion; - region->readFromMemory(regionData, size); + size_t actualSize = region->readFromMemory(regionData, size); + + if (size != actualSize) { + delete region; + return NULL; + } return region; } From 8e445a793a3faff845e641523fca4eba2626211f Mon Sep 17 00:00:00 2001 From: Sebastien Hertz Date: Mon, 10 Aug 2015 18:55:34 +0200 Subject: [PATCH 20/86] Allow debugging only for apps forked from zygote DO NOT MERGE When starting the runtime from app_process, we only pass JDWP options if starting zygote. It prevents from opening a JDWP connection in non-zygote programs while Android apps (forked from zygote) remain debuggable. Bug: 23050463 (cherry picked from commit 7a09b8322cab26d6e3da1362d3c74964ae66b5d4) Change-Id: I2400ecc8aea7579c43300efccf288b69f70eef53 --- cmds/app_process/app_main.cpp | 4 ++-- core/jni/AndroidRuntime.cpp | 20 ++++++++++++-------- include/android_runtime/AndroidRuntime.h | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 28752a5315ad..cb27c5bb7e07 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -221,14 +221,14 @@ int main(int argc, char* const argv[]) if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", - startSystemServer ? "start-system-server" : ""); + startSystemServer ? "start-system-server" : "", zygote); } else if (className) { // Remainder of args get passed to startup class main() runtime.mClassName = className; runtime.mArgC = argc - i; runtime.mArgV = argv + i; runtime.start("com.android.internal.os.RuntimeInit", - application ? "application" : "tool"); + application ? "application" : "tool", zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index bcc87d556b57..9e09ae6e2533 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -432,7 +432,7 @@ void AndroidRuntime::parseExtraOpts(char* extraOptsBuf) * * Returns 0 on success. */ -int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) +int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) { int result = -1; JavaVMInitArgs initArgs; @@ -633,11 +633,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) } } - /* enable debugging; set suspend=y to pause during VM init */ - /* use android ADB transport */ - opt.optionString = - "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y"; - mOptions.add(opt); + /* + * Enable debugging only for apps forked from zygote. + * Set suspend=y to pause during VM init and use android ADB transport. + */ + if (zygote) { + opt.optionString = + "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y"; + mOptions.add(opt); + } ALOGD("CheckJNI is %s\n", checkJni ? "ON" : "OFF"); if (checkJni) { @@ -795,7 +799,7 @@ char* AndroidRuntime::toSlashClassName(const char* className) * Passes the main function two arguments, the class name and the specified * options string. */ -void AndroidRuntime::start(const char* className, const char* options) +void AndroidRuntime::start(const char* className, const char* options, bool zygote) { ALOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", className != NULL ? className : "(unknown)"); @@ -828,7 +832,7 @@ void AndroidRuntime::start(const char* className, const char* options) JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; - if (startVm(&mJavaVM, &env) != 0) { + if (startVm(&mJavaVM, &env, zygote) != 0) { return; } onVmCreated(env); diff --git a/include/android_runtime/AndroidRuntime.h b/include/android_runtime/AndroidRuntime.h index 0b3ce9a261b4..e8e869da1e91 100644 --- a/include/android_runtime/AndroidRuntime.h +++ b/include/android_runtime/AndroidRuntime.h @@ -64,7 +64,7 @@ class AndroidRuntime int addVmArguments(int argc, const char* const argv[]); - void start(const char *classname, const char* options); + void start(const char *classname, const char* options, bool zygote); void exit(int code); @@ -116,7 +116,7 @@ class AndroidRuntime private: static int startReg(JNIEnv* env); void parseExtraOpts(char* extraOptsBuf); - int startVm(JavaVM** pJavaVM, JNIEnv** pEnv); + int startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote); Vector mOptions; bool mExitWithoutCleanup; From 326c5910bf822780610b1bd554e8ff5f55f2d0fb Mon Sep 17 00:00:00 2001 From: Zach Jang Date: Fri, 11 Sep 2015 16:35:04 -0700 Subject: [PATCH 21/86] DO NOT MERGE - Backport of ag/748165 to klp-dev Security patch level in Settings b/23946860 Change-Id: I610d4dedf18fe1825d7df5febf29e6f0c006490d (cherry picked from commit bdf7f3583c637f370e97e74526df14b9c70e6493) --- core/java/android/os/Build.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index c525bb8ffbaf..81c65d3fc51b 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -89,6 +89,21 @@ public static class VERSION { */ public static final String RELEASE = getString("ro.build.version.release"); + /** + * The base OS build the product is based on. + * For Pre-API 23 - use support libs to access. + * @hide + */ + public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", ""); + + /** + * The user-visible security patch level. + * For Pre-API 23 - use support libs to access. + * @hide + */ + public static final String SECURITY_PATCH = SystemProperties.get( + "ro.build.version.security_patch", ""); + /** * The user-visible SDK version of the framework in its raw String * representation; use {@link #SDK_INT} instead. From 241b87f702a6f956a8138519a8390326efb115e9 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 11 Mar 2015 13:12:06 -0400 Subject: [PATCH 22/86] Make Bitmap_createFromParcel check the color count. DO NOT MERGE When reading from the parcel, if the number of colors is invalid, early exit. Add two more checks: setInfo must return true, and Parcel::readInplace must return non-NULL. The former ensures that the previously read values (width, height, etc) were valid, and the latter checks that the Parcel had enough data even if the number of colors was reasonable. Also use an auto-deleter to handle deletion of the SkBitmap. Cherry pick from change-Id: Icbd562d6d1f131a723724883fd31822d337cf5a6 BUG=19666945 Change-Id: Iab0d218c41ae0c39606e333e44cda078eef32291 --- core/jni/android/graphics/Bitmap.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 90ae0de674c0..fd1580696cc9 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -488,24 +488,33 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { return NULL; } - SkBitmap* bitmap = new SkBitmap; + SkAutoTDelete bitmap(new SkBitmap); - bitmap->setConfig(config, width, height, rowBytes); + if (!bitmap->setConfig(config, width, height, rowBytes)) { + return NULL; + } SkColorTable* ctable = NULL; if (config == SkBitmap::kIndex8_Config) { int count = p->readInt32(); + if (count < 0 || count > 256) { + // The data is corrupt, since SkColorTable enforces a value between 0 and 256, + // inclusive. + return NULL; + } if (count > 0) { size_t size = count * sizeof(SkPMColor); const SkPMColor* src = (const SkPMColor*)p->readInplace(size); + if (src == NULL) { + return NULL; + } ctable = new SkColorTable(src, count); } } - jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable); + jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable); if (NULL == buffer) { SkSafeUnref(ctable); - delete bitmap; return NULL; } @@ -517,7 +526,6 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { android::status_t status = p->readBlob(size, &blob); if (status) { doThrowRE(env, "Could not read bitmap from parcel blob."); - delete bitmap; return NULL; } @@ -527,8 +535,8 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { blob.release(); - return GraphicsJNI::createBitmap(env, bitmap, buffer, getPremulBitmapCreateFlags(isMutable), - NULL, NULL, density); + return GraphicsJNI::createBitmap(env, bitmap.detach(), buffer, + getPremulBitmapCreateFlags(isMutable), NULL, NULL, density); } static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, From c85cddfb9763f7f3b1902786f8cc39eea0368a3b Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 19 Mar 2014 18:06:58 -0700 Subject: [PATCH 23/86] Get rid of not specifying a user errors in bluetooth. * LordNerevar: Ported from LP. Includes fix for additional KK files. * W/ContextImpl( 1772): Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1559 android.content.ContextWrapper.bindService:513 android.bluetooth.BluetoothInputDevice.doBind:262 android.bluetooth.BluetoothInputDevice.:255 android.bluetooth.BluetoothAdapter.getProfileProxy:1365 W/ContextImpl( 1772): Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1559 android.content.ContextWrapper.bindService:513 android.bluetooth.BluetoothPan.doBind:148 android.bluetooth.BluetoothPan.:140 android.bluetooth.BluetoothAdapter.getProfileProxy:1368 W/ContextImpl( 1772): Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1559 android.content.ContextWrapper.bindService:513 android.bluetooth.BluetoothMap.doBind:108 android.bluetooth.BluetoothMap.:101 android.bluetooth.BluetoothAdapter.getProfileProxy:1374 W/ContextImpl( 1772): Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1559 android.content.ContextWrapper.bindService:513 android.bluetooth.BluetoothPbap.doBind:163 android.bluetooth.BluetoothPbap.:156 com.android.settings.bluetooth.PbapServerProfile.:68 Change-Id: I0a1e24ee71aef7d796fcee5692b9d19462a93637 --- core/java/android/bluetooth/BluetoothA2dp.java | 3 ++- core/java/android/bluetooth/BluetoothDun.java | 3 ++- core/java/android/bluetooth/BluetoothHandsfreeClient.java | 6 ++++-- core/java/android/bluetooth/BluetoothHeadset.java | 3 ++- core/java/android/bluetooth/BluetoothHealth.java | 3 ++- core/java/android/bluetooth/BluetoothHidDevice.java | 3 ++- core/java/android/bluetooth/BluetoothInputDevice.java | 3 ++- core/java/android/bluetooth/BluetoothMap.java | 3 ++- core/java/android/bluetooth/BluetoothPan.java | 3 ++- core/java/android/bluetooth/BluetoothPbap.java | 3 ++- core/java/android/bluetooth/BluetoothSap.java | 3 ++- 11 files changed, 24 insertions(+), 12 deletions(-) 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/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/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; } From f56bdd4901a9e39cd739807275c2ce641da417b6 Mon Sep 17 00:00:00 2001 From: Sangkyu Lee Date: Thu, 9 Jan 2014 14:11:57 +0900 Subject: [PATCH 24/86] Fix graphics corruption caused by HWUI caches Some caches(PatchCache, TextureCache, PathCache) for HWUI uses deferred removal for their cache entries even though actual resource objects are immediately freed by ResourceCache. For this reason, the uniqueness of a resource address in the caches is not guaranteed in specific cases. (Because malloc() can return the same address when malloc() and free() called very frequently.) So it can be possible the cache have two cache entries for two different resources but the same memory address. (Of course one of the resources is already freed.) It also can be possible mGarbage vector in PatchCache has duplicated addresses and this can lead to duplicated free blocks in the free block list and graphics corruption. (Deferred removal was implmeneted based on an assumption of unique resource addresses.) So this patch makes sure resource objects are freed after the resources are removed from the caches to guarantee the uniqueness of a resource address and prevent graphics corruption. Change-Id: I040f033a4fc783d2c4bc04b113589657c36fb15b Signed-off-by: Sangkyu Lee --- libs/hwui/PatchCache.cpp | 6 +++++- libs/hwui/PathCache.cpp | 4 +++- libs/hwui/ResourceCache.cpp | 30 ++++++++++++++++++------------ libs/hwui/TextureCache.cpp | 4 +++- 4 files changed, 29 insertions(+), 15 deletions(-) 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..a9ab2c63435b 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)); + const SkBitmap* bitmap = mGarbage.itemAt(i); + mCache.remove(bitmap); + delete bitmap; } mGarbage.clear(); } From 69655e6837fe1f63682b592f74f9c32f5f50f936 Mon Sep 17 00:00:00 2001 From: Darren Smith Date: Fri, 8 Jan 2016 14:43:32 -0600 Subject: [PATCH 25/86] Fix previous patch to compile with out compiler settings Change-Id: I4bcfd2efc379d347df72425f30484d16e6dbae65 --- libs/hwui/TextureCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index a9ab2c63435b..d5ba8c3008d5 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -184,7 +184,7 @@ void TextureCache::clearGarbage() { Mutex::Autolock _l(mLock); size_t count = mGarbage.size(); for (size_t i = 0; i < count; i++) { - const SkBitmap* bitmap = mGarbage.itemAt(i); + SkBitmap* bitmap = mGarbage.itemAt(i); mCache.remove(bitmap); delete bitmap; } From 93b6827a1b048f65ec7e4f19f51814d1ac6b1a0a Mon Sep 17 00:00:00 2001 From: Shreyas Basarge Date: Thu, 12 Nov 2015 15:32:44 +0000 Subject: [PATCH 26/86] Sync extras bundle comparison can throw NPE Bug: 23591205 Change-Id: I960dfcc1584c0a17685790d5d722eaf11b930e25 (cherry picked from commit 2f137b7705875ee026319abb8e512d918c118222) --- core/java/android/content/PeriodicSync.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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; } } From 274768f82f0d8d10e26359322a8255b761e33bd9 Mon Sep 17 00:00:00 2001 From: Roman Birg Date: Tue, 27 Oct 2015 11:52:53 -0700 Subject: [PATCH 27/86] AppOps: fix wifi scan op There's no direct permission tied to it and fix the op-to-switch entry. Change-Id: I661ef6707ba50adb371e3223a91880c4838df669 Signed-off-by: Roman Birg (cherry picked from commit 72a1fbe5f76ef6c5e6fb9cd56d4bd1aee940303b) --- core/java/android/app/AppOpsManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, From b0de4596c6cd56b182c8c4d9e7775e49c6b2829a Mon Sep 17 00:00:00 2001 From: Robert Craig Date: Mon, 2 Dec 2013 10:24:23 -0500 Subject: [PATCH 28/86] Augment SELinuxMMAC functionality. * No longer support a package name stanza outside of a signature tag. Package names, by themselves, have no security associated with them in Android and thus we should not be allowing or encouraging this type of policy. * Allow for nested package name stanzas inside signature stanzas. There are cases where a finer distinction needs to be made among apps signed with the same cert. New code allows a different seinfo tag to be assigned to the listed package names signed by the parent cert. When a determination needs to be made concerning seinfo assignments, the inner seinfo tag takes precedence over the outer seinfo labels which are assigned to just the signature. * Temp structures are now used to parse new policy files until the entire xml file is parsed and deemed correct, at which time the temp structures are copied over to the permanent class structures. This ensures that any structural errors with the policy will not result in partial loads. * Valid stanzas look like the following with the inner package piece being optional. Change-Id: Ia204d71211776dcf9b2dcc86ad6d77c4ad39dc25 --- .../com/android/server/pm/SELinuxMMAC.java | 248 ++++++++++++------ 1 file changed, 174 insertions(+), 74 deletions(-) diff --git a/services/java/com/android/server/pm/SELinuxMMAC.java b/services/java/com/android/server/pm/SELinuxMMAC.java index 04f43d9676f7..1d68afa546e8 100644 --- a/services/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/java/com/android/server/pm/SELinuxMMAC.java @@ -48,12 +48,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 +60,45 @@ public final class SELinuxMMAC { new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"), null}; + // 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 +122,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 +146,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 +175,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 +228,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 +347,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 +367,28 @@ 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); } } From 8fdbb432e9e574c5d1f0dac4bf89e3c319f3a85f Mon Sep 17 00:00:00 2001 From: Robert Craig Date: Tue, 4 Mar 2014 11:57:23 -0500 Subject: [PATCH 29/86] Allow PMS to restorecon directories under /data. This change applies a relabel to both /data/data and /data/user directories on boot. Not every boot will apply this relabeling however. The appropriate seapp_contexts is hashed and compared to /data/system/seapp_hash to decide if the relabel should occur. Change-Id: I05e8b438950ddb908e46c9168ea6ee601e6d674f Signed-off-by: rpcraig --- .../java/com/android/server/pm/Installer.java | 4 + .../server/pm/PackageManagerService.java | 7 ++ .../com/android/server/pm/SELinuxMMAC.java | 97 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index adc38cadbbbb..a528f931f49a 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -501,4 +501,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..e97122976843 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()); diff --git a/services/java/com/android/server/pm/SELinuxMMAC.java b/services/java/com/android/server/pm/SELinuxMMAC.java index 1d68afa546e8..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; @@ -60,6 +65,13 @@ 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; @@ -391,4 +403,89 @@ public static boolean assignSeinfoValue(PackageParser.Package pkg) { 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 + } + } } From 131777d8fcd76ee4d1b01769b8eb9a4a12ef963b Mon Sep 17 00:00:00 2001 From: Robert Craig Date: Mon, 29 Jul 2013 09:06:51 -0400 Subject: [PATCH 30/86] Proper security labeling of multi-user data directories. This patch covers 2 cases. When an app is installed and the resulting data directory is created for all existing users. And when a new user is created and all existing app data directories are created for the new user. Change-Id: Iaba7c40645bc7b6cc823d613da0c3782acf6ddd5 Signed-off-by: rpcraig --- services/java/com/android/server/pm/Installer.java | 4 +++- .../java/com/android/server/pm/PackageManagerService.java | 2 +- services/java/com/android/server/pm/Settings.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index a528f931f49a..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()); } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index e97122976843..6acd4ae4abc9 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -4109,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/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); From 9ebc1c32de2fb45377c1c19da4e3fc1a6af16019 Mon Sep 17 00:00:00 2001 From: Chris Lei Date: Thu, 31 Oct 2013 16:24:58 -0700 Subject: [PATCH 31/86] Telephony: Update TD-SCDMA Network Type String - Updating the string used to identify TD-SCDMA network type to be consistent with the ServiceState string. Change-Id: I5aee8728ee80dc199f37414aba9019523082862c CRs-Fixed: 564062 --- telephony/java/android/telephony/TelephonyManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c05686aa882f..2b921e184242 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -848,7 +848,7 @@ public static String getNetworkTypeName(int type) { case NETWORK_TYPE_GSM: return "GSM"; case NETWORK_TYPE_TD_SCDMA: - return "TD_SCDMA"; + return "TD-SCDMA"; default: return "UNKNOWN"; } From de92d2f00b9b6f243b4e4d08f782648c09ac6ef6 Mon Sep 17 00:00:00 2001 From: Rakesh Pallerla Date: Wed, 6 Feb 2013 11:44:23 +0530 Subject: [PATCH 32/86] Telephony: Add functionality to handle ICC IO error At present in Android all ICC Card states other than ICC PRESENT are treated as ICC ABSENT.Adding functionality to handle ICC IO error card state. Change-Id: I229bd80cb5e487f4345bef9fb7fee60850f085d9 CRs-Fixed: 184479 (cherry picked from commit 5f414649d354f5b5f3208524e9827d488d183c76) (cherry picked from commit fa5fb0852573189c107847da711ed61489cf92b5) (cherry picked from commit 49e48d07a9cb7660d65ee3de30cf6e9f05bbc0ec) --- .../systemui/statusbar/phone/PhoneStatusBarPolicy.java | 3 +++ telephony/java/android/telephony/TelephonyManager.java | 8 ++++++++ .../com/android/internal/telephony/IccCardConstants.java | 7 +++++-- 3 files changed, 16 insertions(+), 2 deletions(-) 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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2b921e184242..a80d24f4c2a9 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -875,6 +875,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 +905,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 +924,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/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)); } } } From 4993c587c144d85cdec542886ab35dca3b2ad9ea Mon Sep 17 00:00:00 2001 From: Alex Yakavenka Date: Tue, 15 Oct 2013 12:47:11 -0700 Subject: [PATCH 33/86] Telephony: Add data rat constant This event constant is used in DcTracker Change-Id: I1f9b34a8689c43c2586e9e3a92f0453649775d73 (cherry picked from commit f7ca98035479b670ff51bc9298fd7928a5e0f80d) (cherry picked from commit f4d8f7b8fa390302b9cc51a5640e017ff393dfcc) (cherry picked from commit 77d810390fbca8cb1d10bce148035fada414e0eb) --- telephony/java/com/android/internal/telephony/DctConstants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 4be11b879230..4b2c8ffddbd4 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -97,6 +97,7 @@ 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; /***** Constants *****/ From bc113dec1534d359f8afb07c5e026dd57af46e15 Mon Sep 17 00:00:00 2001 From: Susheel nyamala Date: Mon, 7 Oct 2013 21:43:15 +0530 Subject: [PATCH 34/86] Property constants to support OMH Add static constant variables required for OMH support Change-Id: Idfeb2f3f1a3aa0403a7ff0ab7c69fc945d9e7083 (cherry picked from commit cf61f807ee07b4497da78c6bb753a835fa46b5b5) (cherry picked from commit 8e68d056ca7c14fdefecbcf9b2b69048f092754a) (cherry picked from commit 4a3b31e38c92bb1943c9290d8a4dd535b42bbd07) --- .../java/com/android/internal/telephony/DctConstants.java | 4 ++++ .../java/com/android/internal/telephony/RILConstants.java | 1 + 2 files changed, 5 insertions(+) diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 4b2c8ffddbd4..b030a3835e24 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. @@ -98,6 +101,7 @@ public enum Activity { 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; /***** Constants *****/ 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; From a6d1aef276ded4b471fdf2ddf1aecca422931453 Mon Sep 17 00:00:00 2001 From: Yashdev Singh Date: Fri, 4 Oct 2013 17:24:57 -0700 Subject: [PATCH 35/86] Telephony: Handle DATA_CALL_LIST in DCC. - Added async handling of NetStatPoll. Change-Id: Ic77bb949ffb72fb3c54bacc6ad2a6ee87e877023 --- telephony/java/com/android/internal/telephony/DctConstants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index b030a3835e24..2925d3ff618f 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -102,6 +102,7 @@ public enum Activity { 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; /***** Constants *****/ From 1e25cea155ee76221093e4341bf4294fa89529a6 Mon Sep 17 00:00:00 2001 From: Stacy Devino Date: Tue, 14 Oct 2014 18:08:23 -0500 Subject: [PATCH 36/86] MTU should be 1358 as per 3GPP standards, especially for LTE radio interfaces. Change-Id: I5d3601b05a41d4c59cff3b39ddfb66d00cb23c45 Signed-off-by: Stacy Devino --- core/res/res/values/config.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3309bb100c30..16d50f426d19 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 + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 22de76a43001..02119a9d9643 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -289,6 +289,7 @@ + diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5150b709e796..57a64110f0a8 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. */ @@ -544,6 +548,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); @@ -864,7 +871,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 a80d24f4c2a9..6a721fbb211b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -673,7 +673,8 @@ 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; /** * @return the NETWORK_TYPE_xxxx for current data connection. @@ -795,6 +796,7 @@ public static int getNetworkClass(int networkType) { case NETWORK_TYPE_TD_SCDMA: return NETWORK_CLASS_3_G; case NETWORK_TYPE_LTE: + case NETWORK_TYPE_IWLAN: return NETWORK_CLASS_4_G; default: return NETWORK_CLASS_UNKNOWN; @@ -849,6 +851,8 @@ public static String getNetworkTypeName(int type) { return "GSM"; case NETWORK_TYPE_TD_SCDMA: return "TD-SCDMA"; + case NETWORK_TYPE_IWLAN: + return "IWLAN"; default: return "UNKNOWN"; } diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 2925d3ff618f..4329bf049241 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -103,6 +103,7 @@ public enum Activity { 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 *****/ From a307587d300b7f11498eee077672f5f7f265c396 Mon Sep 17 00:00:00 2001 From: Daniele Di Conza Date: Fri, 6 Dec 2013 15:10:36 +0100 Subject: [PATCH 38/86] frameworks/base: forward port DC-HSPAP support from cm-10 PS10: add new drawables, I need to add new QS drawables and also RTL support PS11: fix blank space and prevents checkapi to fail PS13: fixed tile icon and rebase Change-Id: I1503c464b2db841fc193e57311e62415f5ff40f2 --- .../android/net/MobileDataStateTracker.java | 5 +++++ core/java/android/os/BatteryStats.java | 5 +++-- .../android/internal/os/BatteryStatsImpl.java | 3 +++ .../res/drawable-hdpi/ic_qs_signal_dc.png | Bin 0 -> 17499 bytes .../drawable-hdpi/ic_qs_signal_full_dc.png | Bin 0 -> 17417 bytes .../stat_sys_data_fully_connected_dc.png | Bin 0 -> 474 bytes .../res/drawable-mdpi/ic_qs_signal_dc.png | Bin 0 -> 17366 bytes .../drawable-mdpi/ic_qs_signal_full_dc.png | Bin 0 -> 17318 bytes .../stat_sys_data_fully_connected_dc.png | Bin 0 -> 310 bytes .../res/drawable-xhdpi/ic_qs_signal_dc.png | Bin 0 -> 17586 bytes .../drawable-xhdpi/ic_qs_signal_full_dc.png | Bin 0 -> 17521 bytes .../stat_sys_data_fully_connected_dc.png | Bin 0 -> 483 bytes .../res/drawable-xxhdpi/ic_qs_signal_dc.png | Bin 0 -> 17782 bytes .../drawable-xxhdpi/ic_qs_signal_full_dc.png | Bin 0 -> 17498 bytes .../stat_sys_data_fully_connected_dc.png | Bin 0 -> 867 bytes packages/SystemUI/res/values/slim_strings.xml | 3 +++ .../statusbar/policy/NetworkController.java | 7 +++++++ .../statusbar/policy/TelephonyIcons.java | 18 ++++++++++++++++++ .../server/location/GpsLocationProvider.java | 3 ++- .../telephony/NeighboringCellInfo.java | 9 +++++++-- .../java/android/telephony/ServiceState.java | 7 +++++-- .../android/telephony/TelephonyManager.java | 10 +++++++++- 22 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 packages/SystemUI/res/drawable-hdpi/ic_qs_signal_dc.png create mode 100644 packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_dc.png create mode 100644 packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_dc.png create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_qs_signal_dc.png create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_dc.png create mode 100644 packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_dc.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_dc.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_dc.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/stat_sys_data_fully_connected_dc.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_dc.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_dc.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_fully_connected_dc.png diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 7229627bf67e..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; @@ -864,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), @@ -897,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/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/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/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 0000000000000000000000000000000000000000..04ad347b6dfba0070c49fbf76c4a0333f242205a GIT binary patch literal 17499 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e9b8243c8f21885e4bd29f3bae4660792d3c3cf6 GIT binary patch literal 17586 zcmeI4du$ZP8NkOTsfrsy1GI@~5;o^Xgwnlz-0eM<^BL>|Y?ZT(ePBa^%6fOs-umwL z*j?La7ZegIh}%>_Bx&oWM2aga(@IDrZ7PyKNFPQdZ7LL1G^L10Z9@_ooJgtCHmO8s z?>W8);}rVTobcVp%=en#%zQh$f9yn8$Hqks%Nqayi`v^F-RO6%^Y`(Oq3`eOyDp%g zh3U3!766}E>ipG#Z-3`D09rQdu|B&mx=B`zq+8L9e(265(?}bDP;(}&s6)_Z`r&|{ zYI42v{7Dz1YfY|RDauFFt#D9p8#iIkct=bfA5w#wt2x{d%E-t;650xrNhVU3oN01p z{mMvonmHGfRk4SfTL`Y&W@FPN3o^Kdxjt8ZCxOS$TShBbQOqoZ#lU zWRk09Y1!Sdlw?&)D>jyfTsO3gVN-=jy|KEZ^&=>|{AlOlX#EIG*;QSkIlH=}YVkF- zXpHzEOuuhRts$F@frY~o|yo`BPZd-_>kkHD zh^sI!<%9x+a@|_xT+I(=g?Tma#vQ4*a!yLmpoZ&k~s|W6y z+gj*I=<}ST;!L6XA}gv>4#Qe*rkx4ET1!diY@ARz1+)?rRPUi_g;gUp0!G@|X z>HpED%+=SqCxODTqVnY696{w)6DsIL5i^Vg;hIpcUD`V(M=+w;ubURzo&grH_8JOEz==i8dx>7pXSRLqy7LK&yX_ngO zq=5oDG0W_oq^sFy+so~*?=`eCJK^?sC&@S2xAG%S}s`TT;3m7W2`{nlUTq z?mD+Cde9Rk81_k$M+$M}&E*zddIl!?B09S1wwyZU-*)vDNrxs7q1ze^*!&W>?+Gzd9_AMMQ)vP+T|-qD*lS5#b6H7fypHQ(QzuxB|t6(;&(e7ZDMzKyl$Th%&`RM1(6) zTsRG)OmPtr;R+NNPJ<{@Ttq~;0>y>XAj%XM5fQFHap5$GGQ~wigey>7I1QpqaS;*W z3KSPkgD6v6L`1j(#f8%#$`lt75w1XS;WUUc#YIGfD^OfG4WdkO5fR}E6c9_AMMQ)vP+T|-qI^|c4W;))z!Z8p#3*_T#89Mp9eRTV zqqcQN0T{mcFKfHHj<<~BrTKUkO zXF$f++c>)t%-*r8`BQ(|8sbJnmtRWkGMirLH>Og**)jS0)Ulptp7_zbC%69OwTTyo zz#}dz4$eOH_}&XIPM+Taj`taly>r34`K=Y&;^{5T58Y9BIkD&LD|Y8|HzeLac6Ori zRqk`jOFF;m?bQo@xO+xjvvBs7>0O^@K>eFDhq~(ieD}WNowN5{I`jB%V~20Obn^DE zeo&u2y6map>3x@XAFMxpVEH2--t$0g(VBauz=^ec&dnS&KUfWROdk2h!gtr-`0MH8 zy&b!IFPe30UR-ke-hV$&FW;vhUT{Jq!eGJiew-VdgyA3P0`_da8N z{f&)}edf{G6E~h8ip8G4OH2Rs{{5LHp9S?Op^x@&@BN7t3qa@oq2A3$j4wHF6=`4J L5jobf^TB@r7>`$o literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f5d8fde98e120de062b68b63a4168a7ea45da8cb GIT binary patch literal 17521 zcmeI4e{3699l)AC0m__Bx@1`q|bNH$*H#Q zdUuJPltS8d)h19!2W(9R?8hHtEutGk`=dmo1&NJGv_b-gR3Q)`wz3uIRt>@UqrCI^ z>=)bV%V_zjXEpY>?|py0&wKBC@9vL#V4(lbH680Z7=~HX*P9+hznk5^_IIK06Z)QC zp`Uk~y<;}R-1vU?Zv}Jgs~=*R?vLxkqt0k%yP_I}5YUV)43!He(q@>%rm_jt-OypP za6&I81AksP9bk1W85of=e8%j7lX~y01&3z)ht=8LYFrC!N_8a43UW|@4q(fLe9>0Q z$w0-gf@HUu3$PUxXLmBN#SO@gW(L?E!-A|7l7cEPMA%q7BuGL;7H(%nUI=r1m=nT5 zK~(s-B1mlY6G(L+DPd_jWiY+18V>z08JKh&Q{lK$sT3-OLxwfM3GsNG<3&ysgGeK2 z&lDX{4i@c=o)h0s8rrI*n~rW2S=TSf8dFX(5O4$4KGks*%uJ>hs%Tf~A&y)bn4A#e zxk7+cGb}?!_n4hSae#tLUVR?N3G&( zYR$%+vho$sn##dEETA|xVj#2xU``s2VNV)MgsgQ}AhrtZ=(!~Tx;;yp!Lq)DncL%; zRo0X@)d>V8lTmh9I$Hi9pElGfcU*nx6zT|Rx`x)GEXAa7PgD}cs4NI_OxPxeWj-c! zNBM{xOK?lNsIRDMm=^BksH{fgG7QFIaW$CBML(x;)e%`ys= z4Pye!hVJ4`2K?1q+hJ9zmF2JK%{j?xE-Xh?h;qt_!ALeD2D3Rn8_a4T3Zp=c0{|0T zlX*QS6d>VsYt3`DJeW=9t-Kr8vLd_)EnL(i`QS0Q&C9Y^w;jWp!8+XGyG3gV?pfUG zcjWbD&QWcqP<>H=>XyTl=FPM_0a$B2Y0t(DmzzxAo7qw~?t(5>j9OkVtWP)=Co!9>bW3~ZW+kOgF4vpdKBW|^% zm$idEV3_&RF-nuLm_paYNRStUqBJb>N?20D{B69T@VuXDMQdp%wvls6z=B&QP&rF^ z7vTZFCbK;~>@6dDQ8P;Rt-iy>7E60pCqZ!nYAMb)^cxyY{;MZ#nRUqB2LJ4&MI?on z=%m%EORYrs3x~Mdu!>NrDuhDf)|y)3@q=JK{*4Rt`c=~ZoiSf?C0*Wu<=QIg|Iw!O z>g(c@fPbuLJ~_BYP^HI$0G%j?4I@vuCNyf-_fFjrOalitT&cu}B1Qe8S99WNHu%UG znx30!;bYMpxjrz>Mjz;IQv(iImKR(X^P{Os&aeu=NnypK-iT?|jx0)#kvA-q7EFZ` zT&w1mR>QFDYzH&$d8yv-9xmL|O*fcW-0F9{(<5Cyol2~JbVN&~d+;>t?TgYt0o|Al z_HNRx>?`e!_Sg5C-;halB&=b%Cy@W-fV^QYo-sV1mJ^+DZ(QA5O>`ow9EMXuf@^B4 z6ZP~*-FvM3RONgg6M6LE1oyjYig!1fj$Pdp?`|}$Ti#RDofNuHtR&jdj9IyN*WRue zLQj-nDk@1~DZw>1H(CtnWtbmL>*%K2cJDxp1=YTAb0#<SC@!1^QKqEFKi zY}b*y($7rHeE-~iO#7}^t^1D-t-KI>;c4dKo4dT?7Hgo?k!N~k$>c_S-tDf1-WR5fMecGL;VTzKC%w>*FO@ymN}xU>#j*?HoIPX4*vinkyzODV~lij-x`~!d8I>`V4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..037c1bd80fd48c08cee4621d95e135c4ebb9b7ab GIT binary patch literal 483 zcmeAS@N?(olHy`uVBq!ia0vp^>Oic*!3HF`%#~*YDYhhUcNd2LAh=-f^2tCE&H|6f zVg?31We{epSZZGe6l5>)^mS#w$0#JKBJVo)4M-hNW=KSdbAE1aYF-JD%fPT_-KLog z42%(;E{-7)hu==!@5SsW&|d#oM$wEbDq;mk*uR6~0^HVuIyo(Sn)?$nS9a_b`^C3t zMQ`tdq)dGl8yyj*ES|z*``NuZ-jhyQH=ntF=G>ciA2%L9$8^lzWlr&`Rm&M(WXm(i zF~oG7DBaWegr`B5L7SnC(T2%FWa8cU?ZxL*-<(TT*(Pw-TK;~KTE9Y7j@g84OqhW;Lh^L z=XQRIKe6?aiRdIzhS~eJy?-p*z{arRRp^!fWnCGnhNhDiHy#Nzxu0kL-u3j{i8|#K z`@UJN`FrMdu4w37-h0>5rltC?y7gjP=M$EON`_{JxJ^2{L$4mn+n6ZP21ah``xxJv Vh9=59zA*z7)}F3@F6*2UngGhi$=Uz_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..53dfb394f998ce65a68e487eb6af974b95cdc6d2 GIT binary patch literal 17782 zcmeI4dvFuS8Nk;xAz&IN6FLo)Nl@fKD2;q~l1@6^$O>4(*np@pZZNdPobOih6-lR_ z&PaB^p=2O+CImvmqX`KR9w9BI4NVHvOhelt%#a7cnNmOyCLs-lHn=G*DTS2o>A99= z)-AM6I;R;+pLf5SZtwoMj@oIHZY}*%DFEQsnrdGi`kkx)4H}5P-&-5!(9hs# z^{f~GLx$;pCE%%b!vUzAF4s3`4gRUTsD#S}Noj)RiEtEY1K_?t5f#LFP%|~bpd6_% zcXz&GHpxJ*Dmhj~s-dCYTc8P$b4DD$FUr zJd*Wh%4|xhX!9z}6ZC+l27j%|tEkY#ma`U-X6z=XtDIpOJICB-veJx=qHPpovoKbk zcJT~r%6!b8QY5)mDZtnHsxslwe--9tO^fmr6_3ZuMp_4{+7BC!lT#F0t}QHm+2 zsc@JoW*O7!V1AHAEo1eqQApLnn9`z(5UH0HcQkQ68eL|#{ctpKK8$EZU7=U`-D60Z{Mh(+no$KFsu< zv{@>r(o5||Q2c)WtE!CBUkLdWu|=O(jn9KR$|YGsxyZ3j*5-AvR;zp=|IlJf(v#v4oUaoymmFGjL zN>#$C4HFgA7}S%u!ko?C+z!i7O{G6C_a2kg>0)WtCR!MlWi3rMyB$qefM{UQ5n!N8 zvI#af)yq6TCKSM(?v{G5RsUf2GB4&`xt@$jM=0VVA1MnSgWEeTtL2!csI6FsK7GAt z&4AzT*_!PL$^BfT+)APP!V98a4n0zOrS%2CTJwWW+vuUv3MdlrjfP}V&_hu3BT}Y{=1M>|9OkVsq?^(w9g_b8bB+lpx3fdY za3~C)pK+0t?r+lRL^1QeW5k2|~$3Weu|pg(jcoMeA=J zx}HU|+ju)`ELyI*j}^U74*C(4_o`4pCyIJS2@$Rdh1&VOlXnDtf(8q&R8~9B zIQ7h2TH2^ebd$>^p#rr&|M92uL!j-rOM`ZB~i zGKT3D!=C-5_FB3XJy}8zwW!=vtzO$Oy)ka^cYa1U*fH+T2!hif$_6eXB3!}1h0`F) z1}-8ZT*1JF(;&(QE+Qga!N7&nAj$?VA|hPDz=hKw$_6eXB3!}1h0`F)1}-8ZT*1JF z(;&(QE+Qga!N7&nAj$?VA|hPDz=hKw$_6eXB3!}1h0`F)1}-8ZT*1JF(;&(QE+Qga z!N7&nAj$?VA|hPDz=hKw$_6eXB3!}1h0`F)1}-8ZT*1JF(;&(QE+Qga!N7&nAj$?V zA|hPDz=hKw$_6eXB3!}1h0`F)1}-8ZT*1JF(;&(QE+Qga!N7&nAj;RoRhoZK1dO1U zL&VWrAQrzpW-WSygh{Nf^8?T}8h}L)190go`aKIk3k|@zxd8A;=MH6MwtMgC zOJMNFyO-JCO0M1$zj*B2iFd9%w0Ydh)3N6+UwY?n3(mi<@g45QIfrisU_6S8hEp>nbK9+)v{{z5OAX7o`o03fA@{{pWe0p#T`5E z8MtB3_U&KiKmI}R%+;#lW&7rIbsj%8spggg)eVj($0bKBpbuQ&+sAJCedF>)m0kB< zn0@HM1GC!YV-HOJdy{LL@Ux^^vTyyHN!R2j!e4m(&Y9ej)3?7wcPHB?0j~2VOU0XA zN11Vt-&OX;10R5G&+HEeFK_qWa+PIA{ATLzU^{oPRy*>`>7#&c*ihBset*!|k>yh+ zJ$-jcDVX!B?=gM;09fgLkDYJ-KG?suWa+xWuY3fe{v*$g**0#<2RlzJ7$W>AQN3sn zOupL@cx_GF-sZ-!i;}~kT zb**^XG4s!jr%q0tnOxU#^z`HIr+&NR*dN|Kwtk|wqjQb+`M;K&9DH>v|BF*#001`u zFbK_c{2l2D=UQ$<>6p(1lyF(qestK+oB^LKd3f>ANuPTZxdk;7r}=hN&RzUZTV%It literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2c28328ed08d90fba84349801c202e2f84f54312 GIT binary patch literal 17498 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 HSPA+ + + DC-HSPA+ + Cancel Ok 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/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index a4447201e34b..c1da9da111a9 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -1770,7 +1770,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/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 57a64110f0a8..1b05bf5ab125 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -539,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"; @@ -814,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; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 6a721fbb211b..b4c717759ca6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -676,6 +676,11 @@ public String getNetworkCountryIso() { /** 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. */ @@ -705,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() { @@ -794,6 +799,7 @@ 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: @@ -853,6 +859,8 @@ public static String getNetworkTypeName(int type) { return "TD-SCDMA"; case NETWORK_TYPE_IWLAN: return "IWLAN"; + case NETWORK_TYPE_DCHSPAP: + return "DC-HSPA+"; default: return "UNKNOWN"; } From f60eaa5f8ae51725f6fc8b7636554d04cff8d40b Mon Sep 17 00:00:00 2001 From: Tushar Janefalkar Date: Wed, 4 Dec 2013 11:57:31 -0800 Subject: [PATCH 39/86] Revert "Add support for BeiDou and Glonass" This reverts commit 9c3c49b082916f83f4ced99ae81647e5c3fc1202 since it picked up the wrong patch for merge CRs-fixed: 582614 Change-Id: I6e1d45856fa26d470209396980f46ea13e5041db --- .../server/location/GpsLocationProvider.java | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index c1da9da111a9..fa6a32279d68 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -135,35 +135,19 @@ 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; - private static final int GPS_DELETE_TIME = 0x00000008; - private static final int GPS_DELETE_IONO = 0x00000010; - private static final int GPS_DELETE_UTC = 0x00000020; - private static final int GPS_DELETE_HEALTH = 0x00000040; - private static final int GPS_DELETE_SVDIR = 0x00000080; - private static final int GPS_DELETE_SVSTEER = 0x00000100; - private static final int GPS_DELETE_SADATA = 0x00000200; - private static final int GPS_DELETE_RTI = 0x00000400; - 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 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 GPS_DELETE_ALL = 0xFFFFFFFF; + private static final int GPS_DELETE_EPHEMERIS = 0x0001; + private static final int GPS_DELETE_ALMANAC = 0x0002; + private static final int GPS_DELETE_POSITION = 0x0004; + private static final int GPS_DELETE_TIME = 0x0008; + private static final int GPS_DELETE_IONO = 0x0010; + private static final int GPS_DELETE_UTC = 0x0020; + private static final int GPS_DELETE_HEALTH = 0x0040; + private static final int GPS_DELETE_SVDIR = 0x0080; + private static final int GPS_DELETE_SVSTEER = 0x0100; + private static final int GPS_DELETE_SADATA = 0x0200; + private static final int GPS_DELETE_RTI = 0x0400; + private static final int GPS_DELETE_CELLDB_INFO = 0x8000; + private static final int GPS_DELETE_ALL = 0xFFFF; // The GPS_CAPABILITY_* flags must match the values in gps.h private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; From 4948fd3212002c05296cf4daabcaab97da6ae290 Mon Sep 17 00:00:00 2001 From: Tushar Janefalkar Date: Mon, 5 Aug 2013 17:05:06 -0700 Subject: [PATCH 40/86] Add support for BeiDou and Glonass Add masks for BeiDou amd Glonass to delete aiding data CRs-fixed: 582614 Change-Id: I651adcf4f5486c827bea8487e3437f15cd6d8013 --- .../server/location/GpsLocationProvider.java | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index fa6a32279d68..5242957e6f4f 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -135,19 +135,34 @@ 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 - private static final int GPS_DELETE_EPHEMERIS = 0x0001; - private static final int GPS_DELETE_ALMANAC = 0x0002; - private static final int GPS_DELETE_POSITION = 0x0004; - private static final int GPS_DELETE_TIME = 0x0008; - private static final int GPS_DELETE_IONO = 0x0010; - private static final int GPS_DELETE_UTC = 0x0020; - private static final int GPS_DELETE_HEALTH = 0x0040; - private static final int GPS_DELETE_SVDIR = 0x0080; - private static final int GPS_DELETE_SVSTEER = 0x0100; - private static final int GPS_DELETE_SADATA = 0x0200; - private static final int GPS_DELETE_RTI = 0x0400; - private static final int GPS_DELETE_CELLDB_INFO = 0x8000; - private static final int GPS_DELETE_ALL = 0xFFFF; + private static final int GPS_DELETE_EPHEMERIS = 0x00000001; + private static final int GPS_DELETE_ALMANAC = 0x00000002; + private static final int GPS_DELETE_POSITION = 0x00000004; + private static final int GPS_DELETE_TIME = 0x00000008; + private static final int GPS_DELETE_IONO = 0x00000010; + private static final int GPS_DELETE_UTC = 0x00000020; + private static final int GPS_DELETE_HEALTH = 0x00000040; + private static final int GPS_DELETE_SVDIR = 0x00000080; + private static final int GPS_DELETE_SVSTEER = 0x00000100; + private static final int GPS_DELETE_SADATA = 0x00000200; + private static final int GPS_DELETE_RTI = 0x00000400; + 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 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 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 private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; @@ -1039,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; } From aa6805dff54c89707c9c7175aebdd8cd4600f725 Mon Sep 17 00:00:00 2001 From: Darren Smith Date: Thu, 18 Feb 2016 14:12:16 -0600 Subject: [PATCH 41/86] Fix derp on DC-HSPAP merge Change-Id: I7f8d30bdd3ade31a91ea29c6fd1d175c0a1ff32c --- packages/SystemUI/res/values/slim_strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/slim_strings.xml b/packages/SystemUI/res/values/slim_strings.xml index 1bf6de1807f0..e42d05f7bafe 100644 --- a/packages/SystemUI/res/values/slim_strings.xml +++ b/packages/SystemUI/res/values/slim_strings.xml @@ -33,7 +33,7 @@ HSPA+ - DC-HSPA+ + DC-HSPA+ Cancel From 1fd4444189b7187b2229d9c540ed8c08d1f413f9 Mon Sep 17 00:00:00 2001 From: Shreyas Basarge Date: Thu, 18 Feb 2016 11:56:05 +0000 Subject: [PATCH 42/86] NPE fix for SyncStorageEngine read authority Add a null check after an authority is read from disk. Bug: 26513719 Change-Id: I18f01828141110e776cc96f3b3be3d80125e70c1 (cherry picked from commit a962d9eba7a8f741ed149964126fb08fe4fd7128) --- .../com/android/server/content/SyncStorageEngine.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 178c3723817c..7f0374dfc31e 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; @@ -1744,8 +1745,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); From e056b53fb01e5e8b417e5390b0a32397a754f0ad Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Tue, 19 Jan 2016 23:04:04 +0000 Subject: [PATCH 43/86] DO NOT MERGE Redact Account info from getCurrentSyncs BUG:26094635 If the caller to ContentResolver#getCurrentSyncs does not hold the GET_ACCOUNTS permission, return a SyncInfo object that does not contain any Account information. Change-Id: I5628ebe1f56c8e3f784aaf1b3281e6b829d19314 (cherry picked from commit b63057e698a01dafcefc7ba09b397b0336bba43d) (cherry picked from commit a5cafd2a93ce1c481328e2ce25c3bf2a19b81601) --- core/java/android/content/SyncInfo.java | 18 ++++++++++ .../server/content/ContentService.java | 33 +++++++++++++++++-- .../server/content/SyncStorageEngine.java | 16 ++++++--- 3 files changed, 61 insertions(+), 6 deletions(-) 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/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 7f0374dfc31e..e3a35887f250 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -1307,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; } From d87e4d6881a16cb47660e4c12240a988c404b5ad Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Fri, 15 Apr 2016 10:41:13 -0400 Subject: [PATCH 44/86] Don't pass URL path and username/password to PAC scripts The URL path could contain credentials that apps don't want exposed to a potentially malicious PAC script. Bug: 27593919 Change-Id: I4bb0362fc91f70ad47c4c7453d77d6f9a1e8eeed --- core/java/android/net/PacProxySelector.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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(); } From bf01fcd631fa00ec71c931b3c0ab49cf4b8fc7bc Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Fri, 10 Jun 2016 17:59:44 -0700 Subject: [PATCH 45/86] DO NOT MERGE: Don't trust callers to supply app info to bindBackupAgent() Get the canonical identity and metadata about the package from the Package Manager at time of usage rather than rely on the caller to have gotten things right, even when the caller has the system uid. Bug 28795098 Change-Id: I62710b15bb601fdfedd68e32349168c10725eb45 (cherry picked from commit d85a4ed28323d9a72179ff17a6d928f43096e407) --- core/java/android/app/ActivityManagerNative.java | 10 ++++++---- core/java/android/app/IActivityManager.java | 2 +- .../com/android/server/BackupManagerService.java | 3 ++- .../server/am/ActivityManagerService.java | 16 ++++++++++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) 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/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/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/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 558a41b8b517..049875c0743f 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -12922,10 +12922,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; From 82de2c3e122b1a08ba7010eed5d12e3349cbfab7 Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Fri, 29 May 2015 14:49:03 -0700 Subject: [PATCH 46/86] Update ExifInterface.getDateTime to support subseconds Also document that it returns local time. Bug: 20638367 Change-Id: I6a1c35925e0555bcdcb079be0ca5b19a279a05a6 --- media/java/android/media/ExifInterface.java | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 9db35fce7db7..aa5d43a1fa70 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -58,6 +58,11 @@ public class ExifInterface { /** Type is String. */ public static final String TAG_ISO = "ISOSpeedRatings"; + /** + * @hide + */ + public static final String TAG_SUBSECTIME = "SubSecTime"; + /** * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF. * Type is rational. @@ -346,7 +351,7 @@ public double getAltitude(double 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 */ @@ -356,9 +361,24 @@ public long getDateTime() { 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(); + long msecs = datetime.getTime(); + + String subSecs = mAttributes.get(TAG_SUBSECTIME); + if (subSecs != null) { + try { + long sub = Long.valueOf(subSecs); + while (sub > 1000) { + sub /= 10; + } + msecs += sub; + } catch (NumberFormatException e) { + } + } + return msecs; } catch (IllegalArgumentException ex) { return -1; } @@ -375,7 +395,6 @@ public long getGpsDateTime() { if (date == null || time == null) return -1; String dateTimeString = date + ' ' + time; - if (dateTimeString == null) return -1; ParsePosition pos = new ParsePosition(0); try { From c7ebe6f0cf0cf62e6787aca2cd346af15dc73526 Mon Sep 17 00:00:00 2001 From: Eino-Ville Talvala Date: Mon, 8 Jun 2015 15:28:09 -0700 Subject: [PATCH 47/86] ExifInterface: Add a few tag fields. Since these are being CTS tested, they should be public. Bug: 21568414 Change-Id: I3f9f6e5da240b2ac0b2b88b5d25ec3c8c4704b8d --- api/current.txt | 4 ++++ media/java/android/media/ExifInterface.java | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/api/current.txt b/api/current.txt index d2db84ac24c9..e420396882b8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11999,6 +11999,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 +12018,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 diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index aa5d43a1fa70..6bf5721d55c3 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -57,6 +57,16 @@ public class ExifInterface { public static final String TAG_APERTURE = "FNumber"; /** Type is String. */ public static final String TAG_ISO = "ISOSpeedRatings"; + /** Type is String. */ + public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME = "SubSecTime"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + + /** * @hide From 70b17085cd20a46b68d07ddbd49b32aef5a4a079 Mon Sep 17 00:00:00 2001 From: Sungsoo Lim Date: Wed, 13 Jul 2016 09:31:16 +0900 Subject: [PATCH 48/86] DO NOT MERGE: Remove the use of JHEAD in ExifInterface Bug: 29270469 Change-Id: I6a6c8aeab2a842ff1646316363d614851625e78f --- media/java/android/media/ExifInterface.java | 2451 +++++++++++++++++-- media/jni/Android.mk | 3 - 2 files changed, 2258 insertions(+), 196 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 6bf5721d55c3..c5e978f8703c 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -16,85 +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. */ - public static final String TAG_MAKE = "Make"; - /** Type is String. */ - public static final String TAG_MODEL = "Model"; + /** Type is String. @hide */ + public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription"; /** Type is int. */ - public static final String TAG_FLASH = "Flash"; + public static final String TAG_IMAGE_LENGTH = "ImageLength"; /** Type is int. */ public static final String TAG_IMAGE_WIDTH = "ImageWidth"; - /** 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 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_GPS_LATITUDE_REF = "GPSLatitudeRef"; + public static final String TAG_MAKE = "Make"; /** Type is String. */ - public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; + public static final String TAG_MODEL = "Model"; + /** Type is int. */ + 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 String. */ + /** Type is double. */ public static final String TAG_APERTURE = "FNumber"; - /** Type is String. */ + /** Type is String. @hide */ + public static final String TAG_FILE_SOURCE = "FileSource"; + /** Type is int. */ + 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_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_DATETIME_DIGITIZED = "DateTimeDigitized"; - /** Type is int. */ public static final String TAG_SUBSEC_TIME = "SubSecTime"; - /** Type is int. */ + /** Type is String. */ + public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + /** Type is String. */ 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_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; - - - - /** - * @hide - */ - public static final String TAG_SUBSECTIME = "SubSecTime"; - + 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; @@ -102,34 +324,730 @@ 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; - // 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) { @@ -139,53 +1057,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; } } @@ -197,126 +1161,299 @@ 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); - } - - /** - * Initialize mAttributes with the attributes from the file mFilename. - * - * 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. - * - * This function also initialize mHasThumbnail to indicate whether the - * file has a thumbnail inside. - */ - 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"); + // 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 { - mAttributes.put(attrName, attrValue); + try { + double doubleValue = Double.parseDouble(value); + value = (long) (doubleValue * 10000L) + "/10000"; + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid value for " + tag + " : " + value); + return; + } } } - } - /** - * 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. - */ - 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 + for (int i = 0 ; i < EXIF_TAGS.length; ++i) { + if (i == IFD_THUMBNAIL_HINT && !mHasThumbnail) { continue; } - String val = iter.getValue(); - sb.append(key + "="); - sb.append(val.length() + " "); - sb.append(val); - } - String s = sb.toString(); - synchronized (sLock) { - saveAttributesNative(mFilename, s); - commitChangesNative(mFilename); + 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; + } + } } } /** - * Returns true if the JPEG file has a thumbnail. + * Update the values of the tags in the tag groups if any value for the tag already was stored. + * + * @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. */ - public boolean hasThumbnail() { - return mHasThumbnail; + 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; } /** - * Returns the thumbnail inside the JPEG 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)} + * Remove any values of the specified tag. + * + * @param tag the name of the tag. */ - public byte[] getThumbnail() { - synchronized (sLock) { - return getThumbnailNative(mFilename); + private void removeAttribute(String tag) { + for (int i = 0 ; i < EXIF_TAGS.length; ++i) { + mAttributes[i].remove(tag); } } /** - * Returns the offset and length of thumbnail inside the JPEG file, or - * {@code null} if there is no thumbnail. - * - * @return two-element array, the offset in the first value, and length in - * the second, or {@code null} if no thumbnail was found. - * @hide + * 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 { + try { + InputStream in = new FileInputStream(mFilename); + // Initialize mAttributes. + for (int i = 0; i < EXIF_TAGS.length; ++i) { + mAttributes[i] = new HashMap(); + } + getJpegAttributes(in); + } catch (IOException e) { + // Ignore exceptions in order to keep the compatibility with the old versions of + // ExifInterface. + Log.w(TAG, "Invalid image.", e); + } finally { + addDefaultValuesForCompatibility(); + if (DEBUG) { + printAttributes(); + } + } + } + + // 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 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 { + // 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 image file has a thumbnail. + */ + public boolean hasThumbnail() { + return mHasThumbnail; + } + + /** + * 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() { + 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 image file, or + * {@code null} if there is no thumbnail. + * + * @return two-element array, the offset in the first value, and length in + * the second, or {@code null} if no thumbnail was found. + * @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; } /** @@ -325,10 +1462,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 { @@ -354,7 +1491,7 @@ 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; } @@ -366,8 +1503,9 @@ public double getAltitude(double defaultValue) { * @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 { @@ -377,7 +1515,7 @@ public long getDateTime() { if (datetime == null) return -1; long msecs = datetime.getTime(); - String subSecs = mAttributes.get(TAG_SUBSECTIME); + String subSecs = getAttribute(TAG_SUBSEC_TIME); if (subSecs != null) { try { long sub = Long.valueOf(subSecs); @@ -386,10 +1524,11 @@ public long getDateTime() { } msecs += sub; } catch (NumberFormatException e) { + // Ignored } } return msecs; - } catch (IllegalArgumentException ex) { + } catch (IllegalArgumentException e) { return -1; } } @@ -400,9 +1539,13 @@ 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; @@ -411,13 +1554,12 @@ public long getGpsDateTime() { 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(","); @@ -439,26 +1581,949 @@ private static float convertRationalLatLonToFloat( return (float) -result; } return (float) result; - } catch (NumberFormatException e) { - // Some of the nubmers are not valid - throw new IllegalArgumentException(); - } catch (ArrayIndexOutOfBoundsException e) { - // Some of the rational does not follow the correct format + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + // Not valid throw new IllegalArgumentException(); } } - private native boolean appendThumbnailNative(String fileName, - String thumbnailFileName); + // 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; + } + } + } + } + + // 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); + + // 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)); + } + + // Set byte order. + dataInputStream.setByteOrder(mExifByteOrder); + + int startCode = dataInputStream.readUnsignedShort(); + if (startCode != 0x2a) { + throw new IOException("Invalid exif start: " + Integer.toHexString(startCode)); + } + + // 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()); + } + } + } - private native void saveAttributesNative(String fileName, - String compressedAttributes); + // 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)); + } - private native String getAttributesNative(String fileName); + // 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; + } - private native void commitChangesNative(String fileName); + // 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; + } - private native byte[] getThumbnailNative(String fileName); + // 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/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 From b538c5aa257a9da5d4ec965b063717e82f56fb3a Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Tue, 30 Sep 2014 07:33:15 -0700 Subject: [PATCH 49/86] systemui: Unbreak the planet * Not sure who to blame, but it's probably themes... :) Change-Id: I0c534354a699f756d809aff10843ebea21f3f369 --- packages/SystemUI/proguard.flags | 2 ++ 1 file changed, 2 insertions(+) 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 From 44e10440da7f638d6cfc0de74158bf07bcaaa1c1 Mon Sep 17 00:00:00 2001 From: Paul Stewart Date: Wed, 11 Nov 2015 10:23:43 -0800 Subject: [PATCH 50/86] WifiEnterpriseConfiguration: Do not print credentials in toString BUG:25624963 Change-Id: I939a12a27d6b915d8a9cc8b142f645fba0ee42ec --- wifi/java/android/net/wifi/WifiEnterpriseConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index c7ebecb7e1ee..5e2f7d530c3a 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 = (key == PASSWORD_KEY) ? "" : mFields.get(key); + sb.append(key).append(" ").append(value).append("\n"); } return sb.toString(); } From 2f6b68d18c39ea4a03a3e472f4d1dc5c680dff82 Mon Sep 17 00:00:00 2001 From: Darren Smith Date: Sat, 12 Nov 2016 19:12:44 -0600 Subject: [PATCH 51/86] Updates for java-1.5 compatibility Change-Id: I4333d6aa6fbbc9ca7131990b959f4d0cc4da8ad3 --- media/java/android/media/ExifInterface.java | 33 +++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index c5e978f8703c..455215bd98d3 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -981,7 +981,7 @@ private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) 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( + private static final HashSet sTagSetForCompatibility = new HashSet(Arrays.asList( TAG_APERTURE, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE, TAG_GPS_TIMESTAMP)); @@ -1581,9 +1581,12 @@ private static float convertRationalLatLonToFloat(String rationalString, String return (float) -result; } return (float) result; - } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + } catch (NumberFormatException e) { + // Not valid + throw new IllegalArgumentException(e.getMessage()); + } catch (ArrayIndexOutOfBoundsException e) { // Not valid - throw new IllegalArgumentException(); + throw new IllegalArgumentException(e.getMessage()); } } @@ -2254,14 +2257,14 @@ private static Pair guessDataFormat(String entryValue) { second = dataFormat.second; } if (first == -1 && second == -1) { - return new Pair<>(IFD_FORMAT_STRING, -1); + return new Pair(IFD_FORMAT_STRING, -1); } if (first == -1) { - dataFormat = new Pair<>(second, -1); + dataFormat = new Pair(second, -1); continue; } if (second == -1) { - dataFormat = new Pair<>(first, -1); + dataFormat = new Pair(first, -1); continue; } } @@ -2275,37 +2278,37 @@ private static Pair guessDataFormat(String entryValue) { long numerator = Long.parseLong(rationalNumber[0]); long denominator = Long.parseLong(rationalNumber[1]); if (numerator < 0L || denominator < 0L) { - return new Pair<>(IFD_FORMAT_SRATIONAL, - 1); + 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_URATIONAL, -1); } - return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL); + return new Pair(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL); } catch (NumberFormatException e) { // Ignored } } - return new Pair<>(IFD_FORMAT_STRING, -1); + 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); + return new Pair(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG); } if (longValue < 0) { - return new Pair<>(IFD_FORMAT_SLONG, -1); + return new Pair(IFD_FORMAT_SLONG, -1); } - return new Pair<>(IFD_FORMAT_ULONG, -1); + return new Pair(IFD_FORMAT_ULONG, -1); } catch (NumberFormatException e) { // Ignored } try { Double.parseDouble(entryValue); - return new Pair<>(IFD_FORMAT_DOUBLE, -1); + return new Pair(IFD_FORMAT_DOUBLE, -1); } catch (NumberFormatException e) { // Ignored } - return new Pair<>(IFD_FORMAT_STRING, -1); + 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 From 04134c4ac86fd26e2fbdc65daca97ebd43eb7ca7 Mon Sep 17 00:00:00 2001 From: Paul Stewart Date: Mon, 21 Mar 2016 09:51:27 -0700 Subject: [PATCH 52/86] Fix string equality comparison Don't use "==" to compare strings. Bug: 25624963 Change-Id: Id25696e4fdcbcf4d48ec74e8ed65c1a33716b30c --- wifi/java/android/net/wifi/WifiEnterpriseConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index 5e2f7d530c3a..2b8ca48877d5 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -838,7 +838,7 @@ public String toString() { StringBuffer sb = new StringBuffer(); for (String key : mFields.keySet()) { // Don't display password in toString(). - String value = (key == PASSWORD_KEY) ? "" : mFields.get(key); + String value = PASSWORD_KEY.equals(key) ? "" : mFields.get(key); sb.append(key).append(" ").append(value).append("\n"); } return sb.toString(); From 53208ec41c2cc03ff3419a261565ddacb899e04a Mon Sep 17 00:00:00 2001 From: Sungsoo Lim Date: Mon, 25 Jul 2016 11:53:13 +0900 Subject: [PATCH 53/86] DO NOT MERGE: Fix CTS regression Bug: 30297223, Bug: 30437363 Change-Id: I7b18af40e4eac2713577204428fbfb96cc346582 --- media/java/android/media/ExifInterface.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 455215bd98d3..8e694d4b454b 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1330,12 +1330,12 @@ private void removeAttribute(String tag) { * determine whether the image data format is JPEG or not. */ private void loadAttributes() throws IOException { + // Initialize mAttributes. + for (int i = 0; i < EXIF_TAGS.length; ++i) { + mAttributes[i] = new HashMap(); + } try { InputStream in = new FileInputStream(mFilename); - // Initialize mAttributes. - for (int i = 0; i < EXIF_TAGS.length; ++i) { - mAttributes[i] = new HashMap(); - } getJpegAttributes(in); } catch (IOException e) { // Ignore exceptions in order to keep the compatibility with the old versions of From 2d05e40664db110fa60d8dfdf3abfbc1dabca3b9 Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Tue, 9 Aug 2016 17:00:25 +0100 Subject: [PATCH 54/86] Process: Fix communication with zygote. Don't write partial requests, and don't return (or throw) early after partially reading a response. bug: 30143607 (cherry-picked from commit 448be0a62209c977593d81617853a8a428d013df) Change-Id: I5881fdd5e81023cd21fb4d23a471a5031987a1f1 (cherry picked from commit 8e69dd2284580bd0587363fe5279c1b6539e3b89) --- core/java/android/os/Process.java | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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 { From cee6d0d0aa7a2e5bcad171c78c3ffd27b4a2da84 Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Fri, 12 Aug 2016 18:49:56 -0700 Subject: [PATCH 55/86] DO NOT MERGE: Clean up when recycling a pid with a pending launch Fix for accidental launch of a broadcast receiver in an incorrect app instance. Bug: 30202481 Change-Id: I84b74edc29ca3fb88048b44af682ecbeb176b774 (cherry picked from commit a3af5c620727a9c1cd51d7864b3c40bb5de70a40) --- .../server/am/ActivityManagerService.java | 23 +++++++++++++++---- .../com/android/server/am/BroadcastQueue.java | 5 ++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 049875c0743f..c4b8a398560d 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2890,6 +2890,15 @@ private final void startProcessLocked(ProcessRecord app, app.usingWrapper = startResult.usingWrapper; app.removed = false; synchronized (mPidsSelfLocked) { + ProcessRecord oldApp; + // If there is already an app occupying that pid that hasn't been cleaned up + if ((oldApp = mPidsSelfLocked.get(startResult.pid)) != 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*/); + } this.mPidsSelfLocked.put(startResult.pid, app); Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); msg.obj = app; @@ -3679,7 +3688,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); } @@ -12494,7 +12504,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 +12629,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)); @@ -15917,7 +15930,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); From 2a040bb1e4a0c0a7f31c6594e7d7c24b6bd183d4 Mon Sep 17 00:00:00 2001 From: David Christie Date: Mon, 25 Jul 2016 17:13:23 -0700 Subject: [PATCH 56/86] Fix vulnerability where large GPS XTRA data can be injected. -Can potentially crash system with OOM. Bug: 29555864 Change-Id: I7157f48dddf148a9bcab029cf12e26a58d8054f4 (cherry picked from commit dde12c69233e8553252c2e010bdfda6b91762ff9) --- .../java/com/android/server/location/GpsXtraDownloader.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); From b1dac3bd51fa9213558d03ff112845948f0777eb Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Wed, 21 Sep 2016 11:20:54 +0900 Subject: [PATCH 57/86] DO NOT MERGE Check caller for sending media key to telephony service Prevent sending media key events from the non-system app to the telephony service through the AudioManager.dispatchMediaKeyEvent() or sending media key broadcast directly. Bug: 29833954 Tested: Installed malicious apps and confirmed that they don't work. Tested: Run CtsTelecomTestCases and CtsMediaTestCases Change-Id: I2a9e78196ba7455324e485f098f095d03b47ee15 (cherry picked from commit d1641e8c2719dd5e83f841dfddf7a6efff86e4d9) --- media/java/android/media/MediaFocusControl.java | 9 ++++++++- .../internal/policy/impl/PhoneWindowManager.java | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) 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/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 8c3eb42a6d48..6e5d8a81e39c 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -4804,6 +4804,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 From 5d671c24c92227c5fece59348299c25eb59b48d9 Mon Sep 17 00:00:00 2001 From: Sungsoo Date: Thu, 8 Sep 2016 16:04:44 +0900 Subject: [PATCH 58/86] DO NOT MERGE) ExifInterface: Make saveAttributes throw an exception before change ExifInterface object can be created with a unsupported file format. If saveAttribute is called with an unsupported file format, ExifInterface makes the file corrupted. This CL prevents those cases by throwing an exception before making any change on the file. Bug: 30936376 Change-Id: I915f56b00ec9422b53591ac5534e070a1d6798e6 (cherry picked from commit 1bdd10a953d4eee4fe65272b0e8a6039e02a223d) --- media/java/android/media/ExifInterface.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 8e694d4b454b..479608721c11 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1039,6 +1039,7 @@ private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) private int mThumbnailOffset; private int mThumbnailLength; private byte[] mThumbnailBytes; + private boolean mIsSupportedFile; // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); @@ -1337,9 +1338,11 @@ private void loadAttributes() throws IOException { try { InputStream 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(); @@ -1368,6 +1371,10 @@ private void printAttributes() { * and make a single call rather than multiple calls for each attribute. */ public void saveAttributes() throws IOException { + if (!mIsSupportedFile) { + throw new UnsupportedOperationException( + "ExifInterface only supports saving attributes on JPEG formats."); + } // Keep the thumbnail in memory mThumbnailBytes = getThumbnail(); From e985cc97d0eb3f0dd3f60bf893d5ed2344e4ea1e Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Mon, 22 Aug 2016 09:15:40 -0400 Subject: [PATCH 59/86] Avoid crashing when downloading MitM'd PAC that is too big There's two pieces to this fix: 1. Move PAC loading off IoThread which isn't meant for blocking network fetches. If the fetch takes more than 60s Android reboots when the IoThread is used. 2. Limit PAC fetching to 20MB. Any PAC bigger than that is likely evil. MitM of PACs should only be possbile when a non-SSL PAC URL is used. Change-Id: Ie1658a1c705615dc85a7fc68053f0dad8d048294 Fixes: 30100884 (cherry picked from commit 7d2198b586bcfbf96fb627021a0eb85d32829cc0) --- .../server/connectivity/PacManager.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) 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); } } } From 78a5a4e0c753c88ef215755a6eeead7680cda619 Mon Sep 17 00:00:00 2001 From: Ajay Panicker Date: Thu, 8 Sep 2016 13:23:02 -0700 Subject: [PATCH 60/86] Prevent FDs from being leaked when accepted sockets are closed Bug: 28672558 Change-Id: I4bc14bd7f098e34012c2ae1eeba2d439145901f0 (cherry picked from commit 786e2694b1de828cef333af907d6270ecad445df) --- core/java/android/bluetooth/BluetoothSocket.java | 1 + 1 file changed, 1 insertion(+) 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(); From f46303a8896f78a1f9a9b3c2b8418bcf1e9990d8 Mon Sep 17 00:00:00 2001 From: Ajay Panicker Date: Thu, 8 Sep 2016 11:01:29 -0700 Subject: [PATCH 61/86] Fix setPairingConfirmation permissions issue (2/2) setPairingConfirmation was set to only require BLUETOOTH_ADMIN permission which shouldn't be able to set the confirmation itself. This is restricted to BLUETOOTH_PRIVILEGED permission. Bug: 29043989 Change-Id: Iddc935f0b02f5ff56e930914b4b664377e786184 (cherry picked from commit edae39d59aa2155c2d79cca991e163e2284cc355) --- core/java/android/bluetooth/BluetoothDevice.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 6b4cd3ed5387..71148180cbd4 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1023,7 +1023,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 From ea83ffc22a5e2cd63e22331b0e6c66429d2b7cf3 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 16 Sep 2016 12:00:57 +0900 Subject: [PATCH 62/86] DO NOT MERGE: Catch all exceptions when parsing IME meta data Bug: 30568284 Change-Id: I0b613f8ce0f014320c5ac1bf445699ea2702a0a2 (manually cherry picked from 9b2997d22e6ce2a15065d8e7608dd77b316c2065) (cherry picked from commit f71d2cddf1780dbfca14039d791813145bd2f1d6) --- .../java/com/android/server/InputMethodManagerService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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); } } From 915c2fdc5822b4c07a59ed141f15046cddeb2920 Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Thu, 15 Sep 2016 19:00:43 -0700 Subject: [PATCH 63/86] DO NOT MERGE: Fix deadlock in AcitivityManagerService. Don't hold mPidsSelfLocked lock when calling cleanUpApplicationRecordLocked. Bug: 31463143 Change-Id: I1fddd06f5e35b67fea041741f5746c57a39208ba (cherry picked from commit dce4be63bb0842ec0d1f8865c46edadb0f7df148) --- .../server/am/ActivityManagerService.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index c4b8a398560d..ac49c299e928 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2889,16 +2889,20 @@ 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) { - ProcessRecord oldApp; - // If there is already an app occupying that pid that hasn't been cleaned up - if ((oldApp = mPidsSelfLocked.get(startResult.pid)) != 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*/); - } this.mPidsSelfLocked.put(startResult.pid, app); Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); msg.obj = app; From 391e238f90ce5cd27b1648e0fd432a3a3150960d Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Wed, 12 Oct 2016 19:01:11 -0700 Subject: [PATCH 64/86] DO NOT MERGE Isolated processes don't get precached system service binders More specifically, they get a PackageManager binder -- necessary for Android process startup and configuration -- but none of the other usual preloaded service binders. (backported from commit 2c61c57ac53cbb270b4e76b9d04465f8a3f6eadc) Bug: 30202228 Change-Id: I3810649f504cd631665ece338a83d2e54d41ad05 (cherry picked from commit 2aa7e5e861123bab22bd2af676bcab662bcd37da) --- .../server/am/ActivityManagerService.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index ac49c299e928..12be9389f6e1 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(); @@ -5062,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(); From 4d317aa6b4d560bfae3964e367203bf156822dde Mon Sep 17 00:00:00 2001 From: Sungsoo Date: Tue, 18 Oct 2016 14:12:00 +0900 Subject: [PATCH 65/86] DO NOT MERGE) ExifInterface: Close the file when an exception happens Bug: 32068647, Bug: 30936376 Change-Id: I22fa2384348c890ca726d2b1632cd54e59d25a8f (cherry picked from commit 418e0869ba936ff1e1d31f576021d15e8da6a105) --- media/java/android/media/ExifInterface.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 479608721c11..994a4f7a1a94 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1335,8 +1335,9 @@ private void loadAttributes() throws IOException { for (int i = 0; i < EXIF_TAGS.length; ++i) { mAttributes[i] = new HashMap(); } + InputStream in = null; try { - InputStream in = new FileInputStream(mFilename); + in = new FileInputStream(mFilename); getJpegAttributes(in); mIsSupportedFile = true; } catch (IOException e) { @@ -1349,6 +1350,7 @@ private void loadAttributes() throws IOException { if (DEBUG) { printAttributes(); } + IoUtils.closeQuietly(in); } } From 4c8d87bd683bb9ed487417af373a20c10f3bb700 Mon Sep 17 00:00:00 2001 From: Tom O'Neill Date: Thu, 22 Dec 2016 17:59:47 +0000 Subject: [PATCH 66/86] Fix exploit where can hide the fact that a location was mocked am: a206a0f17e am: d417e54872 am: 3380a77516 am: 0a8978f04b am: 1684e5f344 am: d28eef0cc2 am: 1f458fdc66 am: d82f8a67fc am: 1ac8affd51 am: 56098f81b6 am: 7cec76de0f am: 2da05d0f9e Change-Id: I8c94a06f5fa722312436484609bafcb0585d6d18 --- .../android/server/LocationManagerService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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); } } From 7329cc96d125e92040a203b395f13ae7171bca8c Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Mon, 27 Feb 2017 17:33:18 -0800 Subject: [PATCH 67/86] [DO NOT MERGE] Check bounds in offsetToPtr Check whether specified offset belongs to mData. Also added a default argument bufferSize to check the end offset. Size of the ashmem descriptor can be modified between ashmem_get_size_region call and mmap. createFromParcel method was updated to check ashmem size again immediately after memory is mapped. Test: manual - using the test app from the bug Bug: 34128677 Change-Id: I3ecd1616a870ce20941ce9b20a1843d2b4295750 (cherry picked from commit 45e2e95c2ffeb2d978e2cce80b729ef6ada3b8d2) (cherry picked from commit acede24109412a4c09e6e4e93d7b96bc9b1ad440) --- include/androidfw/CursorWindow.h | 17 ++++++++++++++--- libs/androidfw/CursorWindow.cpp | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) 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*/); From dcd23541e07c71dbf38f0a397ce9d24f98c0de47 Mon Sep 17 00:00:00 2001 From: Phil Weaver Date: Wed, 12 Jul 2017 14:04:16 -0700 Subject: [PATCH 68/86] Back-port fixes for b/62196835 Bug: 62196835 Test: Created an accessibility service that displays a system and a toast overlay, confirmed that it disappeared when we reached the accessibility permission screen that uses this flag. Change-Id: Ic51ead670fc480e549512ba1d02f49d9c13bc3f0 (cherry picked from commit 41ff5389daa6e6ce4aa853bfae96e5ced0b1d8df) --- api/current.txt | 1 + core/java/android/view/WindowManager.java | 28 ++++++++++++++ core/res/AndroidManifest.xml | 9 +++++ .../java/com/android/server/wm/Session.java | 9 +++++ .../server/wm/WindowManagerService.java | 36 ++++++++++++++++++ .../com/android/server/wm/WindowState.java | 38 +++++++++++++++++++ .../server/wm/WindowStateAnimator.java | 2 + 7 files changed, 123 insertions(+) diff --git a/api/current.txt b/api/current.txt index e420396882b8..af96804e71ad 100644 --- a/api/current.txt +++ b/api/current.txt @@ -67,6 +67,7 @@ package android { field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST"; + field public static final java.lang.String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b1399a0fc8f0..eeacf394553f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1856,6 +1856,15 @@ android:description="@string/permdesc_internalSystemWindow" android:protectionLevel="signature" /> + + + 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); From 69b72c6202ccda8de1c69532877af6b20e1d4fe5 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 12 Jun 2017 17:33:07 -0600 Subject: [PATCH 69/86] DO NOT MERGE. KEY_INTENT shouldn't grant permissions. KEY_INTENT has no business granting any Uri permissions, so remove any grant flags that malicious apps may have tried sneaking in. Also fix ordering bug in general-purpose security check that was allowing FLAG_GRANT_PERSISTABLE to bypass it. Test: builds, boots Bug: 32990341, 32879915 Change-Id: I657455a770c81f045ccce6abbd2291407a1cfb42 (cherry picked from commit d722e780bac7685e8a012b5f479eba8c348c3c53) --- .../server/accounts/AccountManagerService.java | 3 +++ .../android/server/am/ActivityManagerService.java | 13 +++++++++++++ 2 files changed, 16 insertions(+) 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 12be9389f6e1..5809bca7e410 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -6067,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) { From b70b1f5ba4426295defaddd0b8111e4738bc6dc9 Mon Sep 17 00:00:00 2001 From: Adam Vartanian Date: Tue, 7 Nov 2017 12:22:23 +0000 Subject: [PATCH 70/86] Adjust Uri host parsing to use last instead of first @. Malformed authority segments can currently cause the parser to produce a hostname that doesn't match the hostname produced by the WHATWG URL parsing algorithm* used by browsers, which means that a URL could be seen as having a "safe" host when checked by an Android app but actually visit a different host when passed to a browser. The WHATWG URL parsing algorithm always produces a hostname based on the last @ in the authority segment, so we do the same. * https://url.spec.whatwg.org/#authority-state resets the "buffer", which is being used to build up the host name, each time an @ is found, so it has the effect of using the content between the final @ and the end of the authority section as the hostname. Bug: 68341964 Test: vogar android.net.UriTest (on NYC branch) Test: cts -m CtsNetTestCases (on NYC branch) (cherry picked from commit cd6228dd377b2a0caa02a1e6df92f3d9ae702a95) Change-Id: Ib791e5d4ad15dba87d65620513f92a71d780762f --- core/java/android/net/Uri.java | 6 +++--- core/tests/coretests/src/android/net/UriTest.java | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index a7a8a0a8f32b..f81fa1ed4433 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1057,7 +1057,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 +1081,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 +1107,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/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 6fb8946c29e0..78d317f3dc81 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -185,6 +185,11 @@ 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()); } @SmallTest From d4fad7dffbf347eda3babdd1ff373c7412d0f295 Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 1 Sep 2017 14:56:50 -0400 Subject: [PATCH 71/86] DO NOT MERGE Backporting potential usb tapjacking precaution. Bug: 62187985 Test: manual, backport Change-Id: I89ef535e345d402866a083e0bd8e3fd909fc9662 MERGED-IN: I3bdcd1876cd6dbe8a728bbce74edb52ab79f3e4c MERGED-IN: Ic58ddd6d54e96f522445e67b90760dcfed13c27d --- packages/SystemUI/res/values/strings.xml | 5 ++++ .../systemui/usb/UsbDebuggingActivity.java | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+) 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/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 { From 027f8af156d46e3372876c861e5d33a8916ae346 Mon Sep 17 00:00:00 2001 From: Siyamed Sinir Date: Wed, 6 Sep 2017 15:15:44 -0700 Subject: [PATCH 72/86] Prevent getting data from Clipboard if device is locked Clipboard should not return data if the device is locked. This CL checks for device locked state before returning values from get/has functions. Test: bit -t CtsContentTestCases:android.content.cts.ClipboardManagerTest Bug: 64934810 Change-Id: Icefac226615fe22a7735dff4ba4c3b528fb2ac12 --- .../com/android/server/ClipboardService.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index 069ae23f5596..07d5167909dd 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,19 @@ public boolean hasClipboardText(String callingPackage) { } } + private boolean isDeviceLocked() { + boolean isLocked = false; + 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; + } + private final void checkUriOwnerLocked(Uri uri, int uid) { if (!"content".equals(uri.getScheme())) { return; From 30864b04050f2239b015b737d9a32173de5e28e3 Mon Sep 17 00:00:00 2001 From: Siyamed Sinir Date: Wed, 13 Sep 2017 12:19:41 -0700 Subject: [PATCH 73/86] Fix ClipboardService device lock check for cross profile ClipboardService.isDeviceLocked should clear callingIdentity before accessing KeyguardManager. Test: bit CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.ManagedProfileTest Bug: 64934810 Change-Id: I00d491e5fb6d1c5451c7f8c453931b26e6134452 --- .../com/android/server/ClipboardService.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index 07d5167909dd..3ce9a1fb4d2e 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -254,15 +254,22 @@ public boolean hasClipboardText(String callingPackage) { private boolean isDeviceLocked() { boolean isLocked = false; - 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(); + 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); } - return isLocked; } private final void checkUriOwnerLocked(Uri uri, int uid) { From 9ba3ed4eb84d5aaaafa019aadda1f4f242be08da Mon Sep 17 00:00:00 2001 From: Darren Smith Date: Tue, 20 Mar 2018 08:56:05 -0500 Subject: [PATCH 74/86] Ran update-api Change-Id: Ibe2f40d6e9f66b5159f3a99caa841b593bda670e --- api/current.txt | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/api/current.txt b/api/current.txt index af96804e71ad..17a41b26353d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -67,7 +67,6 @@ package android { field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST"; - field public static final java.lang.String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; @@ -1269,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 } @@ -1495,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 @@ -1514,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 { @@ -1567,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 { @@ -1601,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 @@ -1615,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 { @@ -10543,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(); @@ -10553,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[]); @@ -17453,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 { @@ -17596,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(); @@ -18777,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); @@ -21256,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"; @@ -21806,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); } @@ -32742,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(); } From 7cbfca2fbad42c2e78c07c80e1f3ff6e71337973 Mon Sep 17 00:00:00 2001 From: Adam Vartanian Date: Wed, 31 Jan 2018 11:05:10 +0000 Subject: [PATCH 75/86] Adjust URI host parsing to stop on \ character. The WHATWG URL parsing algorithm [1] used by browsers says that for "special" URL schemes (which is basically all commonly-used hierarchical schemes, including http, https, ftp, and file), the host portion ends if a \ character is seen, whereas this class previously continued to consider characters part of the hostname. This meant that a malicious URL could be seen as having a "safe" host when viewed by an app but navigate to a different host when passed to a browser. [1] https://url.spec.whatwg.org/#host-state Bug: 71360761 Test: vogar frameworks/base/core/tests/coretests/src/android/net/UriTest.java (on NYC branch) Test: cts -m CtsNetTestCases (on NYC branch) Change-Id: Id53f7054d1be8d59bbcc7e219159e59a2425106e (cherry picked from commit fa3afbd0e7a9a0d8fc8c55ceefdb4ddf9d0115af) --- core/java/android/net/Uri.java | 8 ++++++++ core/tests/coretests/src/android/net/UriTest.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index f81fa1ed4433..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++; diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 78d317f3dc81..a40e9610fcd1 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -190,6 +190,12 @@ public void testAuthorityParsing() { 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 From aab3132cef34359f122acf6ed993cb4bc2b3e4a8 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Thu, 9 Nov 2017 17:12:17 -0800 Subject: [PATCH 76/86] Check for null-terminator in ResStringPool::string8At All other stringAt methods check for null termination. Be consistent so that upper levels don't end up with huge corrupt strings. Bug: 62537081 Test: none Change-Id: I17bdfb0c1e34507b66c6cad651bbdb12c5d4c417 (cherry picked from commit 3d35a0ea307693a97583a61973e729a5e7db2687) (cherry picked from commit 97f8cb01149b35b1832c7f9efe85ff19edf1083e) (cherry picked from commit 5ec65ae909a85d13d03c030be357c8c14a50d306) --- libs/androidfw/ResourceTypes.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 2f59dafacdbf..92690f7c1d99 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -660,7 +660,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); From 96b9c313e193bfbfbc0b3e5915d5d69c32a6b4eb Mon Sep 17 00:00:00 2001 From: y Date: Thu, 5 Apr 2018 17:57:27 -0700 Subject: [PATCH 77/86] ResStringPool: Fix security vulnerability Adds detection of attacker-modified size and data fields passed to ResStringPool::setTo(). These attacks are modified apks that AAPT would not normally generate. In the rare case this occurs, the installation cannot be allowed to continue. Bug: 71361168 Bug: 71360999 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 Merged-In: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 (cherry picked from commit 7e54c3f261d81316b75cb734075319108d8bc1d1) --- libs/androidfw/ResourceTypes.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 92690f7c1d99..50e5ef306372 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)) { + LOG_ALWAYS_FATAL("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) { + LOG_ALWAYS_FATAL("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) { From bd41581586a936199939dc6fb6ff7e46aa995631 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 26 Apr 2018 14:22:39 -0700 Subject: [PATCH 78/86] DO NOT MERGE Truncate newline and tab characters in BluetoothDevice name Test: manual Bug: 73173182 Change-Id: I7f2201cab36adf7f01d1a794d783cb78a536811f (cherry picked from commit 24da173b63b17a0bc6c80b2fcfefa7fe4574a15b) --- core/java/android/bluetooth/BluetoothDevice.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 71148180cbd4..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; } From 21b625d2ea2011abb111787a45f62988f0abbcca Mon Sep 17 00:00:00 2001 From: Daniel Jarai Date: Wed, 25 Jul 2018 14:54:40 +0200 Subject: [PATCH 79/86] Fix the HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission definition Change Ic51ead670fc480e549512ba1d02f49d9c13bc3f0 incorrectly tries making this permission hidden. While marking it with @hide and only adding it to api/system-current.txt was the proper way on Android 6.0, on 4.4, we need to add system APIs to api/current.txt, without hiding them. This change adapts this permission definition for 4.4. Change-Id: Ib2de017d396aa9dcefa91f7eb1ea210be668234d --- core/res/AndroidManifest.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index eeacf394553f..62a5f9a9dbb0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1856,12 +1856,10 @@ android:description="@string/permdesc_internalSystemWindow" android:protectionLevel="signature" /> - +

Not for use by third-party applications. --> From 574b17e7e8986ec8ece4b3a9a06319901551b737 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 21 May 2018 13:59:23 -0700 Subject: [PATCH 80/86] ResStringPool: Prevenet boot loop from se fix Changes the logs adding in a previous security fix to warnings so devices with malformed APKs currently on them will not undergo DOS when they are upgraded to P. Bug: 79724567 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: Ied54e4bb14abdaf79da562022c7ea6075187c1f8 (cherry picked from commit f05f47b2c1838529e682ad8f931d3da72244b1a1) (cherry picked from commit c31cf80008fdb06ea8e1eab9764096653e7854b1) --- libs/androidfw/ResourceTypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 50e5ef306372..34ee3bc38708 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -332,7 +332,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) // The chunk must be at least the size of the string pool header. if (size < sizeof(ResStringPool_header)) { - LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + ALOGW("Bad string block: data size %zu is too small to be a string block", size); return (mError=BAD_TYPE); } @@ -342,7 +342,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), reinterpret_cast(data) + size, "ResStringPool_header") != NO_ERROR) { - LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + ALOGW("Bad string block: malformed block dimensions"); return (mError=BAD_TYPE); } From ad34efe8741743c2ee4eb52952179376c2288b1e Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 7 Aug 2018 15:02:17 -0600 Subject: [PATCH 81/86] DO NOT MERGE. Persistable Uri grants still require permissions. When FLAG_GRANT_PERSISTABLE_URI_PERMISSION is requested, we still need to check permissions between the source and target packages, instead of shortcutting past them. The spirit of the original change is remains intact: if the caller requested FLAG_GRANT_PERSISTABLE_URI_PERMISSION, then we avoid returning "-1", which would prevent the grant data structure from being allocated. Bug: 111934948 Test: atest android.appsecurity.cts.AppSecurityTests Change-Id: Ief0fc922aa09fc3d9bb6a126c2ff5855347cd030 Merged-In: Ief0fc922aa09fc3d9bb6a126c2ff5855347cd030 (cherry picked from commit d6a6e7127cc341ca875d9d13cf7a864d9f20b479) --- .../server/am/ActivityManagerService.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 5809bca7e410..38a17003c395 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -6101,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? @@ -6123,7 +6136,7 @@ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, } } if (allowed) { - return -1; + return allowedResult; } } From cb1a4bba9ba0e11c8dbc84b06f528d8fe8257ce9 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 25 Jul 2018 14:01:59 -0600 Subject: [PATCH 82/86] DO NOT MERGE. Execute "strict" queries with extra parentheses. SQLiteQueryBuilder has a setStrict() mode which can be used to detect SQL attacks from untrusted sources, which it does by running each query twice: once with an extra set of parentheses, and if that succeeds, it runs the original query verbatim. This sadly doesn't catch inputs of the type "1=1) OR (1=1", which creates valid statements for both tests above, but the final executed query ends up leaking data due to SQLite operator precedence. Instead, we need to continue compiling both variants, but we need to execute the query with the additional parentheses to ensure data won't be leaked. Test: atest cts/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java Bug: 111085900 Change-Id: I6e8746fa48f9de13adae37d2990de11c9c585381 Merged-In: I6e8746fa48f9de13adae37d2990de11c9c585381 (cherry picked from commit 57b04a86802ff879af78e782a8582462323e34e7) --- .../database/sqlite/SQLiteQueryBuilder.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) 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); } From 57776bc66c802e9e9c1a3ec33fb5e782ecafec45 Mon Sep 17 00:00:00 2001 From: Ricardo Cerqueira Date: Mon, 4 Nov 2013 03:02:22 +0000 Subject: [PATCH 83/86] Add config_hasRemovableLid If a device features a dock with a removable lid, lidOpenRotation overrides the rotation even if it was undocked (technically the lid is still open). This setting tells the framework whether to apply lidOpenRotation to undocked devices. Change-Id: I1051278a45875b2139b75db28467f9b8de2936d6 Conflicts: policy/src/com/android/internal/policy/impl/PhoneWindowManager.java --- core/res/res/values/config.xml | 5 +++++ core/res/res/values/symbols.xml | 1 + .../internal/policy/impl/PhoneWindowManager.java | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index eb8806656c3c..bceda4ce00c0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1443,4 +1443,9 @@ false + + false + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 02119a9d9643..48f552371fe3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1764,4 +1764,5 @@ + diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 6e5d8a81e39c..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(); @@ -5241,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)) { From 6c3de9ee2111bdf3cf17081fc6cc87622ae5e077 Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Fri, 24 Aug 2018 21:50:35 -0700 Subject: [PATCH 84/86] Verify number of Map entries written to Parcel Make sure the number of entries written by Parcel#writeMapInternal matches the size written. If a mismatch were allowed, an exploitable scenario could occur where the data read from the Parcel would not match the data written. Fixes: 112859604 Test: cts-tradefed run cts -m CtsOsTestCases -t android.os.cts.ParcelTest Change-Id: I325d08a8b66b6e80fe76501359c41b6656848607 Merged-In: I325d08a8b66b6e80fe76501359c41b6656848607 (cherry picked from commit 057a01d1f38e9b46d3faa4059fdd7c8717681ea0) --- core/java/android/os/Parcel.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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!"); } + } /** From c7d593b59ae77a248ce840aa157ab708691dcf09 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Thu, 29 Nov 2018 18:54:21 +0100 Subject: [PATCH 85/86] Bluetooth: Check descriptors size in BluetoothHidDeviceAppSdpSettings Bug: 119819889 Test: compilation Change-Id: If51d0e2af74d99758f79a603d40cc2f5c84e4dde (cherry picked from commit 63519217e8c0682369b5b3dc11417f79b3b48b7e) --- .../bluetooth/BluetoothHidDeviceAppSdpSettings.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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(); } From d61a0ad43d94b52128b705ec1da058c4ad690f67 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Thu, 29 Nov 2018 17:37:42 +0000 Subject: [PATCH 86/86] RESTRICT AUTOMERGE Do not linkify text with RLO/LRO characters. Also don't show smart actions for selections in text with unsupported characters. Bug: 116321860 Test: runtest -x cts/tests/tests/text/src/android/text/util/cts/LinkifyTest.java Change-Id: Id271cab8aef6b9b13ef17f1a8654c7616f75cf13 (cherry picked from commit 73f398d306a8acf2dbb1dcff4042ecbaec5506a3) --- core/java/android/text/util/Linkify.java | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) 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); }