diff --git a/.classpath b/.classpath deleted file mode 100644 index dcf4178c..00000000 --- a/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..cf01c776 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +charset = utf-8 + +[*.{java,cpp,h,aidl,xml,md}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf + +[*.gradle] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..ae8cd05e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +We ask that you submit a contributor agreement in order for us to accept this request. More information about this agreement can be found [here](https://www.zetetic.net/contributions/). Pull requests are merged into the `master` branch. Please let us know if you have any further questions. Thanks! diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..e62c3968 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 Community support + url: https://discuss.zetetic.net/c/sqlcipher/5 + about: Integration problem or question about SQLCipher for Android, feel free to ask here. + - name: 🔨 Build issue + url: https://discuss.zetetic.net/c/sqlcipher/5 + about: Experience an issue building SQLCipher for Android? Start here. + - name: 📃 SQLCipher documentation + url: https://www.zetetic.net/sqlcipher/sqlcipher-api/ + about: SQLCipher documentation can be found here. + - name: 📖 Contribution instructions + url: https://www.zetetic.net/contributions/ + about: Want to contribute to SQLCipher for Android? Start here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 00000000..4c78327f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,21 @@ +--- +name: 🛠️ Bug report +about: Create a report about a software defect +title: '' +labels: '' +assignees: '' +--- + +### Expected Behavior + +### Actual Behavior + +### Steps to Reproduce + +SQLCipher version (can be identified by executing `PRAGMA cipher_version;`): + +SQLCipher for Android version: + +Are you able to reproduce this issue within the SQLCipher for Android [test suite](https://github.com/sqlcipher/sqlcipher-android-tests)? + +*Note:* If you are not posting a specific issue for the SQLCipher library, please post your question to the SQLCipher [discuss site](https://discuss.zetetic.net/c/sqlcipher). Thanks! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..292dcecc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,2 @@ +Changes proposed in this pull request: +- diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..ff6a36ab --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,21 @@ +# Configuration for probot-stale - https://github.com/probot/stale +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 14 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 14 +# Issues with these labels will never be considered stale +exemptLabels: + - bug + - enhancement + - security +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + Hello, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. + You may also label this issue as "bug", "enhancement", or "security" and I will leave it open. + Thank you for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: > + Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to reopen with up-to-date information. +only: issues diff --git a/.gitignore b/.gitignore index d7186dce..94a7c0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,14 @@ -TAGS -external/libs -jni/libs -obj/ +.gradle +build .DS_Store -local.properties -build.xml -proguard.cfg -bin/ -gen/ -external/openssl -external/sqlcipher +android-database-sqlcipher/src/main/external/sqlcipher +android-database-sqlcipher/src/main/external/openssl +android-database-sqlcipher/src/main/external/android-libs/ +android-database-sqlcipher/.externalNativeBuild/ +android-database-sqlcipher/src/main/libs* +android-database-sqlcipher/src/main/obj +android-database-sqlcipher/src/main/external/openssl-*/ +android-database-sqlcipher/src/main/cpp/sqlite3.[c,h] +.idea/ +*.iml +local.properties \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 543b4b17..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,24 +0,0 @@ -[submodule "external/openssl"] - path = external/openssl - url = git@github.com:guardianproject/openssl-android.git -[submodule "external/sqlcipher"] - path = external/sqlcipher - url = git://github.com/sqlcipher/sqlcipher.git -[submodule "external/dalvik"] - path = external/dalvik - url = git://github.com/android/platform_dalvik.git -[submodule "external/android-sqlite"] - path = external/android-sqlite - url = git://github.com/android/platform_external_sqlite.git -[submodule "external/platform-system-core"] - path = external/platform-system-core - url = git://github.com/android/platform_system_core.git -[submodule "external/platform-frameworks-base"] - path = external/platform-frameworks-base - url = git://github.com/android/platform_frameworks_base.git -[submodule "external/icu4c"] - path = external/icu4c - url = git://github.com/android/platform_external_icu4c.git -[submodule "notepadbot"] - path = dist/SQLCipherForAndroid-SDK/samples/notepadbot - url = git://github.com/guardianproject/notepadbot.git diff --git a/.project b/.project deleted file mode 100644 index a15fae23..00000000 --- a/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - android-database-sqlcipher - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 4ac7a02d..00000000 --- a/AUTHORS +++ /dev/null @@ -1,8 +0,0 @@ - -Stephen Lombardo - original SQLCipher - -Hans-Christoph Steiner - original port to Android, including build scripts - -Nathan Freitas - additional Android development, Database API integration - -Nick Parker - improvements in Android platform compatibility and build scripts diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index c24ec62c..00000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/BUILD b/BUILD deleted file mode 100644 index 0a38b1d2..00000000 --- a/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -git submodule init -git submodule update -cd external -make -f Android.mk build-local-hack -ndk-build -cp libs/armeabi/libsqlcipher_android.so ../libs/armeabi -cd ../jni -ndk-build -cp libs/armeabi/libdatabase_sqlcipher.so ../libs/armeabi -cd .. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile index 6808f843..7a42f371 100644 --- a/Makefile +++ b/Makefile @@ -1,46 +1,100 @@ +.POSIX: +.PHONY: init clean distclean build-openssl build publish-local-snapshot \ + publish-local-release publish-remote-snapshot public-remote-release check +GRADLE = ./gradlew -.DEFAULT_GOAL := all -LIBRARY_ROOT := libs +clean: + $(GRADLE) clean -init: - git submodule update --init - android update project -p . +distclean: + $(GRADLE) distclean \ + -PsqlcipherRoot="$(SQLCIPHER_ROOT)" -all: build-external build-jni build-java copy-libs +build-openssl: + $(GRADLE) buildOpenSSL -build-external: - cd external/ && \ - make -f Android.mk build-local-hack && \ - ndk-build && \ - make -f Android.mk copy-libs-hack +check: + $(GRADLE) check -build-jni: - cd jni/ && \ - ndk-build +format: + $(GRADLE) editorconfigFormat -build-java: - ant release && \ - cd bin/classes && \ - jar -cvf sqlcipher.jar . +build-debug: + $(GRADLE) android-database-sqlcipher:bundleDebugAar \ + -PdebugBuild=true \ + -PsqlcipherRoot="$(SQLCIPHER_ROOT)" \ + -PopensslRoot="$(OPENSSL_ROOT)" \ + -PopensslAndroidNativeRoot="$(OPENSSL_ANDROID_LIB_ROOT)" \ + -PsqlcipherCFlags="$(SQLCIPHER_CFLAGS)" \ + -PsqlcipherAndroidClientVersion="$(SQLCIPHER_ANDROID_VERSION)" -clean: - ant clean - cd external/ && ndk-build clean - cd jni/ && ndk-build clean - -rm ${LIBRARY_ROOT}/armeabi/libsqlcipher_android.so - -rm ${LIBRARY_ROOT}/armeabi/libdatabase_sqlcipher.so - -rm ${LIBRARY_ROOT}/sqlcipher.jar - -copy-libs: - mkdir -p ${LIBRARY_ROOT}/armeabi - cp external/libs/armeabi/libsqlcipher_android.so \ - ${LIBRARY_ROOT}/armeabi && \ - cp jni/libs/armeabi/libdatabase_sqlcipher.so \ - ${LIBRARY_ROOT}/armeabi && \ - cp bin/classes/sqlcipher.jar ${LIBRARY_ROOT} && \ - cp ${ANDROID_NDK_ROOT}/sources/cxx-stl/stlport/libs/armeabi/libstlport_shared.so \ - ${LIBRARY_ROOT}/armeabi - -copy-libs-dist: - cp ${LIBRARY_ROOT}/*.jar dist/SQLCipherForAndroid-SDK/libs/ && \ - cp ${LIBRARY_ROOT}/armeabi/*.so dist/SQLCipherForAndroid-SDK/libs/armeabi/ +build-release: + $(GRADLE) android-database-sqlcipher:bundleReleaseAar \ + -PdebugBuild=false \ + -PsqlcipherRoot="$(SQLCIPHER_ROOT)" \ + -PopensslRoot="$(OPENSSL_ROOT)" \ + -PopensslAndroidNativeRoot="$(OPENSSL_ANDROID_LIB_ROOT)" \ + -PsqlcipherCFlags="$(SQLCIPHER_CFLAGS)" \ + -PsqlcipherAndroidClientVersion="$(SQLCIPHER_ANDROID_VERSION)" + +publish-local-snapshot: + @ $(collect-signing-info) \ + $(GRADLE) \ + -PpublishSnapshot=true \ + -PpublishLocal=true \ + -PsigningKeyId="$$gpgKeyId" \ + -PsigningKeyRingFile="$$gpgKeyRingFile" \ + -PsigningKeyPassword="$$gpgPassword" \ + uploadArchives + +publish-local-release: + @ $(collect-signing-info) \ + $(GRADLE) \ + -PpublishSnapshot=false \ + -PpublishLocal=true \ + -PsigningKeyId="$$gpgKeyId" \ + -PsigningKeyRingFile="$$gpgKeyRingFile" \ + -PsigningKeyPassword="$$gpgPassword" \ + uploadArchives + +publish-remote-snapshot: + @ $(collect-signing-info) \ + $(collect-nexus-info) \ + $(GRADLE) \ + -PpublishSnapshot=true \ + -PpublishLocal=false \ + -PsigningKeyId="$$gpgKeyId" \ + -PsigningKeyRingFile="$$gpgKeyRingFile" \ + -PsigningKeyPassword="$$gpgPassword" \ + -PnexusUsername="$$nexusUsername" \ + -PnexusPassword="$$nexusPassword" \ + uploadArchives + +publish-remote-release: + @ $(collect-signing-info) \ + $(collect-nexus-info) \ + $(GRADLE) \ + -PpublishSnapshot=false \ + -PpublishLocal=false \ + -PdebugBuild=false \ + -PsigningKeyId="$$gpgKeyId" \ + -PsigningKeyRingFile="$$gpgKeyRingFile" \ + -PsigningKeyPassword="$$gpgPassword" \ + -PnexusUsername="$$nexusUsername" \ + -PnexusPassword="$$nexusPassword" \ + -PsqlcipherRoot="$(SQLCIPHER_ROOT)" \ + -PopensslRoot="$(OPENSSL_ROOT)" \ + -PopensslAndroidLibRoot="$(OPENSSL_ANDROID_LIB_ROOT)" \ + -PsqlcipherCFlags="$(SQLCIPHER_CFLAGS)" \ + -PsqlcipherAndroidClientVersion="$(SQLCIPHER_ANDROID_VERSION)" \ + android-database-sqlcipher:publish + +collect-nexus-info := \ + read -p "Enter Nexus username:" nexusUsername; \ + stty -echo; read -p "Enter Nexus password:" nexusPassword; stty echo; + +collect-signing-info := \ + read -p "Enter GPG signing key id:" gpgKeyId; \ + read -p "Enter full path to GPG keyring file \ + (possibly ${HOME}/.gnupg/secring.gpg)" gpgKeyRingFile; \ + stty -echo; read -p "Enter GPG password:" gpgPassword; stty echo; diff --git a/README.md b/README.md new file mode 100644 index 00000000..68637ce0 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +### Deprecated Library + +The `android-database-sqlcipher` project has been [officially deprecated](https://www.zetetic.net/blog/2023/08/31/sqlcipher-4.5.5-release#sqlcipher-android-455). The long-term replacement is [`sqlcipher-android`](https://github.com/sqlcipher/sqlcipher-android). Instructions for migrating from `android-database-sqlcipher` to `sqlcipher-android`may be found [here](https://www.zetetic.net/sqlcipher/sqlcipher-for-android-migration/). + + +### Download Source and Binaries + +The latest AAR binary package information can be [here](https://www.zetetic.net/sqlcipher/open-source), the source can be found [here](https://github.com/sqlcipher/android-database-sqlcipher). +

+ +### Compatibility + +SQLCipher for Android runs on Android from 5.0 (API 21), for `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures. + +### Contributions + +We welcome contributions, to contribute to SQLCipher for Android, a [contributor agreement](https://www.zetetic.net/contributions/) needs to be submitted. All submissions should be based on the `master` branch. + +### An Illustrative Terminal Listing + +A typical SQLite database in unencrypted, and visually parseable even as encoded text. The following example shows the difference between hexdumps of a standard SQLite database and one implementing SQLCipher. + +``` +~ sjlombardo$ hexdump -C sqlite.db +00000000 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 |SQLite format 3.| +… +000003c0 65 74 32 74 32 03 43 52 45 41 54 45 20 54 41 42 |et2t2.CREATE TAB| +000003d0 4c 45 20 74 32 28 61 2c 62 29 24 01 06 17 11 11 |LE t2(a,b)$…..| +… +000007e0 20 74 68 65 20 73 68 6f 77 15 01 03 01 2f 01 6f | the show…./.o| +000007f0 6e 65 20 66 6f 72 20 74 68 65 20 6d 6f 6e 65 79 |ne for the money| + +~ $ sqlite3 sqlcipher.db +sqlite> PRAGMA KEY=’test123′; +sqlite> CREATE TABLE t1(a,b); +sqlite> INSERT INTO t1(a,b) VALUES (‘one for the money’, ‘two for the show’); +sqlite> .quit + +~ $ hexdump -C sqlcipher.db +00000000 84 d1 36 18 eb b5 82 90 c4 70 0d ee 43 cb 61 87 |.?6.?..?p.?C?a.| +00000010 91 42 3c cd 55 24 ab c6 c4 1d c6 67 b4 e3 96 bb |.B?..?| +00000bf0 8e 99 ee 28 23 43 ab a4 97 cd 63 42 8a 8e 7c c6 |..?(#C??.?cB..|?| + +~ $ sqlite3 sqlcipher.db +sqlite> SELECT * FROM t1; +Error: file is encrypted or is not a database +``` +(example courtesy of SQLCipher) + +### Application Integration + +You have a two main options for using SQLCipher for Android in your app: + +- Using it with Room or other consumers of the `androidx.sqlite` API + +- Using the native SQLCipher for Android classes + +In both cases, you will need to add a dependency on `net.zetetic:android-database-sqlcipher`, +such as having the following line in your module's `build.gradle` `dependencies` +closure: + +```gradle +implementation "net.zetetic:android-database-sqlcipher:4.5.3" +implementation "androidx.sqlite:sqlite:2.1.0" +``` + +(replacing `4.5.3` with the version you want) + + + +#### Using SQLCipher for Android With Room + +SQLCipher for Android has a `SupportFactory` class in the `net.sqlcipher.database` package +that can be used to configure Room to use SQLCipher for Android. + +There are three `SupportFactory` constructors: + +- `SupportFactory(byte[] passphrase)` +- `SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook)` +- `SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook, boolean clearPassphrase)` + +All three take a `byte[]` to use as the passphrase (if you have a `char[]`, use +`SQLiteDatabase.getBytes()` to get a suitable `byte[]` to use). + +Two offer a `SQLiteDatabaseHook` parameter that you can use +for executing SQL statements before or after the passphrase is used to key +the database. + +The three-parameter constructor also offers `clearPassphrase`, which defaults +to `true` in the other two constructors. If `clearPassphrase` is set to `true`, +this will zero out the bytes of the `byte[]` after we open the database. This +is safest from a security standpoint, but it does mean that the `SupportFactory` +instance is a single-use object. Attempting to reuse the `SupportFactory` +instance later will result in being unable to open the database, because the +passphrase will be wrong. If you think that you might need to reuse the +`SupportFactory` instance, pass `false` for `clearPassphrase`. + +Then, pass your `SupportFactory` to `openHelperFactory()` on your `RoomDatabase.Builder`: + +```java +final byte[] passphrase = SQLiteDatabase.getBytes(userEnteredPassphrase); +final SupportFactory factory = new SupportFactory(passphrase); +final SomeDatabase room = Room.databaseBuilder(activity, SomeDatabase.class, DB_NAME) + .openHelperFactory(factory) + .build(); +``` + +Now, Room will make all of its database requests using SQLCipher for Android instead +of the framework copy of SQLCipher. + +Note that `SupportFactory` should work with other consumers of the `androidx.sqlite` API; +Room is merely a prominent example. + +#### Using SQLCipher for Android's Native API + +If you have existing SQLite code using classes like `SQLiteDatabase` and `SQLiteOpenHelper`, +converting your code to use SQLCipher for Android mostly is a three-step process: + +1. Replace all `android.database.sqlite.*` `import` statements with ones that +use `net.sqlcipher.database.*` (e.g., convert `android.database.sqlite.SQLiteDatabase` +to `net.sqlcipher.database.SQLiteDatabase`) + +2. Before attempting to open a database, call `SQLiteDatabase.loadLibs()`, passing +in a `Context` (e.g., add this to `onCreate()` of your `Application` subclass, using +the `Application` itself as the `Context`) + +3. When opening a database (e.g., `SQLiteDatabase.openOrCreateDatabase()`), pass +in the passphrase as a `char[]` or `byte[]` + +The rest of your code may not need any changes. + +An article covering both integration of SQLCipher into an Android application as well as building the source can be found [here](https://www.zetetic.net/sqlcipher/sqlcipher-for-android/). + +### ProGuard + +For applications which utilize ProGuard, a few additional rules must be included when using SQLCipher for Android. These rules instruct ProGuard to omit the renaming of the internal SQLCipher classes which are used via lookup from the JNI layer. It is worth noting that since SQLCipher or Android is based on open source code there is little value in obfuscating the library anyway. The more important use of ProGuard is to protect your application code and business logic. + +``` +-keep,includedescriptorclasses class net.sqlcipher.** { *; } +-keep,includedescriptorclasses interface net.sqlcipher.** { *; } +``` + +### Building + +In order to build `android-database-sqlcipher` from source you will need both the Android SDK, Gradle, Android NDK, SQLCipher core source directory, and an OpenSSL source directory. We currently recommend using Android NDK LTS version `23.0.7599858`. + +To complete the `make` command, the `ANDROID_NDK_HOME` environment variable must be defined which should point to your NDK root. Once you have cloned the repo, change directory into the root of the repository and run the following commands: + +``` +SQLCIPHER_ROOT=/some/path/to/sqlcipher-folder \ +OPENSSL_ROOT=/some/path/to/openssl-folder \ +SQLCIPHER_CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2" \ +SQLCIPHER_ANDROID_VERSION="4.5.3" \ +make build-release +``` + +You may specify other build flags/features within `SQLCIPHER_CFLAGS`, however, specifying `-DSQLITE_HAS_CODEC` and `-DSQLITE_TEMP_STORE` is necessary in the list of flags. + +### License + +The Android support libraries are licensed under Apache 2.0, in line with the Android OS code on which they are based. The SQLCipher code itself is licensed under a BSD-style license from Zetetic LLC. Finally, the original SQLite code itself is in the public domain. diff --git a/README.org b/README.org deleted file mode 100644 index 75320306..00000000 --- a/README.org +++ /dev/null @@ -1,94 +0,0 @@ - -Current SDK distro for developers, with the jar’s, .so’s and a quick sample can be found here: - [[https://github.com/sqlcipher/android-database-sqlcipher/downloads]] - -A full demonstration app with the bundled SQLCipher R1 release is here: -[[https://github.com/guardianproject/notepadbot]] - -SQLCipher for Android project source repo is here: -[[https://github.com/sqlcipher/android-database-sqlcipher]] - -*** Update February 2012 - - Adding ICS support to release 1.1.0, SQLCipher for Android now supports platform versions 2.1 - 4.0.3. - -*** Update May 2011: - -After some major breakthroughs during last week’s development sprint, we’re extremely excited to announce SQLCipher for Android, Developer Preview r1. SQLCipher is an SQLite extension that provides transparent 256-bit AES encryption of database files. To date, it has been open-sourced, sponsored and maintained by Zetetic LLC, and we are glad to be able to extend their efforts to a new mobile platform. In the mobile space, SQLCipher has enjoyed widespread use in Apple’s iOS, as well as Nokia / QT for quite some time. Given that Android by default provides integrated support for SQLite databases, our goal was to create an almost identical API for SQLCipher, so that developers of all skill level could use it, without a steep learning curve. - -In an environment where mobile data privacy is increasingly in the headlines, this project will make it easier than ever for mobile developers to properly secure their local application data, and in turn better protect the privacy of their users. The data stored by Android apps protected by this type of encryption will be less vulnerable to access by malicious apps, protected in case of device loss or theft, and highly resistant to mobile data forensics tools that are increasingly used to mass copy a mobile device during routine traffic stops. - -However, while the core SQLCipher database is vetted and market-ready, the Android support libraries in this release are still very much alpha quality, hence the Developer Preview label. This R1 release should not be integrated into critical or production software. Our goal is to give Android developers early access to the technology, so they can provide feedback on our approach, and help us deliver the right offering for securing mobile data. We expect to release a market-ready version this summer, and will be publicly iterating through the codebase until then. - -*** An Illustrative Terminal Listing - -A typical SQLite database in unencrypted, and visually parseable even as encoded text. The following example shows the difference between hexdumps of a standard SQLite db and one implementing SQLCipher. - -: ~ sjlombardo$ hexdump -C sqlite.db -: 00000000 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 |SQLite format 3.| -: … -: 000003c0 65 74 32 74 32 03 43 52 45 41 54 45 20 54 41 42 |et2t2.CREATE TAB| -: 000003d0 4c 45 20 74 32 28 61 2c 62 29 24 01 06 17 11 11 |LE t2(a,b)$…..| -: … -: 000007e0 20 74 68 65 20 73 68 6f 77 15 01 03 01 2f 01 6f | the show…./.o| -: 000007f0 6e 65 20 66 6f 72 20 74 68 65 20 6d 6f 6e 65 79 |ne for the money| -: -: ~ $ sqlite3 sqlcipher.db -: sqlite> PRAGMA KEY=’test123′; -: sqlite> CREATE TABLE t1(a,b); -: sqlite> INSERT INTO t1(a,b) VALUES (‘one for the money’, ‘two for the show’); -: sqlite> .quit -: -: ~ $ hexdump -C sqlite.db -: 00000000 84 d1 36 18 eb b5 82 90 c4 70 0d ee 43 cb 61 87 |.?6.?..?p.?C?a.| -: 00000010 91 42 3c cd 55 24 ab c6 c4 1d c6 67 b4 e3 96 bb |.B?..?| -: 00000bf0 8e 99 ee 28 23 43 ab a4 97 cd 63 42 8a 8e 7c c6 |..?(#C??.?cB..|?| -: -: ~ $ sqlite3 sqlcipher.db -: sqlite> SELECT * FROM t1; -: Error: file is encrypted or is not a database - -(example courtesy of SQLCipher) - -*** Details for Developers - -We’ve packaged up a very simple SDK for any Android developer to add SQLCipher into their app with the following three steps: - -Add a single sqlcipher.jar and a few .so’s to the application libs directory -Update the import path from android.database.sqlite.* to info.guardianproject.database.sqlite.* in any source files that reference it. The original android.database.Cursor can still be used unchanged. -Init the database in onCreate() and pass a variable argument to the open database method with a password*: -SQLiteDatabase.loadLibs(this); //first init the db libraries with the context -SQLiteOpenHelper.getWritableDatabase(“thisismysecret”): -*Note: we are working on some dialog builder helper methods for password and PIN input, password caching, and other features that we would like to standardize across all applications that use SQLCipher. - -*** Compatibility - -The Developer Preview implements SQLCipher v1, is compatible with Android 2.2 & 2.3, and works only within one process (you can’t pass a Cursor from a remote Service to an Activity). - -Notepad + SQLCipher = Notepadbot - -Notepadbot is a sample application pulled from the standard Android samples code and updated to use SQLCipher. You can browse the source here and download the apk here. - -*** Building - -In order to build android-database-sqlcipher from source you will need both the Android SDK as well as Android NDK. Once you have cloned the repo, change directory into the root of the repository and run the following commands: - -: # this only needs to be done once -: make init - -: # to build the source -: make - -Recursively copy the =libs= directory into the root of your application, you will also need the =assets= directory copied into the root of your application folder. A detailed set of instructions and further customization can be found [[http://sqlcipher.net/sqlcipher-for-android/][here]]. - -*** Final Notes - -It’s important to note that this project is not intended to be a distinct, long-term fork of SQLCipher. We’ve been working closely with the SQLCipher team at Zetetic and fully intent to closely maintain the project as SQLCipher evolves, re-integrating changes in upcoming releases such as SQLCipher v2. - -The Android support libraries are licensed under Apache 2.0, in line with the Android OS code on which they are based. The SQLCipher code itself is licensed under a BSD-style license from Zetetic LLC. Finally, the original SQLite code itself is in the public domain. - -*** Downloads and Source - -SQLCipher for Android project source repo is here: [[https://github.com/sqlcipher/android-database-sqlcipher]] -Current SDK distro for developers, with the jar’s, .so’s and a quick sample can be found here: [[https://github.com/sqlcipher/android-database-sqlcipher/downloads]] - diff --git a/SQLCIPHER_LICENSE b/SQLCIPHER_LICENSE index 21566c58..a8ae3b5e 100644 --- a/SQLCIPHER_LICENSE +++ b/SQLCIPHER_LICENSE @@ -2,7 +2,7 @@ http://sqlcipher.net Copyright (c) 2010 Zetetic LLC All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright @@ -13,7 +13,7 @@ http://sqlcipher.net * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - + THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE diff --git a/android-database-sqlcipher/build-openssl-libraries.sh b/android-database-sqlcipher/build-openssl-libraries.sh new file mode 100755 index 00000000..b1424cfa --- /dev/null +++ b/android-database-sqlcipher/build-openssl-libraries.sh @@ -0,0 +1,106 @@ +#! /usr/bin/env bash + +MINIMUM_ANDROID_SDK_VERSION=$1 +MINIMUM_ANDROID_64_BIT_SDK_VERSION=$2 +OPENSSL_DIR=$3 +ANDROID_LIB_ROOT=$4 + +(cd ${OPENSSL_DIR}; + + if [[ ! ${MINIMUM_ANDROID_SDK_VERSION} ]]; then + echo "MINIMUM_ANDROID_SDK_VERSION was not provided, include and rerun" + exit 1 + fi + + if [[ ! ${MINIMUM_ANDROID_64_BIT_SDK_VERSION} ]]; then + echo "MINIMUM_ANDROID_64_BIT_SDK_VERSION was not provided, include and rerun" + exit 1 + fi + + if [[ ! ${ANDROID_NDK_HOME} ]]; then + echo "ANDROID_NDK_HOME environment variable not set, set and rerun" + exit 1 + fi + + HOST_INFO=`uname -a` + case ${HOST_INFO} in + Darwin*) + TOOLCHAIN_SYSTEM=darwin-x86_64 + ;; + Linux*) + if [[ "${HOST_INFO}" == *i686* ]] + then + TOOLCHAIN_SYSTEM=linux-x86 + else + TOOLCHAIN_SYSTEM=linux-x86_64 + fi + ;; + *) + echo "Toolchain unknown for host system" + exit 1 + ;; + esac + + NDK_TOOLCHAIN_VERSION=4.9 + OPENSSL_CONFIGURE_OPTIONS="-fPIC -fstack-protector-all no-idea no-camellia \ + no-seed no-bf no-cast no-rc2 no-rc4 no-rc5 no-md2 \ + no-md4 no-ecdh no-sock no-ssl3 \ + no-dsa no-dh no-ec no-ecdsa no-tls1 \ + no-rfc3779 no-whirlpool no-srp \ + no-mdc2 no-ecdh no-engine \ + no-srtp" + + rm -rf ${ANDROID_LIB_ROOT} + + for SQLCIPHER_TARGET_PLATFORM in armeabi-v7a x86 x86_64 arm64-v8a + do + echo "Building libcrypto.a for ${SQLCIPHER_TARGET_PLATFORM}" + case "${SQLCIPHER_TARGET_PLATFORM}" in + armeabi-v7a) + CONFIGURE_ARCH="android-arm -march=armv7-a" + ANDROID_API_VERSION=${MINIMUM_ANDROID_SDK_VERSION} + OFFSET_BITS=32 + ;; + x86) + CONFIGURE_ARCH=android-x86 + ANDROID_API_VERSION=${MINIMUM_ANDROID_SDK_VERSION} + OFFSET_BITS=32 + ;; + x86_64) + CONFIGURE_ARCH=android64-x86_64 + ANDROID_API_VERSION=${MINIMUM_ANDROID_64_BIT_SDK_VERSION} + OFFSET_BITS=64 + ;; + arm64-v8a) + CONFIGURE_ARCH=android-arm64 + ANDROID_API_VERSION=${MINIMUM_ANDROID_64_BIT_SDK_VERSION} + OFFSET_BITS=64 + ;; + *) + echo "Unsupported build platform:${SQLCIPHER_TARGET_PLATFORM}" + exit 1 + esac + TOOLCHAIN_BIN_PATH=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${TOOLCHAIN_SYSTEM}/bin + PATH=${TOOLCHAIN_BIN_PATH}:${PATH} \ + ./Configure ${CONFIGURE_ARCH} \ + -D__ANDROID_API__=${ANDROID_API_VERSION} \ + -D_FILE_OFFSET_BITS=${OFFSET_BITS} \ + ${OPENSSL_CONFIGURE_OPTIONS} + + if [[ $? -ne 0 ]]; then + echo "Error executing:./Configure ${CONFIGURE_ARCH} ${OPENSSL_CONFIGURE_OPTIONS}" + exit 1 + fi + + make clean + PATH=${TOOLCHAIN_BIN_PATH}:${PATH} \ + make build_libs + + if [[ $? -ne 0 ]]; then + echo "Error executing make for platform:${SQLCIPHER_TARGET_PLATFORM}" + exit 1 + fi + mkdir -p ${ANDROID_LIB_ROOT}/${SQLCIPHER_TARGET_PLATFORM} + mv libcrypto.a ${ANDROID_LIB_ROOT}/${SQLCIPHER_TARGET_PLATFORM} + done +) diff --git a/android-database-sqlcipher/build.gradle b/android-database-sqlcipher/build.gradle new file mode 100644 index 00000000..38d6ab3b --- /dev/null +++ b/android-database-sqlcipher/build.gradle @@ -0,0 +1,58 @@ +apply plugin: "com.android.library" +apply plugin: "org.ec4j.editorconfig" +apply from: "native.gradle" +apply from: "maven.gradle" + +android { + + compileSdkVersion "${compileAndroidSdkVersion}" as Integer + + defaultConfig { + versionName "${clientVersionNumber}" + minSdkVersion "${minimumAndroidSdkVersion}" + targetSdkVersion "${targetAndroidSdkVersion}" + versionCode 1 + versionName "${clientVersionNumber}" + archivesBaseName = "${archivesBaseName}-${versionName}" + } + + editorconfig { + includes = ["src/**", "*.gradle"] + excludes = ["src/main/external/sqlcipher/**", "src/main/external/openssl-*/**"] + } + + buildTypes { + debug { + debuggable true + buildConfigField("String", "VERSION_NAME", "\"${clientVersionNumber}\"") + } + release { + debuggable false + minifyEnabled false + buildConfigField("String", "VERSION_NAME", "\"${clientVersionNumber}\"") + } + } + + sourceSets { + main { + jniLibs.srcDirs "${rootProject.ext.nativeRootOutputDir}/libs" + } + } + + dependencies { + implementation "androidx.sqlite:sqlite:2.2.0" + } + + editorconfig { + excludes = ['src/main/cpp/sqlite3.*', + 'src/main/external/sqlcipher/**', + 'src/main/external/openssl-*/**'] + } + + clean.dependsOn cleanNative + check.dependsOn editorconfigCheck + buildNative.mustRunAfter buildAmalgamation + buildAmalgamation.mustRunAfter buildOpenSSL + preBuild.dependsOn([buildOpenSSL, buildAmalgamation, copyAmalgamation, buildNative]) + buildNative.mustRunAfter(copyAmalgamation) +} diff --git a/android-database-sqlcipher/maven.gradle b/android-database-sqlcipher/maven.gradle new file mode 100644 index 00000000..59389bc7 --- /dev/null +++ b/android-database-sqlcipher/maven.gradle @@ -0,0 +1,99 @@ +apply plugin: "maven-publish" +apply plugin: "signing" +import org.gradle.plugins.signing.Sign + +def isReleaseBuild() { + return mavenVersionName.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('mavenReleaseRepositoryUrl') ? mavenReleaseRepositoryUrl + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getSnapshotRepositoryUrl() { + if(hasProperty('mavenLocalRepositoryPrefix')) { + return "${mavenLocalRepositoryPrefix}${buildDir}/${mavenSnapshotRepositoryUrl}" + } else { + return hasProperty('mavenSnapshotRepositoryUrl') ? mavenSnapshotRepositoryUrl + : "https://oss.sonatype.org/content/repositories/snapshots/" + } +} + +def getRepositoryUsername() { + return hasProperty('nexusUsername') ? nexusUsername : "" +} + +def getRepositoryPassword() { + return hasProperty('nexusPassword') ? nexusPassword : "" +} + +gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.allTasks.any { it instanceof Sign }) { + allprojects { ext."signing.keyId" = "${signingKeyId}" } + allprojects { ext."signing.secretKeyRingFile" = "${signingKeyRingFile}" } + allprojects { ext."signing.password" = "${signingKeyPassword}" } + } +} + + + +afterEvaluate { project -> + publishing { + publications { + mavenJava(MavenPublication) { + from components.release + groupId = mavenGroup + artifactId = mavenArtifactId + version = mavenVersionName + pom { + name = mavenArtifactId + description = mavenPomDescription + url = mavenPomUrl + licenses { + license { + url = mavenLicenseUrl + } + } + developers { + developer { + name = mavenDeveloperName + email = mavenDeveloperEmail + } + } + scm { + connection = mavenScmConnection + developerConnection = mavenScmDeveloperConnection + url = mavenScmUrl + } + } + } + } + repositories { + maven { + def repoUrl = isReleaseBuild() + ? getReleaseRepositoryUrl() + : getSnapshotRepositoryUrl() + url = repoUrl + credentials { + username = getRepositoryUsername() + password = getRepositoryPassword() + } + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("publish") } + sign publishing.publications.mavenJava + } + + task androidSourcesJar(type: Jar) { + classifier = "sources" + from android.sourceSets.main.java.sourceFiles + } + + artifacts { + archives androidSourcesJar + } +} diff --git a/android-database-sqlcipher/native.gradle b/android-database-sqlcipher/native.gradle new file mode 100644 index 00000000..8a04803b --- /dev/null +++ b/android-database-sqlcipher/native.gradle @@ -0,0 +1,170 @@ +import org.gradle.internal.logging.text.StyledTextOutputFactory +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +task buildOpenSSL() { + onlyIf { + def armNativeFile = new File("${androidNativeRootDir}/armeabi-v7a/libcrypto.a") + if (armNativeFile.exists()) { + def out = services.get(StyledTextOutputFactory).create("") + out.style(Style.Normal).text("${androidNativeRootDir}/armeabi-v7a/libcrypto.a exists").style(Style.Info).println(' SKIPPED') + } + return !armNativeFile.exists() + } + doLast { + def nativeRootDirectory = new File("${androidNativeRootDir}") + if(!nativeRootDirectory.exists()){ + nativeRootDirectory.mkdirs() + } + exec { + workingDir "${projectDir}" + commandLine "./build-openssl-libraries.sh", + "${minimumAndroidSdkVersion}", + "${minimumAndroid64BitSdkVersion}", + "${opensslDir}", + "${androidNativeRootDir}" + } + } +} + +task buildAmalgamation() { + onlyIf { + def amalgamation = new File("${sqlcipherDir}/sqlite3.c") + return !amalgamation.exists() + } + doLast { + exec { + workingDir "${sqlcipherDir}" + environment("CFLAGS", "${sqlcipherCFlags}") + commandLine "./configure", "--enable-tempstore=yes", "--with-crypto-lib=none" + } + exec { + workingDir "${sqlcipherDir}" + environment("CFLAGS", "${sqlcipherCFlags}") + commandLine "make", "sqlite3.c" + } + } +} + +task copyAmalgamation() { + doLast { + exec { + workingDir "${sqlcipherDir}" + commandLine "cp", "sqlite3.c", "sqlite3.h", "${nativeRootOutputDir}/cpp/" + } + } +} + +task buildNative() { + description "Build the native SQLCipher binaries" + doLast { + executeNdkBuild( + "${nativeRootOutputDir}/libs32", + file("src/main/cpp").absolutePath, + file("src/main/cpp/Application32.mk").absolutePath, + "${sqlcipherCFlags}", "${otherSqlcipherCFlags}", + "${minimumAndroidSdkVersion}") + executeNdkBuild( + "${nativeRootOutputDir}/libs64", + file("src/main/cpp").absolutePath, + file("src/main/cpp/Application64.mk").absolutePath, + "${sqlcipherCFlags}", "${otherSqlcipherCFlags}", + "${minimumAndroid64BitSdkVersion}") + exec { + workingDir "${nativeRootOutputDir}" + commandLine "mkdir", "-p", "libs" + } + copy { + from fileTree("${nativeRootOutputDir}/libs32").include("*/*") + into "${nativeRootOutputDir}/libs" + from fileTree("${nativeRootOutputDir}/libs64").include("*/*") + into "${nativeRootOutputDir}/libs" + } + } +} + +task cleanOpenSSL() { + description "Clean the OpenSSL native libraries" + doLast { + logger.info "Cleaning OpenSSL native libraries" + exec { + workingDir "${androidNativeRootDir}" + ignoreExitValue true + commandLine "rm", "-rf" + } + } +} + +task cleanSQLCipher() { + description "Clean the SQLCipher source" + doLast { + exec { + workingDir "${sqlcipherDir}" + ignoreExitValue true + commandLine "make", "clean" + } + File amalgamationDestinationSource = new File("${nativeRootOutputDir}/cpp/sqlite3.c") + File amalgamationDestinationHeader = new File("${nativeRootOutputDir}/cpp/sqlite3.h") + if (amalgamationDestinationSource.exists()) amalgamationDestinationSource.delete() + if (amalgamationDestinationHeader.exists()) amalgamationDestinationHeader.delete() + } +} + +task cleanNative() { + description "Clean the native (JNI) build artifacts" + doLast { + logger.info "Cleaning native build artifacts" + ["libs", "libs32", "libs64", "obj"].each { + File file = new File("${projectDir}/src/main/${it}") + if (file.exists()) { + file.deleteDir() + } + } + } +} + +task distclean() { + description "Clean build, SQLCipher, and OpenSSL artifacts" + dependsOn clean, cleanSQLCipher, cleanOpenSSL + doLast { + new File("${androidNativeRootDir}/").deleteDir() + } +} + +def gitClean(directory) { + logger.info "Cleaning directory:${directory}" + exec { + workingDir "${directory}" + commandLine "git", "checkout", "-f" + } + exec { + workingDir "${directory}" + commandLine "git", "clean", "-d", "-f" + } +} + +def executeNdkBuild(outputDir, androidMkDirectory, applicationMkFile, + cflags, otherSqlcipherCFlags, androidVersion) { + logger.info "Executing NDK build command" + def out = services.get(StyledTextOutputFactory).create("") + out.style(Style.Normal).text("SQLCIPHER_CFLAGS=").style(Style.Info).println("${cflags}") + out.style(Style.Normal).text("OPENSSL_DIR=").style(Style.Info).println("${opensslDir}") + out.style(Style.Normal).text("SQLCIPHER_DIR=").style(Style.Info).println("${sqlcipherDir}") + out.style(Style.Normal).text("SQLCIPHER_OTHER_CFLAGS=").style(Style.Info).println("${otherSqlcipherCFlags}") + out.style(Style.Normal).text("ANDROID_NATIVE_ROOT_DIR=").style(Style.Info).println("${androidNativeRootDir}") + out.style(Style.Normal).text("NDK_APP_PLATFORM=").style(Style.Info).println("${androidVersion}") + + exec { + def outputDirectory = "NDK_LIBS_OUT=${outputDir}" + def applicationFile = "NDK_APPLICATION_MK=${applicationMkFile}" + def environmentVariables = ["SQLCIPHER_CFLAGS" : "${cflags}", + "OPENSSL_DIR" : "${opensslDir}", + "SQLCIPHER_DIR" : "${sqlcipherDir}", + "SQLCIPHER_OTHER_CFLAGS" : "${otherSqlcipherCFlags}", + "ANDROID_NATIVE_ROOT_DIR": "${androidNativeRootDir}", + "NDK_APP_PLATFORM" : "${androidVersion}"] + environment(environmentVariables) + commandLine "ndk-build", "V=1", "${ndkBuildType}", + "--environment-overrides", outputDirectory, + "-C", androidMkDirectory, applicationFile + } +} diff --git a/android-database-sqlcipher/src/main/AndroidManifest.xml b/android-database-sqlcipher/src/main/AndroidManifest.xml new file mode 100644 index 00000000..463aa005 --- /dev/null +++ b/android-database-sqlcipher/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/src/net/sqlcipher/IContentObserver.aidl b/android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl similarity index 85% rename from src/net/sqlcipher/IContentObserver.aidl rename to android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl index b534a0e4..22857515 100755 --- a/src/net/sqlcipher/IContentObserver.aidl +++ b/android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl @@ -2,16 +2,16 @@ ** ** Copyright 2007, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at ** -** http://www.apache.org/licenses/LICENSE-2.0 +** http://www.apache.org/licenses/LICENSE-2.0 ** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and ** limitations under the License. */ diff --git a/android-database-sqlcipher/src/main/cpp/Android.mk b/android-database-sqlcipher/src/main/cpp/Android.mk new file mode 100644 index 00000000..0564b775 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/Android.mk @@ -0,0 +1,28 @@ +LOCAL_PATH := $(call my-dir) +MY_PATH := $(LOCAL_PATH) +include $(CLEAR_VARS) +LOCAL_PATH := $(MY_PATH) + +LOCAL_CFLAGS += $(SQLCIPHER_CFLAGS) $(SQLCIPHER_OTHER_CFLAGS) +LOCAL_C_INCLUDES += $(LOCAL_PATH) +LOCAL_LDLIBS := -llog +LOCAL_LDFLAGS += -L$(ANDROID_NATIVE_ROOT_DIR)/$(TARGET_ARCH_ABI) +LOCAL_STATIC_LIBRARIES += static-libcrypto +LOCAL_MODULE := libsqlcipher +LOCAL_SRC_FILES := sqlite3.c \ + jni_exception.cpp \ + net_sqlcipher_database_SQLiteCompiledSql.cpp \ + net_sqlcipher_database_SQLiteDatabase.cpp \ + net_sqlcipher_database_SQLiteProgram.cpp \ + net_sqlcipher_database_SQLiteQuery.cpp \ + net_sqlcipher_database_SQLiteStatement.cpp \ + net_sqlcipher_CursorWindow.cpp \ + CursorWindow.cpp + +include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := static-libcrypto +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_DIR)/include +LOCAL_SRC_FILES := $(ANDROID_NATIVE_ROOT_DIR)/$(TARGET_ARCH_ABI)/libcrypto.a +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android-database-sqlcipher/src/main/cpp/Application32.mk b/android-database-sqlcipher/src/main/cpp/Application32.mk new file mode 100644 index 00000000..8d24d563 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/Application32.mk @@ -0,0 +1,7 @@ +APP_PROJECT_PATH := $(shell pwd) +APP_ABI := armeabi-v7a x86 +APP_PLATFORM := android-$(NDK_APP_PLATFORM) +APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk +APP_STL := c++_static +APP_CFLAGS := -D_FILE_OFFSET_BITS=32 +APP_LDFLAGS += -Wl,--exclude-libs,ALL diff --git a/android-database-sqlcipher/src/main/cpp/Application64.mk b/android-database-sqlcipher/src/main/cpp/Application64.mk new file mode 100644 index 00000000..a4d604b4 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/Application64.mk @@ -0,0 +1,7 @@ +APP_PROJECT_PATH := $(shell pwd) +APP_ABI := x86_64 arm64-v8a +APP_PLATFORM := android-$(NDK_APP_PLATFORM) +APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk +APP_STL := c++_static +APP_CFLAGS := -D_FILE_OFFSET_BITS=64 +APP_LDFLAGS += -Wl,--exclude-libs,ALL diff --git a/jni/CursorWindow.cpp b/android-database-sqlcipher/src/main/cpp/CursorWindow.cpp similarity index 68% rename from jni/CursorWindow.cpp rename to android-database-sqlcipher/src/main/cpp/CursorWindow.cpp index a123bb4e..e58fb3c5 100644 --- a/jni/CursorWindow.cpp +++ b/android-database-sqlcipher/src/main/cpp/CursorWindow.cpp @@ -1,92 +1,60 @@ /* * Copyright (C) 2006-2007 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and * limitations under the License. */ #undef LOG_TAG #define LOG_TAG "CursorWindow" -#include -#include -#include - #include #include #include - +#include #include -#include - #include "CursorWindow.h" - namespace sqlcipher { -CursorWindow::CursorWindow(size_t maxSize) : - mMaxSize(maxSize) +CursorWindow::CursorWindow(size_t initialSize, size_t growthPaddingSize, size_t maxSize) { + mInitialSize = initialSize; + mGrowthPaddingSize = growthPaddingSize; + mMaxSize = maxSize; + LOG_WINDOW("CursorWindow::CursorWindow initialSize:%d growBySize:%d maxSize:%d\n", + initialSize, growthPaddingSize, maxSize); } -bool CursorWindow::setMemory(const android::sp& memory) +bool CursorWindow::initBuffer(bool localOnly) { - mMemory = memory; - mData = (uint8_t *) memory->pointer(); - if (mData == NULL) { - return false; - } + void* data = malloc(mInitialSize); + if(data){ + mData = (uint8_t *) data; mHeader = (window_header_t *) mData; - - // Make the window read-only - ssize_t size = memory->size(); - mSize = size; - mMaxSize = size; - mFreeOffset = size; -LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData); + mSize = mInitialSize; + clear(); + LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mInitialSize = %d, mGrowthPaddingSize = %d, mMaxSize = %d, mData = %p\n", + mFreeOffset, mSize, mInitialSize, mGrowthPaddingSize, mMaxSize, mData); return true; -} - -bool CursorWindow::initBuffer(bool localOnly) -{ - //TODO Use a non-memory dealer mmap region for localOnly - - android::sp heap; - heap = new android::MemoryHeapBase(mMaxSize, 0, "CursorWindow"); - if (heap != NULL) { - mMemory = new android::MemoryBase(heap, 0, mMaxSize); - if (mMemory != NULL) { - mData = (uint8_t *) mMemory->pointer(); - if (mData) { - mHeader = (window_header_t *) mData; - mSize = mMaxSize; - - // Put the window into a clean state - clear(); - LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData); - return true; - } - } - LOGE("CursorWindow heap allocation failed"); - return false; - } else { - LOGE("failed to create the CursorWindow heap"); - return false; - } + } + return false; } CursorWindow::~CursorWindow() { - // Everything that matters is a smart pointer + if(mData){ + free(mData); + } } void CursorWindow::clear() @@ -96,6 +64,8 @@ void CursorWindow::clear() mFreeOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE; // Mark the first chunk's next 'pointer' as null *((uint32_t *)(mData + mFreeOffset - sizeof(uint32_t))) = 0; + mChunkNumToNextChunkOffset.clear(); + mLastChunkPtrOffset = 0; } int32_t CursorWindow::freeSpace() @@ -115,6 +85,9 @@ field_slot_t * CursorWindow::allocRow() return NULL; } + // Record the original offset of the rowSlot prior to allocation of the field directory + uint32_t rowSlotOffset = (uint8_t*)rowSlot - mData; + // Allocate the slots for the field directory size_t fieldDirSize = mHeader->numColumns * sizeof(field_slot_t); uint32_t fieldDirOffset = alloc(fieldDirSize); @@ -126,7 +99,11 @@ field_slot_t * CursorWindow::allocRow() field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(fieldDirOffset); memset(fieldDir, 0x0, fieldDirSize); -LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", (mHeader->numRows - 1), ((uint8_t *)rowSlot) - mData, fieldDirSize, fieldDirOffset); + // Reset the rowSlot pointer relative to mData + // If the last alloc relocated mData this will be rowSlot's new address, otherwise the value will not change + rowSlot = (row_slot_t*)(mData + rowSlotOffset); + + LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", (mHeader->numRows - 1), ((uint8_t *)rowSlot) - mData, fieldDirSize, fieldDirOffset); rowSlot->offset = fieldDirOffset; return fieldDir; @@ -134,39 +111,31 @@ LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at o uint32_t CursorWindow::alloc(size_t requestedSize, bool aligned) { - int32_t size; + size_t size = 0, new_allocation_sz = 0; uint32_t padding; + void *tempData = NULL; if (aligned) { // 4 byte alignment padding = 4 - (mFreeOffset & 0x3); } else { padding = 0; } - size = requestedSize + padding; - if (size > freeSpace()) { - LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows); - // Only grow the window if the first row doesn't fit - if (mHeader->numRows > 1) { -LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize); - return 0; - } - - // Find a new size that will fit the allocation - int allocated = mSize - freeSpace(); - int newSize = mSize + WINDOW_ALLOCATION_SIZE; - while (size > (newSize - allocated)) { - newSize += WINDOW_ALLOCATION_SIZE; - if (newSize > mMaxSize) { - LOGE("Attempting to grow window beyond max size (%d)", mMaxSize); - return 0; - } - } -LOG_WINDOW("found size %d", newSize); - mSize = newSize; + new_allocation_sz = mSize + size - freeSpace() + mGrowthPaddingSize; + LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d new_allocation_sz:%d\n", + mSize, size, freeSpace(), mHeader->numRows, new_allocation_sz); + if(mMaxSize == 0 || new_allocation_sz <= mMaxSize) { + tempData = realloc((void *)mData, new_allocation_sz); + if(tempData == NULL) return 0; + mData = (uint8_t *)tempData; + mHeader = (window_header_t *)mData; + LOGE("allocation grew to:%d", new_allocation_sz); + mSize = new_allocation_sz; + } else { + return 0; + } } - uint32_t offset = mFreeOffset + padding; mFreeOffset += size; return offset; @@ -174,17 +143,29 @@ LOG_WINDOW("found size %d", newSize); row_slot_t * CursorWindow::getRowSlot(int row) { - LOG_WINDOW("enter getRowSlot current row num %d, this row %d", mHeader->numRows, row); + LOG_WINDOW("getRowSlot entered: requesting row:%d, current row num:%d", row, mHeader->numRows); + unordered_map::iterator result; int chunkNum = row / ROW_SLOT_CHUNK_NUM_ROWS; int chunkPos = row % ROW_SLOT_CHUNK_NUM_ROWS; int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t); uint8_t * rowChunk = mData + sizeof(window_header_t); + + // check for chunkNum in cache + result = mChunkNumToNextChunkOffset.find(chunkNum); + if(result != mChunkNumToNextChunkOffset.end()){ + rowChunk = offsetToPtr(result->second); + LOG_WINDOW("Retrieved chunk offset from cache for row:%d", row); + return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t))); + } + + // walk the list, this shouldn't occur + LOG_WINDOW("getRowSlot walking list %d times to find rowslot for row:%d", chunkNum, row); for (int i = 0; i < chunkNum; i++) { rowChunk = offsetToPtr(*((uint32_t *)(mData + chunkPtrOffset))); chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)); } return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t))); - LOG_WINDOW("exit getRowSlot current row num %d, this row %d", mHeader->numRows, row); + LOG_WINDOW("exit getRowSlot current row num %d, this row %d", mHeader->numRows, row); } row_slot_t * CursorWindow::allocRowSlot() @@ -193,38 +174,49 @@ row_slot_t * CursorWindow::allocRowSlot() int chunkPos = mHeader->numRows % ROW_SLOT_CHUNK_NUM_ROWS; int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t); uint8_t * rowChunk = mData + sizeof(window_header_t); -LOG_WINDOW("Allocating row slot, mHeader->numRows is %d, chunkNum is %d, chunkPos is %d", mHeader->numRows, chunkNum, chunkPos); - for (int i = 0; i < chunkNum; i++) { + LOG_WINDOW("allocRowSlot entered: Allocating row slot, mHeader->numRows is %d, chunkNum is %d, chunkPos is %d", + mHeader->numRows, chunkNum, chunkPos); + + if(mLastChunkPtrOffset != 0){ + chunkPtrOffset = mLastChunkPtrOffset; + } + if(chunkNum > 0) { uint32_t nextChunkOffset = *((uint32_t *)(mData + chunkPtrOffset)); -LOG_WINDOW("nextChunkOffset is %d", nextChunkOffset); + LOG_WINDOW("nextChunkOffset is %d", nextChunkOffset); if (nextChunkOffset == 0) { + mLastChunkPtrOffset = chunkPtrOffset; // Allocate a new row chunk nextChunkOffset = alloc(ROW_SLOT_CHUNK_SIZE, true); + mChunkNumToNextChunkOffset.insert(make_pair(chunkNum, nextChunkOffset)); if (nextChunkOffset == 0) { return NULL; } rowChunk = offsetToPtr(nextChunkOffset); -LOG_WINDOW("allocated new chunk at %d, rowChunk = %p", nextChunkOffset, rowChunk); + LOG_WINDOW("allocated new chunk at %d, rowChunk = %p", nextChunkOffset, rowChunk); *((uint32_t *)(mData + chunkPtrOffset)) = rowChunk - mData; // Mark the new chunk's next 'pointer' as null *((uint32_t *)(rowChunk + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t))) = 0; } else { -LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chunkPtrOffset); + LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chunkPtrOffset); rowChunk = offsetToPtr(nextChunkOffset); chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)); + if(chunkPos == ROW_SLOT_CHUNK_NUM_ROWS - 1){ + // prepare to allocate new rowslot_t now at end of row + mLastChunkPtrOffset = chunkPtrOffset; + } } } mHeader->numRows++; - return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t))); } field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column) { + LOG_WINDOW("getFieldSlotWithCheck entered: row:%d column:%d", row, column); if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) { LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns); return NULL; - } + } row_slot_t * rowSlot = getRowSlot(row); if (!rowSlot) { LOGE("Failed to find rowSlot for row %d", row); @@ -233,17 +225,18 @@ field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column) if (rowSlot->offset == 0 || rowSlot->offset >= mSize) { LOGE("Invalid rowSlot, offset = %d", rowSlot->offset); return NULL; - } + } int fieldDirOffset = rowSlot->offset; - return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column; + return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column; } uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut) { + LOG_WINDOW("read_field_slot entered: row:%d, column:%d, slotOut:%p", row, column, slotOut); if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) { LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns); return -1; - } + } row_slot_t * rowSlot = getRowSlot(row); if (!rowSlot) { LOGE("Failed to find rowSlot for row %d", row); @@ -253,9 +246,9 @@ uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotO LOGE("Invalid rowSlot, offset = %d", rowSlot->offset); return -1; } -LOG_WINDOW("Found field directory for %d,%d at rowSlot %d, offset %d", row, column, (uint8_t *)rowSlot - mData, rowSlot->offset); + LOG_WINDOW("Found field directory for %d,%d at rowSlot %d, offset %d", row, column, (uint8_t *)rowSlot - mData, rowSlot->offset); field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(rowSlot->offset); -LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, column, fieldDir[column].data.buffer.offset, fieldDir[column].data.buffer.size, fieldDir[column].type); + LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, column, fieldDir[column].data.buffer.offset, fieldDir[column].data.buffer.size, fieldDir[column].type); // Copy the data to the out param slotOut->data.buffer.offset = fieldDir[column].data.buffer.offset; @@ -266,7 +259,7 @@ LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, co void CursorWindow::copyIn(uint32_t offset, uint8_t const * data, size_t size) { - assert(offset + size <= mSize); + assert(offset + size <= mSize); memcpy(mData + offset, data, size); } @@ -371,7 +364,7 @@ bool CursorWindow::getLong(unsigned int row, unsigned int col, int64_t * valueOu if (!fieldSlot || fieldSlot->type != FIELD_TYPE_INTEGER) { return false; } - + #if WINDOW_STORAGE_INLINE_NUMERICS *valueOut = fieldSlot->data.l; #else @@ -401,7 +394,7 @@ bool CursorWindow::getNull(unsigned int row, unsigned int col, bool * valueOut) if (!fieldSlot) { return false; } - + if (fieldSlot->type != FIELD_TYPE_NULL) { *valueOut = false; } else { diff --git a/jni/CursorWindow.h b/android-database-sqlcipher/src/main/cpp/CursorWindow.h similarity index 89% rename from jni/CursorWindow.h rename to android-database-sqlcipher/src/main/cpp/CursorWindow.h index 4c036cda..9ad5cfb8 100644 --- a/jni/CursorWindow.h +++ b/android-database-sqlcipher/src/main/cpp/CursorWindow.h @@ -1,16 +1,16 @@ /* * Copyright (C) 2006 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and * limitations under the License. */ @@ -20,29 +20,21 @@ #include #include #include - - -#include #include #include - -#include -#include - #include +#include "log.h" +#include - -#define DEFAULT_WINDOW_SIZE 4096 -#define MAX_WINDOW_SIZE (1024 * 1024) -#define WINDOW_ALLOCATION_SIZE 4096 - -#define ROW_SLOT_CHUNK_NUM_ROWS 16 +#define ROW_SLOT_CHUNK_NUM_ROWS 128 +#define INITIAL_WINDOW_SIZE (1024 * 1024) +#define GROW_WINDOW_SIZE_EXTRA INITIAL_WINDOW_SIZE +#define WINDOW_ALLOCATION_UNBOUNDED 0 // Row slots are allocated in chunks of ROW_SLOT_CHUNK_NUM_ROWS, // with an offset after the rows that points to the next chunk #define ROW_SLOT_CHUNK_SIZE ((ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)) + sizeof(uint32_t)) - #if LOG_NDEBUG #define IF_LOG_WINDOW() if (false) @@ -55,13 +47,16 @@ #endif - // When defined to true strings are stored as UTF8, otherwise they're UTF16 -#define WINDOW_STORAGE_UTF8 1 +#define WINDOW_STORAGE_UTF8 0 -// When defined to true numberic values are stored inline in the field_slot_t, otherwise they're allocated in the window +// When defined to true numberic values are stored inline in the field_slot_t, +// otherwise they're allocated in the window #define WINDOW_STORAGE_INLINE_NUMERICS 1 +using std::make_pair; +using std::unordered_map; + namespace sqlcipher { typedef struct @@ -92,7 +87,7 @@ typedef struct #define FIELD_TYPE_FLOAT 2 #define FIELD_TYPE_STRING 3 #define FIELD_TYPE_BLOB 4 -#define FIELD_TYPE_NULL 5 +#define FIELD_TYPE_NULL 0 /** * This class stores a set of rows from a database in a buffer. The begining of the @@ -105,14 +100,11 @@ typedef struct class CursorWindow { public: - CursorWindow(size_t maxSize); + CursorWindow(size_t initialSize, size_t growthPaddingSize, size_t maxSize); CursorWindow(){} - bool setMemory(const android::sp&); ~CursorWindow(); bool initBuffer(bool localOnly); - android::sp getMemory() {return mMemory;} - size_t size() {return mSize;} uint8_t * data() {return mData;} uint32_t getNumRows() {return mHeader->numRows;} @@ -178,7 +170,7 @@ class CursorWindow row_slot_t * allocRowSlot(); row_slot_t * getRowSlot(int row); - + /** * return NULL if Failed to find rowSlot or * Invalid rowSlot @@ -193,14 +185,16 @@ class CursorWindow private: uint8_t * mData; size_t mSize; + size_t mInitialSize; + size_t mGrowthPaddingSize; size_t mMaxSize; window_header_t * mHeader; - android::sp mMemory; - /** * Offset of the lowest unused data byte in the array. */ uint32_t mFreeOffset; + unordered_map mChunkNumToNextChunkOffset; + int mLastChunkPtrOffset; }; }; // namespace sqlcipher diff --git a/android-database-sqlcipher/src/main/cpp/jni_elements.h b/android-database-sqlcipher/src/main/cpp/jni_elements.h new file mode 100644 index 00000000..32367eaa --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/jni_elements.h @@ -0,0 +1,3 @@ +#ifndef NELEM +# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) +#endif diff --git a/android-database-sqlcipher/src/main/cpp/jni_exception.cpp b/android-database-sqlcipher/src/main/cpp/jni_exception.cpp new file mode 100644 index 00000000..142d606f --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/jni_exception.cpp @@ -0,0 +1,7 @@ +#include "jni_exception.h" + +void jniThrowException(JNIEnv* env, const char* exceptionClass, const char* sqlite3Message) { + jclass exClass; + exClass = env->FindClass(exceptionClass); + env->ThrowNew(exClass, sqlite3Message); +} diff --git a/android-database-sqlcipher/src/main/cpp/jni_exception.h b/android-database-sqlcipher/src/main/cpp/jni_exception.h new file mode 100644 index 00000000..2c66be53 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/jni_exception.h @@ -0,0 +1,6 @@ +#include + +#ifndef _JNI_EXCEPTION_H +#define _JNI_EXCEPTION_H +void jniThrowException(JNIEnv* env, const char* exceptionClass, const char* sqlite3Message); +#endif diff --git a/android-database-sqlcipher/src/main/cpp/log.h b/android-database-sqlcipher/src/main/cpp/log.h new file mode 100644 index 00000000..f4da9683 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/log.h @@ -0,0 +1,44 @@ +#include + +#ifdef LOG_NDEBUG +#define LOGI(...) +#define LOGE(...) +#define LOGV(...) +#define LOGD(...) +#else +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) +#endif + +#ifndef LOG +#define LOG(priority, tag, ...) \ + LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +#ifndef LOG_PRI +#define LOG_PRI(priority, tag, ...) \ + __android_log_print(priority, tag, __VA_ARGS__) +#endif + +#ifndef LOG_ASSERT +#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) +#endif + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__) +#endif + +#ifndef LOG_ALWAYS_FATAL_IF +#define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \ + : (void)0 ) +#endif + +#ifndef CONDITION +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) +#endif + +#define android_printAssert(a, b, ...) printf("%s: ", __VA_ARGS__) diff --git a/android-database-sqlcipher/src/main/cpp/net_sqlcipher_CursorWindow.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_CursorWindow.cpp new file mode 100644 index 00000000..b34b1355 --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_CursorWindow.cpp @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "CursorWindow" + +#include +#include +#include +#include +#include + +#include "CursorWindow.h" +#include "jni_elements.h" +#include "jni_exception.h" +#include "sqlite3_exception.h" + +#include +#include + +#include +#include + +namespace sqlcipher { + + static jfieldID gWindowField; + static jfieldID gBufferField; + static jfieldID gSizeCopiedField; + +#define GET_WINDOW(env, object) ((CursorWindow *)env->GetLongField(object, gWindowField)) +#define SET_WINDOW(env, object, window) (env->SetLongField(object, gWindowField,(intptr_t)window)) +#define SET_BUFFER(env, object, buf) (env->SetObjectField(object, gBufferField, buf)) +#define SET_SIZE_COPIED(env, object, size) (env->SetIntField(object, gSizeCopiedField, size)) + + CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow) + { + return GET_WINDOW(env, javaWindow); + } + + static void native_init_empty(JNIEnv * env, jobject object, + jboolean localOnly, jlong initialSize, + jlong growthPaddingSize, jlong maxSize) + { + uint8_t * data; + size_t size; + CursorWindow * window; + + window = new CursorWindow(initialSize, growthPaddingSize, maxSize); + if (!window) { + jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object"); + return; + } + + if (!window->initBuffer(localOnly)) { + jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window"); + delete window; + return; + } + LOG_WINDOW("native_init_empty: window = %p", window); + SET_WINDOW(env, object, window); + } + + static void native_clear(JNIEnv * env, jobject object) + { + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Clearing window %p", window); + if (window == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "clear() called after close()"); + return; + } + window->clear(); + } + + static void native_close(JNIEnv * env, jobject object) + { + CursorWindow * window = GET_WINDOW(env, object); + if (window) { + LOG_WINDOW("Closing window %p", window); + delete window; + SET_WINDOW(env, object, 0); + } + } + + static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column) + { + char buf[100]; + snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column); + jniThrowException(env, "net/sqlcipher/InvalidRowColumnException", buf); + } + + static void throwUnknowTypeException(JNIEnv * env, jint type) + { + char buf[80]; + snprintf(buf, sizeof(buf), "UNKNOWN type %d", type); + jniThrowException(env, "net/sqlcipher/UnknownTypeException", buf); + } + + static jlong getLong_native(JNIEnv * env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Getting long for %d,%d from %p", row, column, window); + + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return 0; + } + + uint8_t type = field.type; + if (type == FIELD_TYPE_INTEGER) { + int64_t value; + if (window->getLong(row, column, &value)) { + return value; + } + return 0; + } else if (type == FIELD_TYPE_STRING) { + uint32_t size = field.data.buffer.size; + if (size > 0) { + long long int result; + jstring data = env->NewString((const jchar*)window->offsetToPtr(field.data.buffer.offset), (jsize)size / sizeof(jchar)); + const char* utf8data = env->GetStringUTFChars(data, NULL); + result = strtoll(utf8data, NULL, 0); + if(utf8data) env->ReleaseStringUTFChars(data, utf8data); + if(data) env->DeleteLocalRef(data); + return result; + } else { + return 0; + } + } else if (type == FIELD_TYPE_FLOAT) { + double value; + if (window->getDouble(row, column, &value)) { + return value; + } + return 0; + } else if (type == FIELD_TYPE_NULL) { + return 0; + } else if (type == FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to long"); + return 0; + } else { + throwUnknowTypeException(env, type); + return 0; + } + } + + static jbyteArray getBlob_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window); + + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return NULL; + } + + uint8_t type = field.type; + if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) { + jbyteArray byteArray = env->NewByteArray(field.data.buffer.size); + if(byteArray == NULL) return NULL; + env->SetByteArrayRegion(byteArray, 0, field.data.buffer.size, + (const jbyte*)window->offsetToPtr(field.data.buffer.offset)); + return byteArray; + } else if (type == FIELD_TYPE_INTEGER) { + throw_sqlite3_exception(env, "INTEGER data in getBlob_native "); + } else if (type == FIELD_TYPE_FLOAT) { + throw_sqlite3_exception(env, "FLOAT data in getBlob_native "); + } else if (type == FIELD_TYPE_NULL) { + // do nothing + } else { + throwUnknowTypeException(env, type); + } + return NULL; + } + + static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Checking if column is a blob or null for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return false; + } + return field.type == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL; + } + + static jboolean isString_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Checking if column is a string or null for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return false; + } + return field.type == FIELD_TYPE_STRING || field.type == FIELD_TYPE_NULL; + } + + static jboolean isInteger_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Checking if column is an integer for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return false; + } + return field.type == FIELD_TYPE_INTEGER; + } + + static jint getType_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Getting type for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return false; + } + return field.type; + } + + static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Checking if column is a float for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return false; + } + return field.type == FIELD_TYPE_FLOAT; + } + + static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column) + { + int i; + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Getting string for %d,%d from %p", row, column, window); + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return NULL; + } + uint8_t type = field.type; + jint size = (jint)field.data.buffer.size; + if (type == FIELD_TYPE_NULL) { + return NULL; + } else if (type == FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to string"); + return NULL; + } else if (type == FIELD_TYPE_STRING) { + return env->NewString((const jchar*)window->offsetToPtr(field.data.buffer.offset), (jsize)size / sizeof(jchar)); + } else if (type == FIELD_TYPE_INTEGER) { + int64_t value; + if (window->getLong(row, column, &value)) { + char buf[32]; + snprintf(buf, sizeof(buf), "%" PRId64 "", value); + return env->NewStringUTF((const char*)buf); + } + return NULL; + } else if (type == FIELD_TYPE_FLOAT) { + double value; + if (window->getDouble(row, column, &value)) { + char buf[32]; + snprintf(buf, sizeof(buf), "%g", value); + return env->NewStringUTF(buf); + } + } + return NULL; + } + + /** + * Use this only to convert characters that are known to be within the + * 0-127 range for direct conversion to UTF-16 + */ + static jint charToJchar(const char* src, jchar* dst, jint bufferSize) + { + int32_t len = strlen(src); + if (bufferSize < len) { + len = bufferSize; + } + for (int i = 0; i < len; i++) { + *dst++ = (*src++ & 0x7F); + } + return len; + } + + static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row, + jint column, jint bufferSize, jobject buf) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Copying string for %d,%d from %p", row, column, window); + + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot"); + return NULL; + } + + jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField); + if (buffer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null"); + return NULL; + } + jchar* dst = env->GetCharArrayElements(buffer, NULL); + uint8_t type = field.type; + uint32_t sizeCopied = 0; + jcharArray newArray = NULL; + if (type == FIELD_TYPE_STRING) { + uint32_t size = field.data.buffer.size; + if (size > 0) { + jsize length = (jsize)size/sizeof(jchar); + int32_t strSize = (jsize)size/sizeof(jchar); + jstring content = env->NewString((const jchar *)window->offsetToPtr(field.data.buffer.offset), length); + const jchar *elements = env->GetStringChars(content, JNI_FALSE); + if (strSize > bufferSize || dst == NULL) { + newArray = env->NewCharArray(length); + env->SetCharArrayRegion(newArray, 0, length, elements); + if(elements) env->ReleaseStringChars(content, elements); + if(content) env->DeleteLocalRef(content); + } else { + memcpy(dst, elements, strSize * 2); + } + sizeCopied = strSize; + } + } else if (type == FIELD_TYPE_INTEGER) { + int64_t value; + if (window->getLong(row, column, &value)) { + int len; + char buf[32]; + len = snprintf(buf, sizeof(buf), "%" PRId64 "", value); + jint bufferLength = env->GetArrayLength(buffer); + if(len > bufferLength || dst == NULL){ + jstring content = env->NewStringUTF(buf); + const jchar *elements = env->GetStringChars(content, JNI_FALSE); + newArray = env->NewCharArray(len); + env->SetCharArrayRegion(newArray, 0, len, elements); + sizeCopied = len; + if(elements) env->ReleaseStringChars(content, elements); + if(content) env->DeleteLocalRef(content); + } else { + memcpy(dst, buf, len); + sizeCopied = charToJchar(buf, dst, bufferSize); + } + } + } else if (type == FIELD_TYPE_FLOAT) { + double value; + if (window->getDouble(row, column, &value)) { + int len; + char buf[32]; + len = snprintf(buf, sizeof(buf), "%g", value); + jint bufferLength = env->GetArrayLength(buffer); + if(len > bufferLength || dst == NULL){ + jstring content = env->NewStringUTF(buf); + const jchar *elements = env->GetStringChars(content, JNI_FALSE); + newArray = env->NewCharArray(len); + env->SetCharArrayRegion(newArray, 0, len, elements); + sizeCopied = len; + if(elements) env->ReleaseStringChars(content, elements); + if(content) env->DeleteLocalRef(content); + } else { + memcpy(dst, buf, len); + sizeCopied = charToJchar(buf, dst, bufferSize); + } + } + } else if (type == FIELD_TYPE_NULL) { + } else if (type == FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to string"); + } else { + LOGE("Unknown field type %d", type); + throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()"); + } + SET_SIZE_COPIED(env, buf, sizeCopied); + env->ReleaseCharArrayElements(buffer, dst, JNI_OK); + return newArray; + } + + static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column) + { + int32_t err; + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); + + field_slot_t field; + err = window->read_field_slot(row, column, &field); + if (err != 0) { + throwExceptionWithRowCol(env, row, column); + return 0.0; + } + + uint8_t type = field.type; + if (type == FIELD_TYPE_FLOAT) { + double value; + if (window->getDouble(row, column, &value)) { + return value; + } + return 0.0; + } else if (type == FIELD_TYPE_STRING) { + uint32_t size = field.data.buffer.size; + if (size > 0) { + double result; + jstring data = env->NewString((const jchar*)window->offsetToPtr(field.data.buffer.offset), (jsize)size / sizeof(jchar)); + const char* utf8data = env->GetStringUTFChars(data, NULL); + result = strtod(utf8data, NULL); + if(utf8data) env->ReleaseStringUTFChars(data, utf8data); + if(data) env->DeleteLocalRef(data); + return result; + } else { + return 0.0; + } + } else if (type == FIELD_TYPE_INTEGER) { + int64_t value; + if (window->getLong(row, column, &value)) { + return (double) value; + } + return 0.0; + } else if (type == FIELD_TYPE_NULL) { + return 0.0; + } else if (type == FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to double"); + return 0.0; + } else { + throwUnknowTypeException(env, type); + return 0.0; + } + } + + static jboolean isNull_native(JNIEnv* env, jobject object, jint row, jint column) + { + CursorWindow * window = GET_WINDOW(env, object); + LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window); + bool isNull; + if (window->getNull(row, column, &isNull)) { + return isNull; + } + //TODO throw execption? + return true; + } + + static jint getNumRows(JNIEnv * env, jobject object) + { + CursorWindow * window = GET_WINDOW(env, object); + return window->getNumRows(); + } + + static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum) + { + CursorWindow * window = GET_WINDOW(env, object); + return window->setNumColumns(columnNum); + } + + static jboolean allocRow(JNIEnv * env, jobject object) + { + CursorWindow * window = GET_WINDOW(env, object); + return window->allocRow() != NULL; + } + + static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col) + { + CursorWindow * window = GET_WINDOW(env, object); + if (!value) { + LOG_WINDOW("How did a null value send to here"); + return false; + } + field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); + if (fieldSlot == NULL) { + LOG_WINDOW(" getFieldSlotWithCheck error "); + return false; + } + + jint len = env->GetArrayLength(value); + int offset = window->alloc(len); + if (!offset) { + LOG_WINDOW("Failed allocating %u bytes", len); + return false; + } + jbyte * bytes = env->GetByteArrayElements(value, NULL); + window->copyIn(offset, (uint8_t const *)bytes, len); + + // This must be updated after the call to alloc(), since that + // may move the field around in the window + fieldSlot->type = FIELD_TYPE_BLOB; + fieldSlot->data.buffer.offset = offset; + fieldSlot->data.buffer.size = len; + env->ReleaseByteArrayElements(value, bytes, JNI_ABORT); + LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset); + return true; + } + + static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col) + { + CursorWindow * window = GET_WINDOW(env, object); + if (!value) { + LOG_WINDOW("How did a null value send to here"); + return false; + } + field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); + if (fieldSlot == NULL) { + LOG_WINDOW(" getFieldSlotWithCheck error "); + return false; + } + +#if WINDOW_STORAGE_UTF8 + int len = env->GetStringUTFLength(value) + 1; + char const * valStr = env->GetStringUTFChars(value, NULL); +#else + int len = env->GetStringLength(value); + // GetStringLength return number of chars and one char takes 2 bytes + len *= 2; + const jchar* valStr = env->GetStringChars(value, NULL); +#endif + if (!valStr) { + LOG_WINDOW("value can't be transfer to UTFChars"); + return false; + } + + int offset = window->alloc(len); + if (!offset) { + LOG_WINDOW("Failed allocating %u bytes", len); +#if WINDOW_STORAGE_UTF8 + env->ReleaseStringUTFChars(value, valStr); +#else + env->ReleaseStringChars(value, valStr); +#endif + return false; + } + + window->copyIn(offset, (uint8_t const *)valStr, len); + + // This must be updated after the call to alloc(), since that + // may move the field around in the window + fieldSlot->type = FIELD_TYPE_STRING; + fieldSlot->data.buffer.offset = offset; + fieldSlot->data.buffer.size = len; + + LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset); +#if WINDOW_STORAGE_UTF8 + env->ReleaseStringUTFChars(value, valStr); +#else + env->ReleaseStringChars(value, valStr); +#endif + + return true; + } + + static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col) + { + CursorWindow * window = GET_WINDOW(env, object); + if (!window->putLong(row, col, value)) { + LOG_WINDOW(" getFieldSlotWithCheck error "); + return false; + } + LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value); + return true; + } + + static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col) + { + CursorWindow * window = GET_WINDOW(env, object); + if (!window->putDouble(row, col, value)) { + LOG_WINDOW(" getFieldSlotWithCheck error "); + return false; + } + LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value); + return true; + } + + static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col) + { + CursorWindow * window = GET_WINDOW(env, object); + if (!window->putNull(row, col)) { + LOG_WINDOW(" getFieldSlotWithCheck error "); + return false; + } + LOG_WINDOW("%d,%d is NULL", row, col); + return true; + } + + // free the last row + static void freeLastRow(JNIEnv * env, jobject object) { + CursorWindow * window = GET_WINDOW(env, object); + window->freeLastRow(); + } + + static JNINativeMethod sMethods[] = + { + /* name, signature, funcPtr */ + {"native_init", "(ZJJJ)V", (void *)native_init_empty}, + // {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory}, + // {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder}, + {"native_clear", "()V", (void *)native_clear}, + {"close_native", "()V", (void *)native_close}, + {"getLong_native", "(II)J", (void *)getLong_native}, + {"getBlob_native", "(II)[B", (void *)getBlob_native}, + {"isBlob_native", "(II)Z", (void *)isBlob_native}, + {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native}, + //{"getString_native", "(II)[B", (void *)getString_native}, + {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native}, + {"getDouble_native", "(II)D", (void *)getDouble_native}, + {"isNull_native", "(II)Z", (void *)isNull_native}, + {"getNumRows_native", "()I", (void *)getNumRows}, + {"setNumColumns_native", "(I)Z", (void *)setNumColumns}, + {"allocRow_native", "()Z", (void *)allocRow}, + {"putBlob_native", "([BII)Z", (void *)putBlob_native}, + {"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native}, + {"putLong_native", "(JII)Z", (void *)putLong_native}, + {"putDouble_native", "(DII)Z", (void *)putDouble_native}, + {"freeLastRow_native", "()V", (void *)freeLastRow}, + {"putNull_native", "(II)Z", (void *)putNull_native}, + {"isString_native", "(II)Z", (void *)isString_native}, + {"isFloat_native", "(II)Z", (void *)isFloat_native}, + {"isInteger_native", "(II)Z", (void *)isInteger_native}, + {"getType_native", "(II)I", (void *)getType_native}, + }; + + int register_android_database_CursorWindow(JNIEnv * env) + { + jclass clazz; + clazz = env->FindClass("net/sqlcipher/CursorWindow"); + if (clazz == NULL) { + LOGE("Can't find net/sqlcipher/CursorWindow"); + return -1; + } + gWindowField = env->GetFieldID(clazz, "nWindow", "J"); + if (gWindowField == NULL) { + LOGE("Error locating fields"); + return -1; + } + clazz = env->FindClass("android/database/CharArrayBuffer"); + if (clazz == NULL) { + LOGE("Can't find android/database/CharArrayBuffer"); + return -1; + } + gBufferField = env->GetFieldID(clazz, "data", "[C"); + if (gBufferField == NULL) { + LOGE("Error locating fields data in CharArrayBuffer"); + return -1; + } + gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I"); + if (gSizeCopiedField == NULL) { + LOGE("Error locating fields sizeCopied in CharArrayBuffer"); + return -1; + } + clazz = env->FindClass("net/sqlcipher/CursorWindow"); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); + } +} // namespace sqlcipher diff --git a/jni/net_sqlcipher_database_SQLiteCompiledSql.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp similarity index 83% rename from jni/net_sqlcipher_database_SQLiteCompiledSql.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp index 8833bda1..4db2864e 100644 --- a/jni/net_sqlcipher_database_SQLiteCompiledSql.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp @@ -18,20 +18,20 @@ #define LOG_TAG "Cursor" #include -#include -#include +// #include +// #include +// #include #include - -#include - #include +#include #include #include - +#include "log.h" +#include "jni_elements.h" +#include "jni_exception.h" #include "sqlite3_exception.h" - namespace sqlcipher { static jfieldID gHandleField; @@ -39,9 +39,9 @@ static jfieldID gStatementField; #define GET_STATEMENT(env, object) \ - (sqlite3_stmt *)env->GetIntField(object, gStatementField) + (sqlite3_stmt *)env->GetLongField(object, gStatementField) #define GET_HANDLE(env, object) \ - (sqlite3 *)env->GetIntField(object, gHandleField) + (sqlite3 *)env->GetLongField(object, gHandleField) sqlite3_stmt * compile(JNIEnv* env, jobject object, @@ -55,7 +55,7 @@ sqlite3_stmt * compile(JNIEnv* env, jobject object, // Make sure not to leak the statement if it already exists if (statement != NULL) { sqlite3_finalize(statement); - env->SetIntField(object, gStatementField, 0); + env->SetLongField(object, gStatementField, 0); } // Compile the SQL @@ -67,7 +67,7 @@ sqlite3_stmt * compile(JNIEnv* env, jobject object, if (err == SQLITE_OK) { // Store the statement in the Java object for future calls LOGV("Prepared statement %p on %p", statement, handle); - env->SetIntField(object, gStatementField, (int)statement); + env->SetLongField(object, gStatementField, (intptr_t)statement); return statement; } else { // Error messages like 'near ")": syntax error' are not @@ -98,7 +98,7 @@ static void native_finalize(JNIEnv* env, jobject object) if (statement != NULL) { sqlite3_finalize(statement); - env->SetIntField(object, gStatementField, 0); + env->SetLongField(object, gStatementField, 0); } } @@ -119,16 +119,14 @@ int register_android_database_SQLiteCompiledSql(JNIEnv * env) return -1; } - gHandleField = env->GetFieldID(clazz, "nHandle", "I"); - gStatementField = env->GetFieldID(clazz, "nStatement", "I"); + gHandleField = env->GetFieldID(clazz, "nHandle", "J"); + gStatementField = env->GetFieldID(clazz, "nStatement", "J"); if (gHandleField == NULL || gStatementField == NULL) { LOGE("Error locating fields"); return -1; } - - return android::AndroidRuntime::registerNativeMethods(env, - "net/sqlcipher/database/SQLiteCompiledSql", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } diff --git a/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDatabase.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDatabase.cpp new file mode 100644 index 00000000..503372ad --- /dev/null +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDatabase.cpp @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2006-2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Database" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "jni_elements.h" +#include "jni_exception.h" +#include "sqlite3_exception.h" +#include "sqlcipher_loading.h" + +#define UTF16_STORAGE 0 +#define INVALID_VERSION -1 +#define SQLITE_SOFT_HEAP_LIMIT (4 * 1024 * 1024) +#define ANDROID_TABLE "android_metadata" +/* uncomment the next line to force-enable logging of all statements */ +// #define DB_LOG_STATEMENTS + +namespace sqlcipher { + + + enum { + OPEN_READWRITE = 0x00000000, + OPEN_READONLY = 0x00000001, + OPEN_READ_MASK = 0x00000001, + NO_LOCALIZED_COLLATORS = 0x00000010, + CREATE_IF_NECESSARY = 0x10000000 + }; + + static jfieldID offset_db_handle; + + static char *createStr(const char *path) { + int len = strlen(path); + char *str = (char *)malloc(len + 1); + strncpy(str, path, len); + str[len] = 0; + return str; + } + + static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) { + // skip printing this message if it is due to certain types of errors + if (iErrCode == SQLITE_CONSTRAINT) return; + LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg); + } + + // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called. + static void registerLoggingFunc(const char *path) { + static bool loggingFuncSet = false; + if (loggingFuncSet) { + return; + } + + LOGV("Registering sqlite logging func \n"); + int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path)); + if (err != SQLITE_OK) { + LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err); + return; + } + loggingFuncSet = true; + } + + int native_status(JNIEnv* env, jobject object, jint operation, jboolean reset) + { + int value; + int highWater; + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + int status = sqlite3_status(operation, &value, &highWater, reset); + if(status != SQLITE_OK){ + throw_sqlite3_exception(env, handle); + } + return value; + } + + void native_key(JNIEnv* env, jobject object, jbyteArray jKey) { + int rc = 0; + int index = 0; + jsize size = 0; + jbyte *key = 0; + sqlite3 *handle = NULL; + handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + if(handle == NULL){ + LOGE("env->GetLongField returned NULL when retrieving sqlite3 *\n"); + } + key = env->GetByteArrayElements(jKey, NULL); + size = env->GetArrayLength(jKey); + if(key == NULL || size == 0) goto done; + rc = sqlite3_key(handle, key, size); + if(rc != SQLITE_OK) { + throw_sqlite3_exception(env, handle); + } + done: + if(key) env->ReleaseByteArrayElements(jKey, key, JNI_ABORT); + } + + void native_rekey(JNIEnv* env, jobject object, jbyteArray jKey) { + int rc = 0; + jsize size = 0; + jbyte *key = 0; + sqlite3 *handle = NULL; + handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + key = env->GetByteArrayElements(jKey, NULL); + size = env->GetArrayLength(jKey); + if(key == NULL || size == 0) goto done; + rc = sqlite3_rekey(handle, key, size); + if(rc != SQLITE_OK) { + throw_sqlite3_exception(env, handle); + } + done: + if(key) env->ReleaseByteArrayElements(jKey, key, JNI_ABORT); + } + + void native_key_mutf8(JNIEnv* env, jobject object, jcharArray jKey) { + int rc; + int idx; + jint releaseElements = 0; + jboolean arrayIsCopy; + sqlite3 *handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + jsize sz = env->GetArrayLength(jKey); + jchar* jKeyChar = env->GetCharArrayElements(jKey, &arrayIsCopy); + jstring key = env->NewString(jKeyChar, sz); + const char* password = env->GetStringUTFChars(key, JNI_FALSE); + int password_sz = env->GetStringUTFLength(key); + if(password_sz > 0){ + rc = sqlite3_key(handle, password, password_sz); + if(rc != SQLITE_OK){ + throw_sqlite3_exception(env, handle); + } + } + env->ReleaseCharArrayElements(jKey, jKeyChar, JNI_ABORT); + env->ReleaseStringUTFChars(key, password); + } + + void native_rawExecSQL(JNIEnv* env, jobject object, jstring sql) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + char const * sqlCommand = env->GetStringUTFChars(sql, NULL); + int status = sqlite3_exec(handle, sqlCommand, NULL, NULL, NULL); + env->ReleaseStringUTFChars(sql, sqlCommand); + if(status != SQLITE_OK){ + throw_sqlite3_exception(env, handle); + } + } + + /* public native void setICURoot(String path); */ + // void setICURoot(JNIEnv* env, jobject object, jstring ICURoot) + // { + // char const * ICURootPath = env->GetStringUTFChars(ICURoot, NULL); + // setenv("SQLCIPHER_ICU_PREFIX", ICURootPath, 1); + // env->ReleaseStringUTFChars(ICURoot, ICURootPath); + // } + + + /* public native void dbopen(String path, int flags, String locale); */ + void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags) + { + int err; + sqlite3 * handle = NULL; + sqlite3_stmt * statement = NULL; + char const * path8 = env->GetStringUTFChars(pathString, NULL); + int sqliteFlags; + + // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called. + registerLoggingFunc(path8); + + // convert our flags into the sqlite flags + if (flags & CREATE_IF_NECESSARY) { + sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + } else if (flags & OPEN_READONLY) { + sqliteFlags = SQLITE_OPEN_READONLY; + } else { + sqliteFlags = SQLITE_OPEN_READWRITE; + } + + err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL); + if (err != SQLITE_OK) { + LOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags); + throw_sqlite3_exception_errcode(env, err, "Could not open database"); + goto done; + } + + // Check that the database is really read/write when that is what we asked for. + if ((sqliteFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(handle, NULL)) { + throw_sqlite3_exception(env, handle, "Could not open the database in read/write mode."); + goto done; + } + + // The soft heap limit prevents the page cache allocations from growing + // beyond the given limit, no matter what the max page cache sizes are + // set to. The limit does not, as of 3.5.0, affect any other allocations. + sqlite3_soft_heap_limit(SQLITE_SOFT_HEAP_LIMIT); + + // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY + err = sqlite3_busy_timeout(handle, 1000 /* ms */); + if (err != SQLITE_OK) { + LOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8); + throw_sqlite3_exception(env, handle, "Could not set busy timeout"); + goto done; + } + +#ifdef DB_INTEGRITY_CHECK + static const char* integritySql = "pragma integrity_check(1);"; + err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL); + if (err != SQLITE_OK) { + LOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8); + throw_sqlite3_exception(env, handle, "sqlite_prepare_v2(handle, \"pragma integrity_check(1);\") failed"); + goto done; + } + + // first is OK or error message + err = sqlite3_step(statement); + if (err != SQLITE_ROW) { + LOGE("integrity check failed for \"%s\"\n", integritySql, path8); + throw_sqlite3_exception(env, handle); + goto done; + } else { + const char *text = (const char*)sqlite3_column_text(statement, 0); + if (strcmp(text, "ok") != 0) { + LOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text); + jniThrowException(env, "net/sqlcipher/database/SQLiteDatabaseCorruptException", text); + goto done; + } + } +#endif + + sqlite3_enable_load_extension(handle, 1); + + LOGV("Opened '%s' - %p\n", path8, handle); + env->SetLongField(object, offset_db_handle, (intptr_t)handle); + handle = NULL; // The caller owns the handle now. + + done: + // Release allocated resources + if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8); + if (statement != NULL) sqlite3_finalize(statement); + if (handle != NULL) sqlite3_close(handle); + } + + static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName) { + char const *path = env->GetStringUTFChars(databaseName, NULL); + if (path == NULL) { + LOGE("Failure in getDatabaseName(). VM ran out of memory?\n"); + return NULL; // VM would have thrown OutOfMemoryError + } + char *dbNameStr = createStr(path); + env->ReleaseStringUTFChars(databaseName, path); + return dbNameStr; + } + + static void sqlTrace(void *databaseName, const char *sql) { + LOGI("sql_statement|%s|%s\n", (char *)databaseName, sql); + } + + /* public native void enableSqlTracing(); */ + static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName)); + } + + static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) { + double d = tm/1000000.0; + LOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql); + } + + /* public native void enableSqlProfiling(); */ + static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName)); + } + + + /* public native void close(); */ + static void dbclose(JNIEnv* env, jobject object) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + + if (handle != NULL) { + // release the memory associated with the traceFuncArg in enableSqlTracing function + void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL); + if (traceFuncArg != NULL) { + free(traceFuncArg); + } + // release the memory associated with the traceFuncArg in enableSqlProfiling function + traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL); + if (traceFuncArg != NULL) { + free(traceFuncArg); + } + LOGV("Closing database: handle=%p\n", handle); + int result = sqlite3_close(handle); + if (result == SQLITE_OK) { + LOGV("Closed %p\n", handle); + env->SetLongField(object, offset_db_handle, 0); + } else { + // This can happen if sub-objects aren't closed first. Make sure the caller knows. + LOGE("sqlite3_close(%p) failed: %d\n", handle, result); + throw_sqlite3_exception(env, handle, "sqlite3_close() failed"); + } + } + } + + /* public native void native_execSQL(String sql); */ + static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString) + { + int err; + int stepErr; + sqlite3_stmt * statement = NULL; + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + jchar const * sql = env->GetStringChars(sqlString, NULL); + jsize sqlLen = env->GetStringLength(sqlString); + + if (sql == NULL || sqlLen == 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string"); + return; + } + + err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); + + env->ReleaseStringChars(sqlString, sql); + + if (err != SQLITE_OK) { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8); + throw_sqlite3_exception(env, handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + return; + } + + stepErr = sqlite3_step(statement); + err = sqlite3_finalize(statement); + + if (stepErr != SQLITE_DONE) { + if (stepErr == SQLITE_ROW) { + throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead."); + } else { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8); + throw_sqlite3_exception(env, handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + + } + } else +#ifndef DB_LOG_STATEMENTS + // IF_LOGV() +#endif + { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGV("Success on %p when executing '%s'\n", handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + } + } + + /* native long lastInsertRow(); */ + static jlong lastInsertRow(JNIEnv* env, jobject object) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + + return sqlite3_last_insert_rowid(handle); + } + + /* native int lastChangeCount(); */ + static jint lastChangeCount(JNIEnv* env, jobject object) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + + return sqlite3_changes(handle); + } + + /* native int native_getDbLookaside(); */ + static jint native_getDbLookaside(JNIEnv* env, jobject object) + { + sqlite3 * handle = (sqlite3 *)env->GetLongField(object, offset_db_handle); + int pCur = -1; + int unused; + sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0); + return pCur; + } + + /* set locale in the android_metadata table, install localized collators, and rebuild indexes */ + // static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags) + // { + // if ((flags & NO_LOCALIZED_COLLATORS)) return; + + // int err; + // char const* locale8 = env->GetStringUTFChars(localeString, NULL); + // sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + // sqlite3_stmt* stmt = NULL; + // char** meta = NULL; + // int rowCount, colCount; + // char* dbLocale = NULL; + + // // create the table, if necessary and possible + // if (!(flags & OPEN_READONLY)) { + // static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)"; + // err = sqlite3_exec(handle, createSql, NULL, NULL, NULL); + // if (err != SQLITE_OK) { + // LOGE("CREATE TABLE " ANDROID_TABLE " failed\n"); + // throw_sqlite3_exception(env, handle, "create locale table failed"); + // goto done; + // } + // } + + // // try to read from the table + // static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1"; + // err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL); + // if (err != SQLITE_OK) { + // LOGE("SELECT locale FROM " ANDROID_TABLE " failed\n"); + // throw_sqlite3_exception(env, handle, "select locale failed"); + // goto done; + // } + + // dbLocale = (rowCount >= 1) ? meta[colCount] : NULL; + + // if (dbLocale != NULL && !strcmp(dbLocale, locale8)) { + // // database locale is the same as the desired locale; set up the collators and go + // err = register_localized_collators(handle, locale8, UTF16_STORAGE); + // if (err != SQLITE_OK) throw_sqlite3_exception(env, handle); + // goto done; // no database changes needed + // } + + // if ((flags & OPEN_READONLY)) { + // // read-only database, so we're going to have to put up with whatever we got + // // For registering new index. Not for modifing the read-only database. + // err = register_localized_collators(handle, locale8, UTF16_STORAGE); + // if (err != SQLITE_OK) throw_sqlite3_exception(env, handle, "register localized collators failed"); + // goto done; + // } + + // // need to update android_metadata and indexes atomically, so use a transaction... + // err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL); + // if (err != SQLITE_OK) { + // LOGE("BEGIN TRANSACTION failed setting locale\n"); + // throw_sqlite3_exception(env, handle, "BEGIN TRANSACTION failed setting locale"); + // goto done; + // } + + // err = register_localized_collators(handle, locale8, UTF16_STORAGE); + // if (err != SQLITE_OK) { + // LOGE("register_localized_collators() failed setting locale\n"); + // throw_sqlite3_exception(env, handle, "register_localized_collators() failed setting locale"); + // goto rollback; + // } + + // err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL); + // if (err != SQLITE_OK) { + // LOGE("DELETE failed setting locale\n"); + // throw_sqlite3_exception(env, handle, "DELETE failed setting locale"); + // goto rollback; + // } + + // static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);"; + // err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL); + // if (err != SQLITE_OK) { + // LOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql); + // throw_sqlite3_exception(env, handle, "sqlite3_prepare_v2() failed setting locale"); + // goto rollback; + // } + + // err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT); + // if (err != SQLITE_OK) { + // LOGE("sqlite3_bind_text() failed setting locale\n"); + // throw_sqlite3_exception(env, handle, "sqlite3_bind_text() failed setting locale"); + // goto rollback; + // } + + // err = sqlite3_step(stmt); + // if (err != SQLITE_OK && err != SQLITE_DONE) { + // LOGE("sqlite3_step(\"%s\") failed setting locale\n", sql); + // throw_sqlite3_exception(env, handle, "sqlite3_step() failed setting locale"); + // goto rollback; + // } + + // err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL); + // if (err != SQLITE_OK) { + // LOGE("REINDEX LOCALIZED failed\n"); + // throw_sqlite3_exception(env, handle, "REINDEX LOCALIZED failed"); + // goto rollback; + // } + + // // all done, yay! + // err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL); + // if (err != SQLITE_OK) { + // LOGE("COMMIT TRANSACTION failed setting locale\n"); + // throw_sqlite3_exception(env, handle, "COMMIT TRANSACTION failed setting locale"); + // goto done; + // } + + // rollback: + // if (err != SQLITE_OK) { + // sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); + // } + + // done: + // if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8); + // if (stmt != NULL) sqlite3_finalize(stmt); + // if (meta != NULL) sqlite3_free_table(meta); + // } + + static jint native_releaseMemory(JNIEnv *env, jobject clazz) + { + // Attempt to release as much memory from the + return sqlite3_release_memory(SQLITE_SOFT_HEAP_LIMIT); + } + + static JNINativeMethod sMethods[] = + { + /* name, signature, funcPtr */ + {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen}, + {"dbclose", "()V", (void *)dbclose}, + {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing}, + {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling}, + {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL}, + {"lastInsertRow", "()J", (void *)lastInsertRow}, + {"lastChangeCount", "()I", (void *)lastChangeCount}, + {"native_getDbLookaside", "()I", (void *)native_getDbLookaside}, + {"releaseMemory", "()I", (void *)native_releaseMemory}, + {"native_rawExecSQL", "(Ljava/lang/String;)V", (void *)native_rawExecSQL}, + {"native_status", "(IZ)I", (void *)native_status}, + {"key_mutf8", "([C)V", (void *)native_key_mutf8}, + {"key", "([B)V", (void *)native_key}, + {"rekey", "([B)V", (void *)native_rekey}, + }; + + int register_android_database_SQLiteDatabase(JNIEnv *env) + { + jclass clazz; + + clazz = env->FindClass("net/sqlcipher/database/SQLiteDatabase"); + if (clazz == NULL) { + LOGE("Can't find net/sqlcipher/database/SQLiteDatabase\n"); + return -1; + } + + offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "J"); + if (offset_db_handle == NULL) { + LOGE("Can't find SQLiteDatabase.mNativeHandle\n"); + return -1; + } + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); + } + + //this code is not executed + extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) + { + JNIEnv *env; + //gJavaVM = vm; + LOGI("JNI_OnLoad called"); + if (vm->GetEnv((void**) &env, JNI_VERSION_1_2) != JNI_OK) { + LOGE("Failed to get the environment using GetEnv()"); + return -1; + } + + LOGI("JNI_OnLoad register methods "); + + register_android_database_SQLiteDatabase(env); + register_android_database_SQLiteCompiledSql(env); + register_android_database_SQLiteQuery(env); + register_android_database_SQLiteProgram(env); + register_android_database_SQLiteStatement(env); + register_android_database_CursorWindow(env); + + //register_android_database_SQLiteDebug(env); + + return JNI_VERSION_1_2; + + } + + /* throw a SQLiteException with a message appropriate for the error in handle */ + void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) { + throw_sqlite3_exception(env, handle, NULL); + } + + /* throw a SQLiteException with the given message */ + void throw_sqlite3_exception(JNIEnv* env, const char* message) { + throw_sqlite3_exception(env, NULL, message); + } + + /* throw a SQLiteException with a message appropriate for the error in handle + concatenated with the given message + */ + void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) { + if (handle && sqlite3_errcode(handle) != SQLITE_OK) { + throw_sqlite3_exception(env, sqlite3_errcode(handle), + sqlite3_errmsg(handle), message); + } else { + // we use SQLITE_OK so that a generic SQLiteException is thrown; + // any code not specified in the switch statement below would do. + throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message); + } + } + + /* throw a SQLiteException for a given error code */ + void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) { + if (errcode == SQLITE_DONE) { + throw_sqlite3_exception(env, errcode, NULL, message); + } else { + char temp[21]; + sprintf(temp, "error code %d", errcode); + throw_sqlite3_exception(env, errcode, temp, message); + } + } + + void throw_sqlite3_exception_errcode(JNIEnv* env, + int errcode, + int extended_err_code, + const char* message) { + if (errcode == SQLITE_DONE) { + throw_sqlite3_exception(env, errcode, NULL, message); + } else { + char temp[55]; + sprintf(temp, "error code %d (extended error code %d)", + errcode, extended_err_code); + throw_sqlite3_exception(env, errcode, temp, message); + } + } + + /* throw a SQLiteException for a given error code, sqlite3message, and + user message + */ + void throw_sqlite3_exception(JNIEnv* env, int errcode, + const char* sqlite3Message, const char* message) { + const char* exceptionClass; + switch (errcode) { + case SQLITE_IOERR: + exceptionClass = "android/database/sqlite/SQLiteDiskIOException"; + break; + case SQLITE_CORRUPT: + exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException"; + break; + case SQLITE_CONSTRAINT: + exceptionClass = "android/database/sqlite/SQLiteConstraintException"; + break; + case SQLITE_ABORT: + exceptionClass = "android/database/sqlite/SQLiteAbortException"; + break; + case SQLITE_DONE: + exceptionClass = "android/database/sqlite/SQLiteDoneException"; + break; + case SQLITE_FULL: + exceptionClass = "android/database/sqlite/SQLiteFullException"; + break; + case SQLITE_MISUSE: + exceptionClass = "android/database/sqlite/SQLiteMisuseException"; + break; + default: + exceptionClass = "android/database/sqlite/SQLiteException"; + break; + } + + if (sqlite3Message != NULL && message != NULL) { + char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3); + if (fullMessage != NULL) { + strcpy(fullMessage, sqlite3Message); + strcat(fullMessage, ": "); + strcat(fullMessage, message); + jniThrowException(env, exceptionClass, fullMessage); + free(fullMessage); + } else { + jniThrowException(env, exceptionClass, sqlite3Message); + } + } else if (sqlite3Message != NULL) { + jniThrowException(env, exceptionClass, sqlite3Message); + } else { + jniThrowException(env, exceptionClass, message); + } + } + + +} // namespace sqlcipher diff --git a/jni/net_sqlcipher_database_SQLiteDebug.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp similarity index 99% rename from jni/net_sqlcipher_database_SQLiteDebug.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp index 130345a3..15a31cf6 100644 --- a/jni/net_sqlcipher_database_SQLiteDebug.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp @@ -108,7 +108,7 @@ static int read_mapinfo(FILE *fp, again: skip = 0; - + if(fgets(line, 1024, fp) == 0) return 0; len = strlen(line); @@ -138,7 +138,7 @@ static int read_mapinfo(FILE *fp, if (sscanf(line, "Private_Dirty: %d kB", &private_dirty) != 1) return 0; if (fgets(line, 1024, fp) == 0) return 0; if (sscanf(line, "Referenced: %d kB", &referenced) != 1) return 0; - + if (skip) { goto again; } @@ -154,11 +154,11 @@ static void load_maps(int pid, int *sharedPages, int *privatePages) { char tmp[128]; FILE *fp; - + sprintf(tmp, "/proc/%d/smaps", pid); fp = fopen(tmp, "r"); if (fp == 0) return; - + while (read_mapinfo(fp, sharedPages, privatePages) != 0) { // Do nothing } diff --git a/jni/net_sqlcipher_database_SQLiteProgram.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp similarity index 91% rename from jni/net_sqlcipher_database_SQLiteProgram.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp index 52c66a0f..2a526077 100644 --- a/jni/net_sqlcipher_database_SQLiteProgram.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp @@ -17,21 +17,20 @@ #undef LOG_TAG #define LOG_TAG "Cursor" -#include -#include -#include +// #include +// #include +// #include +#include #include - -#include - #include #include #include - +#include "log.h" +#include "jni_elements.h" +#include "jni_exception.h" #include "sqlite3_exception.h" - namespace sqlcipher { static jfieldID gHandleField; @@ -39,9 +38,9 @@ static jfieldID gStatementField; #define GET_STATEMENT(env, object) \ - (sqlite3_stmt *)env->GetIntField(object, gStatementField) + (sqlite3_stmt *)env->GetLongField(object, gStatementField) #define GET_HANDLE(env, object) \ - (sqlite3 *)env->GetIntField(object, gHandleField) + (sqlite3 *)env->GetLongField(object, gHandleField) static void native_compile(JNIEnv* env, jobject object, jstring sqlString) { @@ -180,16 +179,14 @@ int register_android_database_SQLiteProgram(JNIEnv * env) return -1; } - gHandleField = env->GetFieldID(clazz, "nHandle", "I"); - gStatementField = env->GetFieldID(clazz, "nStatement", "I"); + gHandleField = env->GetFieldID(clazz, "nHandle", "J"); + gStatementField = env->GetFieldID(clazz, "nStatement", "J"); if (gHandleField == NULL || gStatementField == NULL) { LOGE("Error locating fields"); return -1; } - - return android::AndroidRuntime::registerNativeMethods(env, - "net/sqlcipher/database/SQLiteProgram", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } diff --git a/jni/net_sqlcipher_database_SQLiteQuery.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp similarity index 80% rename from jni/net_sqlcipher_database_SQLiteQuery.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp index 39eb2e29..8dcf139f 100644 --- a/jni/net_sqlcipher_database_SQLiteQuery.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp @@ -18,21 +18,20 @@ #define LOG_TAG "Cursor" #include -#include -#include +// #include +// #include +// #include #include - -#include - #include #include #include - +#include "log.h" +#include "jni_elements.h" +#include "jni_exception.h" #include "CursorWindow.h" #include "sqlite3_exception.h" - namespace sqlcipher { CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow); @@ -45,9 +44,9 @@ static jfieldID gStatementField; #define GET_STATEMENT(env, object) \ - (sqlite3_stmt *)env->GetIntField(object, gStatementField) + (sqlite3_stmt *)env->GetLongField(object, gStatementField) #define GET_HANDLE(env, object) \ - (sqlite3 *)env->GetIntField(object, gHandleField) + (sqlite3 *)env->GetLongField(object, gHandleField) static int skip_rows(sqlite3_stmt *statement, int maxRows) { int retryCount = 0; @@ -105,7 +104,8 @@ static int finish_program_and_get_row_count(sqlite3_stmt *statement) { } static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, - jint startPos, jint offsetParam, jint maxRead, jint lastPos) + jint startPos, jint requiredPos, + jint offsetParam, jint maxRead, jint lastPos) { int err; sqlite3_stmt * statement = GET_STATEMENT(env, object); @@ -115,7 +115,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int retryCount; int boundParams; CursorWindow * window; - + if (statement == NULL) { LOGE("Invalid statement in fillWindow()"); jniThrowException(env, "java/lang/IllegalStateException", @@ -166,8 +166,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, LOGE("startPos %d > actual rows %d", startPos, num); return num; } - } - + } + while(startPos != 0 || numRows < maxRead) { err = sqlite3_step(statement); if (err == SQLITE_ROW) { @@ -179,6 +179,13 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, // the field data is being allocated. { field_slot_t * fieldDir = window->allocRow(); + if(!fieldDir && (startPos + numRows) < requiredPos) { + LOG_WINDOW("Failed to allocate row, resetting window", startPos + numRows); + window->clear(); + window->setNumColumns(numColumns); + fieldDir = window->allocRow(); + LOG_WINDOW("Window reset, row allocated at %p", fieldDir); + } if (!fieldDir) { LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; @@ -187,26 +194,48 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, // Pack the row into the window int i; + bool failed = false; + bool reset = false; for (i = 0; i < numColumns; i++) { + + if(reset) { + LOG_WINDOW("Reset requested for row %d, likely cursor window not large enough for current row\n", + startPos + numRows); + if(!failed && (startPos + numRows) < requiredPos) { + LOG_WINDOW("Reseting window, previously unable to map required row %d into window\n", + requiredPos); + i = 0; + window->clear(); + window->setNumColumns(numColumns); + field_slot_t * fieldDir = window->allocRow(); + if(!fieldDir) { + LOG_WINDOW("Failed to allocate row in reset, bailing\n"); + jniThrowException(env, "net/sqlcipher/RowAllocationException", + "Failed to allocate row in reset within native_fill_window"); + } else { + LOG_WINDOW("Allocated row in reset set\n"); + } + } else { + LOG_WINDOW("Bailing from reset, requested row %d already mapped in cursor window\n", + startPos + numRows); + return startPos + numRows + finish_program_and_get_row_count(statement) + 1; + } + failed = true; + reset = false; + } + int type = sqlite3_column_type(statement, i); if (type == SQLITE_TEXT) { // TEXT data -#if WINDOW_STORAGE_UTF8 - uint8_t const * text = (uint8_t const *)sqlite3_column_text(statement, i); - // SQLite does not include the NULL terminator in size, but does - // ensure all strings are NULL terminated, so increase size by - // one to make sure we store the terminator. - size_t size = sqlite3_column_bytes(statement, i) + 1; -#else uint8_t const * text = (uint8_t const *)sqlite3_column_text16(statement, i); size_t size = sqlite3_column_bytes16(statement, i); -#endif int offset = window->alloc(size); if (!offset) { window->freeLastRow(); LOGE("Failed allocating %u bytes for text/blob at %d,%d", size, startPos + numRows, i); - return startPos + numRows + finish_program_and_get_row_count(statement) + 1; + reset = true; + continue; } window->copyIn(offset, text, size); @@ -225,7 +254,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, if (!window->putLong(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a long in column %d", i); - return startPos + numRows + finish_program_and_get_row_count(statement) + 1; + reset = true; + continue; } LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value); } else if (type == SQLITE_FLOAT) { @@ -234,7 +264,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, if (!window->putDouble(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a double in column %d", i); - return startPos + numRows + finish_program_and_get_row_count(statement) + 1; + reset = true; + continue; } LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value); } else if (type == SQLITE_BLOB) { @@ -246,11 +277,10 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, window->freeLastRow(); LOGE("Failed allocating %u bytes for blob at %d,%d", size, startPos + numRows, i); - return startPos + numRows + finish_program_and_get_row_count(statement) + 1; + reset = true; + continue; } - window->copyIn(offset, blob, size); - // This must be updated after the call to alloc(), since that // may move the field around in the window field_slot_t * fieldSlot = window->getFieldSlot(numRows, i); @@ -273,9 +303,9 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, } if (i < numColumns) { - // Not all the fields fit in the window - // Unknown data error happened - break; + // Not all the fields fit in the window + // Unknown data error happened + break; } // Mark the row as complete in the window @@ -335,7 +365,7 @@ static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex) static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ - {"native_fill_window", "(Lnet/sqlcipher/CursorWindow;IIII)I", (void *)native_fill_window}, + {"native_fill_window", "(Lnet/sqlcipher/CursorWindow;IIIII)I", (void *)native_fill_window}, {"native_column_count", "()I", (void*)native_column_count}, {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name}, }; @@ -351,16 +381,14 @@ int register_android_database_SQLiteQuery(JNIEnv * env) return -1; } - gHandleField = env->GetFieldID(clazz, "nHandle", "I"); - gStatementField = env->GetFieldID(clazz, "nStatement", "I"); + gHandleField = env->GetFieldID(clazz, "nHandle", "J"); + gStatementField = env->GetFieldID(clazz, "nStatement", "J"); if (gHandleField == NULL || gStatementField == NULL) { LOGE("Error locating fields"); return -1; } - - return android::AndroidRuntime::registerNativeMethods(env, - "net/sqlcipher/database/SQLiteQuery", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } diff --git a/jni/net_sqlcipher_database_SQLiteStatement.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp similarity index 79% rename from jni/net_sqlcipher_database_SQLiteStatement.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp index 5e000180..6f6ca32c 100644 --- a/jni/net_sqlcipher_database_SQLiteStatement.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp @@ -19,17 +19,13 @@ #define LOG_TAG "Cursor" #include -#include -#include - #include - -#include - #include #include #include +#include "log.h" +#include "jni_elements.h" #include "sqlite3_exception.h" namespace sqlcipher { @@ -43,9 +39,9 @@ static jfieldID gStatementField; #define GET_STATEMENT(env, object) \ - (sqlite3_stmt *)env->GetIntField(object, gStatementField) + (sqlite3_stmt *)env->GetLongField(object, gStatementField) #define GET_HANDLE(env, object) \ - (sqlite3 *)env->GetIntField(object, gHandleField) + (sqlite3 *)env->GetLongField(object, gHandleField) static void native_execute(JNIEnv* env, jobject object) @@ -59,7 +55,9 @@ static void native_execute(JNIEnv* env, jobject object) // Throw an exception if an error occured if (err != SQLITE_DONE) { - throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle)); + throw_sqlite3_exception_errcode(env, err, + sqlite3_extended_errcode(handle), + sqlite3_errmsg(handle)); } // Reset the statment so it's ready to use again @@ -103,8 +101,13 @@ static jstring native_1x1_string(JNIEnv* env, jobject object) // Handle the result if (err == SQLITE_ROW) { // No errors, read the data and return it - char const * text = (char const *)sqlite3_column_text(statement, 0); - value = env->NewStringUTF(text); + //char const * text = (char const *)sqlite3_column_text(statement, 0); + + const jchar *str = 0; + jint strlength = 0; + str = (const jchar*) sqlite3_column_text16(statement, 0); + strlength = sqlite3_column_bytes16(statement, 0) / sizeof(jchar); + value = str ? env->NewString(str, strlength) : NULL; } else { throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle)); } @@ -135,16 +138,14 @@ int register_android_database_SQLiteStatement(JNIEnv * env) return -1; } - gHandleField = env->GetFieldID(clazz, "nHandle", "I"); - gStatementField = env->GetFieldID(clazz, "nStatement", "I"); + gHandleField = env->GetFieldID(clazz, "nHandle", "J"); + gStatementField = env->GetFieldID(clazz, "nStatement", "J"); if (gHandleField == NULL || gStatementField == NULL) { LOGE("Error locating fields"); return -1; } - - return android::AndroidRuntime::registerNativeMethods(env, - "net/sqlcipher/database/SQLiteStatement", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } diff --git a/jni/sqlcipher_loading.h b/android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h similarity index 93% rename from jni/sqlcipher_loading.h rename to android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h index a057fa94..c9201ed9 100644 --- a/jni/sqlcipher_loading.h +++ b/android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h @@ -17,8 +17,8 @@ #include -#include -#include +/* #include */ +/* #include */ #include diff --git a/jni/sqlite3_exception.h b/android-database-sqlcipher/src/main/cpp/sqlite3_exception.h similarity index 84% rename from jni/sqlite3_exception.h rename to android-database-sqlcipher/src/main/cpp/sqlite3_exception.h index 9385467e..db31e571 100644 --- a/jni/sqlite3_exception.h +++ b/android-database-sqlcipher/src/main/cpp/sqlite3_exception.h @@ -19,8 +19,8 @@ #define _SQLITE3_EXCEPTION_H 1 #include -#include -#include +/* #include */ +/* #include */ #include @@ -42,6 +42,10 @@ void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* messa void throw_sqlite3_exception(JNIEnv* env, int errcode, const char* sqlite3Message, const char* message); -} +void throw_sqlite3_exception_errcode(JNIEnv* env, + int errcode, + int extended_err_code, + const char* message); +} #endif // _SQLITE3_EXCEPTION_H diff --git a/src/net/sqlcipher/AbstractCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java similarity index 92% rename from src/net/sqlcipher/AbstractCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java index 651a1fea..f3eaf8aa 100644 --- a/src/net/sqlcipher/AbstractCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java @@ -36,12 +36,14 @@ * This is an abstract cursor class that handles a lot of the common code * that all cursors need to deal with and is provided for convenience reasons. */ -public abstract class AbstractCursor implements android.database.CrossProcessCursor { +public abstract class AbstractCursor implements android.database.CrossProcessCursor, net.sqlcipher.Cursor { private static final String TAG = "Cursor"; DataSetObservable mDataSetObservable = new DataSetObservable(); ContentObservable mContentObservable = new ContentObservable(); + private Bundle mExtras = Bundle.EMPTY; + /* -------------------------------------------------------- */ /* These need to be implemented by subclasses */ abstract public int getCount(); @@ -56,6 +58,8 @@ public abstract class AbstractCursor implements android.database.CrossProcessCur abstract public double getDouble(int column); abstract public boolean isNull(int column); + abstract public int getType(int column); + // TODO implement getBlob in all cursor types public byte[] getBlob(int column) { throw new UnsupportedOperationException("getBlob is not supported"); @@ -73,11 +77,11 @@ public CursorWindow getWindow() { public int getColumnCount() { return getColumnNames().length; } - + public void deactivate() { deactivateInternal(); } - + /** * @hide */ @@ -88,10 +92,10 @@ public void deactivateInternal() { } mDataSetObservable.notifyInvalidated(); } - + public boolean requery() { if (mSelfObserver != null && mSelfObserverRegistered == false) { - + mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); mSelfObserverRegistered = true; } @@ -102,7 +106,7 @@ public boolean requery() { public boolean isClosed() { return mClosed; } - + public void close() { mClosed = true; mContentObservable.unregisterAll(); @@ -139,7 +143,7 @@ public boolean onMove(int oldPosition, int newPosition) { return true; } - + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { // Default implementation, uses getString String result = getString(columnIndex); @@ -151,9 +155,11 @@ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { result.getChars(0, result.length(), data, 0); } buffer.sizeCopied = result.length(); + } else { + buffer.sizeCopied = 0; } } - + /* -------------------------------------------------------- */ /* Implementation */ public AbstractCursor() { @@ -198,47 +204,14 @@ public final boolean moveToPosition(int position) { return result; } - + /** * Copy data from cursor to CursorWindow * @param position start position of data * @param window */ public void fillWindow(int position, android.database.CursorWindow window) { - if (position < 0 || position > getCount()) { - return; - } - window.acquireReference(); - try { - int oldpos = mPos; - mPos = position - 1; - window.clear(); - window.setStartPosition(position); - int columnNum = getColumnCount(); - window.setNumColumns(columnNum); - while (moveToNext() && window.allocRow()) { - for (int i = 0; i < columnNum; i++) { - String field = getString(i); - if (field != null) { - if (!window.putString(field, mPos, i)) { - window.freeLastRow(); - break; - } - } else { - if (!window.putNull(mPos, i)) { - window.freeLastRow(); - break; - } - } - } - } - - mPos = oldpos; - } catch (IllegalStateException e){ - // simply ignore it - } finally { - window.releaseReference(); - } + DatabaseUtils.cursorFillWindow(this, position, window); } public final boolean move(int offset) { @@ -396,7 +369,7 @@ public boolean update(int columnIndex, Object obj) { // Long.valueOf() returns null sometimes! // Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); - Long rowid = new Long(getLong(mRowIdColumnIndex)); + Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); if (rowid == null) { throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex); } @@ -415,7 +388,7 @@ public boolean update(int columnIndex, Object obj) { /** * Returns true if there are pending updates that have not yet been committed. - * + * * @return true if there are pending updates that have not yet been committed. * @hide * @deprecated @@ -462,7 +435,7 @@ public void unregisterContentObserver(ContentObserver observer) { mContentObservable.unregisterObserver(observer); } } - + /** * This is hidden until the data set change model has been re-evaluated. * @hide @@ -470,18 +443,18 @@ public void unregisterContentObserver(ContentObserver observer) { protected void notifyDataSetChange() { mDataSetObservable.notifyChanged(); } - + /** * This is hidden until the data set change model has been re-evaluated. * @hide */ protected DataSetObservable getDataSetObservable() { return mDataSetObservable; - + } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); - + } public void unregisterDataSetObserver(DataSetObserver observer) { @@ -523,12 +496,20 @@ public void setNotificationUri(ContentResolver cr, Uri notifyUri) { } } + public Uri getNotificationUri() { + return mNotifyUri; + } + public boolean getWantsAllOnMoveCalls() { return false; } + public void setExtras(Bundle extras) { + mExtras = (extras == null) ? Bundle.EMPTY : extras; + } + public Bundle getExtras() { - return Bundle.EMPTY; + return mExtras; } public Bundle respond(Bundle extras) { @@ -630,6 +611,12 @@ public void onChange(boolean selfChange) { protected int mRowIdColumnIndex; protected int mPos; + + /** + * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of + * the column at {@link #mRowIdColumnIndex} for the current row this cursor is + * pointing at. + */ protected Long mCurrentRowID; protected ContentResolver mContentResolver; protected boolean mClosed = false; diff --git a/src/net/sqlcipher/AbstractWindowedCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java similarity index 97% rename from src/net/sqlcipher/AbstractWindowedCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java index c1f707d2..deb25f33 100644 --- a/src/net/sqlcipher/AbstractWindowedCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java @@ -50,18 +50,18 @@ public String getString(int columnIndex) return mWindow.getString(mPos, columnIndex); } - + @Override public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { checkPosition(); - + synchronized(mUpdatedRows) { if (isFieldUpdated(columnIndex)) { super.copyStringToBuffer(columnIndex, buffer); } } - + mWindow.copyStringToBuffer(mPos, columnIndex, buffer); } @@ -210,11 +210,17 @@ public boolean isFloat(int columnIndex) return mWindow.isFloat(mPos, columnIndex); } + @Override + public int getType(int columnIndex) { + checkPosition(); + return mWindow.getType(mPos, columnIndex); + } + @Override protected void checkPosition() { super.checkPosition(); - + if (mWindow == null) { throw new StaleDataException("Access closed cursor"); } @@ -224,7 +230,7 @@ protected void checkPosition() public CursorWindow getWindow() { return mWindow; } - + /** * Set a new cursor window to cursor, usually set a remote cursor window * @param window cursor window @@ -235,7 +241,7 @@ public void setWindow(CursorWindow window) { } mWindow = window; } - + public boolean hasWindow() { return mWindow != null; } diff --git a/src/net/sqlcipher/BulkCursorNative.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java similarity index 97% rename from src/net/sqlcipher/BulkCursorNative.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java index d2069d97..868dde6a 100644 --- a/src/net/sqlcipher/BulkCursorNative.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java @@ -28,7 +28,7 @@ /** * Native implementation of the bulk cursor. This is only for use in implementing * IPC, application code should use the Cursor interface. - * + * * {@hide} */ public abstract class BulkCursorNative extends Binder implements IBulkCursor @@ -54,7 +54,7 @@ static public IBulkCursor asInterface(IBinder obj) return new BulkCursorProxy(obj); } - + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -100,7 +100,7 @@ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) reply.writeNoException(); return true; } - + case CLOSE_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); close(); @@ -166,7 +166,7 @@ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) case RESPOND_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); - Bundle extras = data.readBundle(); + Bundle extras = data.readBundle(getClass().getClassLoader()); Bundle returnExtras = respond(extras); reply.writeNoException(); reply.writeBundle(returnExtras); @@ -215,7 +215,7 @@ public CursorWindow getWindow(int startPos) throws RemoteException mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - + CursorWindow window = null; if (reply.readInt() == 1) { window = CursorWindow.newFromParcel(reply); @@ -253,7 +253,7 @@ public int count() throws RemoteException boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - + int count; if (result == false) { count = -1; @@ -275,14 +275,14 @@ public String[] getColumnNames() throws RemoteException mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - + String[] columnNames = null; int numColumns = reply.readInt(); columnNames = new String[numColumns]; for (int i = 0; i < numColumns; i++) { columnNames[i] = reply.readString(); } - + data.recycle(); reply.recycle(); return columnNames; @@ -315,7 +315,7 @@ public void close() throws RemoteException data.recycle(); reply.recycle(); } - + public int requery(IContentObserver observer, CursorWindow window) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -326,7 +326,7 @@ public int requery(IContentObserver observer, CursorWindow window) throws Remote window.writeToParcel(data, 0); boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0); - + DatabaseUtils.readExceptionFromParcel(reply); int count; @@ -334,7 +334,7 @@ public int requery(IContentObserver observer, CursorWindow window) throws Remote count = -1; } else { count = reply.readInt(); - mExtras = reply.readBundle(); + mExtras = reply.readBundle(getClass().getClassLoader()); } data.recycle(); @@ -355,7 +355,7 @@ public boolean updateRows(Map values) throws RemoteException mRemote.transact(UPDATE_ROWS_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - + boolean result = (reply.readInt() == 1 ? true : false); data.recycle(); @@ -376,7 +376,7 @@ public boolean deleteRow(int position) throws RemoteException mRemote.transact(DELETE_ROW_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - + boolean result = (reply.readInt() == 1 ? true : false); data.recycle(); @@ -412,7 +412,7 @@ public Bundle getExtras() throws RemoteException { DatabaseUtils.readExceptionFromParcel(reply); - mExtras = reply.readBundle(); + mExtras = reply.readBundle(getClass().getClassLoader()); data.recycle(); reply.recycle(); } @@ -431,7 +431,7 @@ public Bundle respond(Bundle extras) throws RemoteException { DatabaseUtils.readExceptionFromParcel(reply); - Bundle returnExtras = reply.readBundle(); + Bundle returnExtras = reply.readBundle(getClass().getClassLoader()); data.recycle(); reply.recycle(); return returnExtras; diff --git a/src/net/sqlcipher/BulkCursorToCursorAdaptor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java similarity index 99% rename from src/net/sqlcipher/BulkCursorToCursorAdaptor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java index c5753daf..932507e3 100644 --- a/src/net/sqlcipher/BulkCursorToCursorAdaptor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java @@ -139,7 +139,7 @@ public void deactivate() { } mWindow = null; } - + @Override public void close() { super.close(); @@ -148,7 +148,7 @@ public void close() { } catch (RemoteException ex) { Log.w(TAG, "Remote process exception when closing"); } - mWindow = null; + mWindow = null; } @Override @@ -189,7 +189,7 @@ public boolean deleteRow() { if (result != false) { // The window contains the old value, discard it mWindow = null; - + // Fix up the position mCount = mBulkCursor.count(); if (mPos < mCount) { @@ -246,7 +246,7 @@ public boolean commitUpdates(Map + * Returned column types are + *
    + *
  • {@link #FIELD_TYPE_NULL}
  • + *
  • {@link #FIELD_TYPE_INTEGER}
  • + *
  • {@link #FIELD_TYPE_FLOAT}
  • + *
  • {@link #FIELD_TYPE_STRING}
  • + *
  • {@link #FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param columnIndex the zero-based index of the target column. + * @return column value type + */ + int getType(int columnIndex); +} diff --git a/src/net/sqlcipher/CursorIndexOutOfBoundsException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java similarity index 100% rename from src/net/sqlcipher/CursorIndexOutOfBoundsException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java diff --git a/src/net/sqlcipher/CursorWindow.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java similarity index 70% rename from src/net/sqlcipher/CursorWindow.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java index 78d08882..b130dc26 100644 --- a/src/net/sqlcipher/CursorWindow.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java @@ -17,9 +17,22 @@ package net.sqlcipher; import android.database.CharArrayBuffer; + +import android.content.res.Resources; +import android.database.sqlite.SQLiteClosable; +import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; +import android.util.Log; +import android.util.SparseIntArray; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import net.sqlcipher.CursorWindowAllocation; +import net.sqlcipher.DefaultCursorWindowAllocation; /** * A buffer containing multiple cursor rows. @@ -27,11 +40,25 @@ public class CursorWindow extends android.database.CursorWindow implements Parcelable { /** The pointer to the native window class */ @SuppressWarnings("unused") - private int nWindow; + /** The pointer to the native window class. set by the native methods in + * android_database_CursorWindow.cpp + */ + private long nWindow; private int mStartPos; + private int mRequiredPos; - /** + private static CursorWindowAllocation allocation = new DefaultCursorWindowAllocation(); + + public static void setCursorWindowAllocation(CursorWindowAllocation value){ + allocation = value; + } + + public static CursorWindowAllocation getCursorWindowAllocation() { + return allocation; + } + + /** * Creates a new empty window. * * @param localWindow true if this window will be used in this process only @@ -39,7 +66,13 @@ public class CursorWindow extends android.database.CursorWindow implements Parce public CursorWindow(boolean localWindow) { super(localWindow); mStartPos = 0; - native_init(localWindow); + if(allocation == null){ + allocation = new DefaultCursorWindowAllocation(); + } + native_init(localWindow, + allocation.getInitialAllocationSize(), + allocation.getGrowthPaddingSize(), + allocation.getMaxAllocationSize()); } /** @@ -59,11 +92,19 @@ public int getStartPosition() { */ public void setStartPosition(int pos) { mStartPos = pos; - } - + } + + public int getRequiredPosition(){ + return mRequiredPos; + } + + public void setRequiredPosition(int pos) { + mRequiredPos = pos; + } + /** * Returns the number of rows in this window. - * + * * @return the number of rows in this window. */ public int getNumRows() { @@ -74,10 +115,10 @@ public int getNumRows() { releaseReference(); } } - + private native int getNumRows_native(); /** - * Set number of Columns + * Set number of Columns * @param columnNum * @return true if success */ @@ -89,9 +130,9 @@ public boolean setNumColumns(int columnNum) { releaseReference(); } } - + private native boolean setNumColumns_native(int columnNum); - + /** * Allocate a row in cursor window * @return false if cursor window is out of memory @@ -104,9 +145,9 @@ public boolean allocRow(){ releaseReference(); } } - - private native boolean allocRow_native(); - + + private native boolean allocRow_native(); + /** * Free the last row */ @@ -118,7 +159,7 @@ public void freeLastRow(){ releaseReference(); } } - + private native void freeLastRow_native(); /** @@ -136,8 +177,8 @@ public boolean putBlob(byte[] value, int row, int col) { releaseReference(); } } - - private native boolean putBlob_native(byte[] value, int row, int col); + + private native boolean putBlob_native(byte[] value, int row, int col); /** * Copy String to cursor window @@ -154,9 +195,9 @@ public boolean putString(String value, int row, int col) { releaseReference(); } } - - private native boolean putString_native(String value, int row, int col); - + + private native boolean putString_native(String value, int row, int col); + /** * Copy integer to cursor window * @param value @@ -172,12 +213,12 @@ public boolean putLong(long value, int row, int col) { releaseReference(); } } - + private native boolean putLong_native(long value, int row, int col); - + /** - * Copy double to cursor window + * Copy double to cursor window * @param value * @param row * @param col @@ -191,8 +232,8 @@ public boolean putDouble(double value, int row, int col) { releaseReference(); } } - - private native boolean putDouble_native(double value, int row, int col); + + private native boolean putDouble_native(double value, int row, int col); /** * Set the [row, col] value to NULL @@ -208,13 +249,13 @@ public boolean putNull(int row, int col) { releaseReference(); } } - + private native boolean putNull_native(int row, int col); - + /** * Returns {@code true} if given field is {@code NULL}. - * + * * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is {@code NULL} @@ -227,9 +268,9 @@ public boolean isNull(int row, int col) { releaseReference(); } } - + private native boolean isNull_native(int row, int col); - + /** * Returns a byte array for the given field. * @@ -246,14 +287,50 @@ public byte[] getBlob(int row, int col) { } } + /** + * Returns the value at (row, col) as a byte array. + * + *

If the value is null, then null is returned. If the + * type of column col is a string type, then the result + * is the array of bytes that make up the internal representation of the + * string value. If the type of column col is integral or floating-point, + * then an {@link SQLiteException} is thrown. + */ private native byte[] getBlob_native(int row, int col); + /** + * Returns data type of the given column's value. + *

+ * Returned column types are + *

    + *
  • {@link Cursor#FIELD_TYPE_NULL}
  • + *
  • {@link Cursor#FIELD_TYPE_INTEGER}
  • + *
  • {@link Cursor#FIELD_TYPE_FLOAT}
  • + *
  • {@link Cursor#FIELD_TYPE_STRING}
  • + *
  • {@link Cursor#FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param row the row to read from, row - getStartPosition() being the actual row in the window + * @param col the column to read from + * @return the value type + */ + public int getType(int row, int col) { + acquireReference(); + try { + return getType_native(row - mStartPos, col); + } finally { + releaseReference(); + } + } + /** * Checks if a field contains either a blob or is null. * * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is {@code NULL} or a blob + * @deprecated use {@link #getType(int, int)} instead */ public boolean isBlob(int row, int col) { acquireReference(); @@ -270,6 +347,7 @@ public boolean isBlob(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is a long + * @deprecated use {@link #getType(int, int)} instead */ public boolean isLong(int row, int col) { acquireReference(); @@ -286,6 +364,7 @@ public boolean isLong(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is a float + * @deprecated use {@link #getType(int, int)} instead */ public boolean isFloat(int row, int col) { acquireReference(); @@ -302,6 +381,7 @@ public boolean isFloat(int row, int col) { * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return {@code true} if given field is {@code NULL} or a String + * @deprecated use {@link #getType(int, int)} instead */ public boolean isString(int row, int col) { acquireReference(); @@ -317,31 +397,47 @@ public boolean isString(int row, int col) { private native boolean isInteger_native(int row, int col); private native boolean isFloat_native(int row, int col); + private native int getType_native(int row, int col); + /** * Returns a String for the given field. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return a String value for the given field */ public String getString(int row, int col) { acquireReference(); try { - return getString_native(row - mStartPos, col); + return getString_native(row - mStartPos, col); } finally { releaseReference(); } } - + + /** + * Returns the value at (row, col) as a String. + * + *

If the value is null, then null is returned. If the + * type of column col is integral, then the result is the string + * that is obtained by formatting the integer value with the printf + * family of functions using format specifier %lld. If the + * type of column col is floating-point, then the result is the string + * that is obtained by formatting the floating-point value with the + * printf family of functions using format specifier %g. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native String getString_native(int row, int col); + //private native byte[] getString_native(int row, int col); /** * copy the text for the given field in the provided char array. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from - * @param buffer the CharArrayBuffer to copy the text into, - * If the requested string is larger than the buffer + * @param buffer the CharArrayBuffer to copy the text into, + * If the requested string is larger than the buffer * a new char buffer will be created to hold the string. and assigne to * CharArrayBuffer.data */ @@ -363,15 +459,15 @@ public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) { releaseReference(); } } - + private native char[] copyStringToBuffer_native( int row, int col, int bufferSize, CharArrayBuffer buffer); - + /** * Returns a long for the given field. * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return a long value for the given field */ @@ -383,14 +479,25 @@ public long getLong(int row, int col) { releaseReference(); } } - + + /** + * Returns the value at (row, col) as a long. + * + *

If the value is null, then 0L is returned. If the + * type of column col is a string type, then the result + * is the long that is obtained by parsing the string value with + * strtoll. If the type of column col is + * floating-point, then the result is the floating-point value casted to a long. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native long getLong_native(int row, int col); /** * Returns a double for the given field. * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return a double value for the given field */ @@ -402,14 +509,25 @@ public double getDouble(int row, int col) { releaseReference(); } } - + + /** + * Returns the value at (row, col) as a double. + * + *

If the value is null, then 0.0 is returned. If the + * type of column col is a string type, then the result + * is the double that is obtained by parsing the string value with + * strtod. If the type of column col is + * integral, then the result is the integer value casted to a double. + * If the type of column col is a blob type, then an + * {@link SQLiteException} is thrown. + */ private native double getDouble_native(int row, int col); /** * Returns a short for the given field. * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return a short value for the given field */ @@ -424,8 +542,8 @@ public short getShort(int row, int col) { /** * Returns an int for the given field. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return an int value for the given field */ @@ -437,12 +555,12 @@ public int getInt(int row, int col) { releaseReference(); } } - + /** * Returns a float for the given field. * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window + * + * @param row the row to read from, row - getStartPosition() being the actual row in the window * @param col the column to read from * @return a float value for the given field */ @@ -453,8 +571,8 @@ public float getFloat(int row, int col) { } finally { releaseReference(); } - } - + } + /** * Clears out the existing contents of the window, making it safe to reuse * for new data. Note that the number of columns in the window may NOT @@ -463,7 +581,7 @@ public float getFloat(int row, int col) { public void clear() { acquireReference(); try { - mStartPos = 0; + mStartPos = 0; native_clear(); } finally { releaseReference(); @@ -479,15 +597,18 @@ public void clear() { public void close() { releaseReference(); } - + private native void close_native(); @Override protected void finalize() { // Just in case someone forgot to call close... + if (nWindow == 0) { + return; + } close_native(); } - + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public CursorWindow createFromParcel(Parcel source) { @@ -513,9 +634,9 @@ public void writeToParcel(Parcel dest, int flags) { } public CursorWindow(Parcel source,int foo) { - + super(true); - + IBinder nativeBinder = source.readStrongBinder(); mStartPos = source.readInt(); @@ -526,7 +647,8 @@ public CursorWindow(Parcel source,int foo) { private native IBinder native_getBinder(); /** Does the native side initialization for an empty window */ - private native void native_init(boolean localOnly); + private native void native_init(boolean localOnly, long initialSize, + long growthPaddingSize, long maxSize); /** Does the native side initialization with an existing binder from another process */ private native void native_init(IBinder nativeBinder); @@ -534,6 +656,7 @@ public CursorWindow(Parcel source,int foo) { @Override protected void onAllReferencesReleased() { close_native(); - super.onAllReferencesReleased(); + + super.onAllReferencesReleased(); } } diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java new file mode 100644 index 00000000..6b4c47f2 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindowAllocation.java @@ -0,0 +1,7 @@ +package net.sqlcipher; + +public interface CursorWindowAllocation { + long getInitialAllocationSize(); + long getGrowthPaddingSize(); + long getMaxAllocationSize(); +} diff --git a/src/net/sqlcipher/SQLException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java similarity index 59% rename from src/net/sqlcipher/SQLException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java index 8c8c0373..aae6be0f 100644 --- a/src/net/sqlcipher/SQLException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java @@ -17,14 +17,23 @@ package net.sqlcipher; /** - * An exception that indicates there was an error with SQL parsing or execution. + * Extension of android.database.CursorWrapper to support getType() for API < 11. */ -public class SQLException extends RuntimeException -{ - public SQLException() {} +public class CursorWrapper extends android.database.CursorWrapper implements Cursor { - public SQLException(String error) - { - super(error); + private final Cursor mCursor; + + public CursorWrapper(Cursor cursor) { + super(cursor); + mCursor = cursor; + } + + public int getType(int columnIndex) { + return mCursor.getType(columnIndex); + } + + public Cursor getWrappedCursor() { + return mCursor; } } + diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java new file mode 100644 index 00000000..9575dafe --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CustomCursorWindowAllocation.java @@ -0,0 +1,30 @@ +package net.sqlcipher; + +import net.sqlcipher.CursorWindowAllocation; + +public class CustomCursorWindowAllocation implements CursorWindowAllocation { + + private long initialAllocationSize = 0L; + private long growthPaddingSize = 0L; + private long maxAllocationSize = 0L; + + public CustomCursorWindowAllocation(long initialSize, + long growthPaddingSize, + long maxAllocationSize){ + this.initialAllocationSize = initialSize; + this.growthPaddingSize = growthPaddingSize; + this.maxAllocationSize = maxAllocationSize; + } + + public long getInitialAllocationSize() { + return initialAllocationSize; + } + + public long getGrowthPaddingSize() { + return growthPaddingSize; + } + + public long getMaxAllocationSize() { + return maxAllocationSize; + } +} diff --git a/src/net/sqlcipher/database/SQLiteException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java similarity index 51% rename from src/net/sqlcipher/database/SQLiteException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java index 2c7f11a3..58096f1d 100644 --- a/src/net/sqlcipher/database/SQLiteException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,20 @@ * limitations under the License. */ -package net.sqlcipher.database; +package net.sqlcipher; -import net.sqlcipher.*; +import net.sqlcipher.database.SQLiteDatabase; /** - * A SQLite exception that indicates there was an error with SQL parsing or execution. + * An interface to let the apps define the actions to take when the following errors are detected + * database corruption */ -public class SQLiteException extends SQLException { - public SQLiteException() {} +public interface DatabaseErrorHandler { - public SQLiteException(String error) { - super(error); - } + /** + * defines the method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + void onCorruption(SQLiteDatabase dbObj); } diff --git a/src/net/sqlcipher/DatabaseUtils.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java similarity index 87% rename from src/net/sqlcipher/DatabaseUtils.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java index 34b9a220..a89bebab 100644 --- a/src/net/sqlcipher/DatabaseUtils.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java @@ -16,15 +16,7 @@ package net.sqlcipher; -import android.database.Cursor; - -import net.sqlcipher.database.SQLiteAbortException; -import net.sqlcipher.database.SQLiteConstraintException; import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteDatabaseCorruptException; -import net.sqlcipher.database.SQLiteDiskIOException; -import net.sqlcipher.database.SQLiteException; -import net.sqlcipher.database.SQLiteFullException; import net.sqlcipher.database.SQLiteProgram; import net.sqlcipher.database.SQLiteStatement; @@ -34,10 +26,16 @@ import java.util.HashMap; import java.util.Map; -import org.apache.commons.codec.binary.Hex; - import android.content.ContentValues; import android.content.OperationApplicationException; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteAbortException; +import android.database.sqlite.SQLiteConstraintException; +import android.database.sqlite.SQLiteDatabaseCorruptException; +import android.database.sqlite.SQLiteDiskIOException; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteFullException; import android.os.Parcel; import android.text.TextUtils; import android.util.Config; @@ -75,7 +73,7 @@ public static final void writeExceptionToParcel(Parcel reply, Exception e) { code = 3; } else if (e instanceof SQLiteAbortException) { code = 4; - } else if (e instanceof SQLiteConstraintException) { + } else if (e instanceof android.database.sqlite.SQLiteConstraintException) { code = 5; } else if (e instanceof SQLiteDatabaseCorruptException) { code = 6; @@ -194,6 +192,37 @@ public static void bindObjectToProgram(SQLiteProgram prog, int index, } } + /** + * Returns data type of the given object's value. + *

+ * Returned values are + *

    + *
  • {@link Cursor#FIELD_TYPE_NULL}
  • + *
  • {@link Cursor#FIELD_TYPE_INTEGER}
  • + *
  • {@link Cursor#FIELD_TYPE_FLOAT}
  • + *
  • {@link Cursor#FIELD_TYPE_STRING}
  • + *
  • {@link Cursor#FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param obj the object whose value type is to be returned + * @return object value type + * @hide + */ + public static int getTypeOfObject(Object obj) { + if (obj == null) { + return 0; /* Cursor.FIELD_TYPE_NULL */ + } else if (obj instanceof byte[]) { + return 4; /* Cursor.FIELD_TYPE_BLOB */ + } else if (obj instanceof Float || obj instanceof Double) { + return 2; /* Cursor.FIELD_TYPE_FLOAT */ + } else if (obj instanceof Long || obj instanceof Integer) { + return 1; /* Cursor.FIELD_TYPE_INTEGER */ + } else { + return 3; /* Cursor.FIELD_TYPE_STRING */ + } + } + /** * Appends an SQL string to the given StringBuilder, including the opening * and closing single quotes. Any single quotes internal to sqlString will @@ -294,10 +323,23 @@ public static String getCollationKey(String name) { */ public static String getHexCollationKey(String name) { byte [] arr = getCollationKeyInBytes(name); - char[] keys = Hex.encodeHex(arr); + char[] keys = encodeHex(arr, HEX_DIGITS_LOWER); return new String(keys, 0, getKeyLen(arr) * 2); } + private static final char[] HEX_DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + private static int getKeyLen(byte[] arr) { if (arr[arr.length - 1] != 0) { return arr.length; @@ -455,7 +497,7 @@ public static String dumpCurrentRowToString(Cursor cursor) { * * @param cursor The cursor to read from * @param field The TEXT field to read - * @param values The {@link ContentValues} to put the value into, with the field as the key + * @param values The ContentValues to put the value into, with the field as the key */ public static void cursorStringToContentValues(Cursor cursor, String field, ContentValues values) { @@ -480,7 +522,7 @@ public static void cursorStringToInsertHelper(Cursor cursor, String field, * * @param cursor The cursor to read from * @param field The TEXT field to read - * @param values The {@link ContentValues} to put the value into, with the field as the key + * @param values The ContentValues to put the value into, with the field as the key * @param key The key to store the value with in the map */ public static void cursorStringToContentValues(Cursor cursor, String field, @@ -493,7 +535,7 @@ public static void cursorStringToContentValues(Cursor cursor, String field, * * @param cursor The cursor to read from * @param field The INTEGER field to read - * @param values The {@link ContentValues} to put the value into, with the field as the key + * @param values The ContentValues to put the value into, with the field as the key */ public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) { cursorIntToContentValues(cursor, field, values, field); @@ -504,7 +546,7 @@ public static void cursorIntToContentValues(Cursor cursor, String field, Content * * @param cursor The cursor to read from * @param field The INTEGER field to read - * @param values The {@link ContentValues} to put the value into, with the field as the key + * @param values The ContentValues to put the value into, with the field as the key * @param key The key to store the value with in the map */ public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values, @@ -522,7 +564,7 @@ public static void cursorIntToContentValues(Cursor cursor, String field, Content * * @param cursor The cursor to read from * @param field The INTEGER field to read - * @param values The {@link ContentValues} to put the value into, with the field as the key + * @param values The ContentValues to put the value into, with the field as the key */ public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values) { @@ -534,7 +576,7 @@ public static void cursorLongToContentValues(Cursor cursor, String field, Conten * * @param cursor The cursor to read from * @param field The INTEGER field to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into * @param key The key to store the value with in the map */ public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values, @@ -553,7 +595,7 @@ public static void cursorLongToContentValues(Cursor cursor, String field, Conten * * @param cursor The cursor to read from * @param field The REAL field to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values) { @@ -565,7 +607,7 @@ public static void cursorDoubleToCursorValues(Cursor cursor, String field, Conte * * @param cursor The cursor to read from * @param field The REAL field to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into * @param key The key to store the value with in the map */ public static void cursorDoubleToContentValues(Cursor cursor, String field, @@ -582,7 +624,7 @@ public static void cursorDoubleToContentValues(Cursor cursor, String field, * Read the entire contents of a cursor row and store them in a ContentValues. * * @param cursor the cursor to read from. - * @param values the {@link ContentValues} to put the row into. + * @param values the ContentValues to put the row into. */ public static void cursorRowToContentValues(Cursor cursor, ContentValues values) { AbstractWindowedCursor awc = @@ -678,7 +720,7 @@ public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -694,7 +736,7 @@ public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentVa * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -710,7 +752,7 @@ public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValu * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -726,7 +768,7 @@ public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentVal * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -742,7 +784,7 @@ public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValue * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -758,7 +800,7 @@ public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentVal * * @param cursor The cursor to read from * @param column The column to read - * @param values The {@link ContentValues} to put the value into + * @param values The ContentValues to put the value into */ public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, String column) { @@ -1106,6 +1148,64 @@ public void close() { } } + public static void cursorFillWindow(final Cursor cursor, + int position, final android.database.CursorWindow window) { + if (position < 0 || position >= cursor.getCount()) { + return; + } + final int oldPos = cursor.getPosition(); + final int numColumns = cursor.getColumnCount(); + window.clear(); + window.setStartPosition(position); + window.setNumColumns(numColumns); + if (cursor.moveToPosition(position)) { + do { + if (!window.allocRow()) { + break; + } + for (int i = 0; i < numColumns; i++) { + final int type = cursor.getType(i); + final boolean success; + switch (type) { + case Cursor.FIELD_TYPE_NULL: + success = window.putNull(position, i); + break; + + case Cursor.FIELD_TYPE_INTEGER: + success = window.putLong(cursor.getLong(i), position, i); + break; + + case Cursor.FIELD_TYPE_FLOAT: + success = window.putDouble(cursor.getDouble(i), position, i); + break; + + case Cursor.FIELD_TYPE_BLOB: { + final byte[] value = cursor.getBlob(i); + success = value != null ? window.putBlob(value, position, i) + : window.putNull(position, i); + break; + } + + default: // assume value is convertible to String + case Cursor.FIELD_TYPE_STRING: { + final String value = cursor.getString(i); + success = value != null ? window.putString(value, position, i) + : window.putNull(position, i); + break; + } + } + if (!success) { + window.freeLastRow(); + break; + } + } + position += 1; + } while (cursor.moveToNext()); + } + cursor.moveToPosition(oldPos); + } + + /** * Creates a db and populates it with the sql statements in sqlStatements. * @@ -1119,10 +1219,10 @@ public void close() { /* static public void createDbFromSqlStatements( Context context, String dbName, int dbVersion, String sqlStatements) { - + //TODO TODO TODO what needs ot happen here SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null); - + // TODO: this is not quite safe since it assumes that all semicolons at the end of a line // terminate statements. It is possible that a text field contains ;\n. We will have to fix // this if that turns out to be a problem. diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java new file mode 100644 index 00000000..47b5f548 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultCursorWindowAllocation.java @@ -0,0 +1,21 @@ +package net.sqlcipher; + +import net.sqlcipher.CursorWindowAllocation; + +public class DefaultCursorWindowAllocation implements CursorWindowAllocation { + + private long initialAllocationSize = 1024 * 1024; + private long WindowAllocationUnbounded = 0; + + public long getInitialAllocationSize() { + return initialAllocationSize; + } + + public long getGrowthPaddingSize() { + return initialAllocationSize; + } + + public long getMaxAllocationSize() { + return WindowAllocationUnbounded; + } +} diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java new file mode 100644 index 00000000..f9de1b52 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DefaultDatabaseErrorHandler.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import java.io.File; +import java.util.List; + +import net.sqlcipher.database.SQLiteDatabase; + +import android.database.sqlite.SQLiteException; +import android.util.Log; +import android.util.Pair; + +/** + * Default class used to define the actions to take when the database corruption is reported + * by sqlite. + *

+ * If null is specified for DatabaeErrorHandler param in the above calls, then this class is used + * as the default {@link DatabaseErrorHandler}. + */ +public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler { + + private final String TAG = getClass().getSimpleName(); + + /** + * defines the default method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + public void onCorruption(SQLiteDatabase dbObj) { + // NOTE: Unlike the AOSP, this version does NOT attempt to delete any attached databases. + // TBD: Are we really certain that the attached databases would really be corrupt? + Log.e(TAG, "Corruption reported by sqlite on database, deleting: " + dbObj.getPath()); + + if (dbObj.isOpen()) { + Log.e(TAG, "Database object for corrupted database is already open, closing"); + + try { + dbObj.close(); + } catch (Exception e) { + /* ignored */ + Log.e(TAG, "Exception closing Database object for corrupted database, ignored", e); + } + } + + deleteDatabaseFile(dbObj.getPath()); + } + + private void deleteDatabaseFile(String fileName) { + if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) { + return; + } + Log.e(TAG, "deleting the database file: " + fileName); + try { + new File(fileName).delete(); + } catch (Exception e) { + /* print warning and ignore exception */ + Log.w(TAG, "delete failed: " + e.getMessage()); + } + } +} diff --git a/src/net/sqlcipher/IBulkCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java similarity index 100% rename from src/net/sqlcipher/IBulkCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java new file mode 100644 index 00000000..275b28d9 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/InvalidRowColumnException.java @@ -0,0 +1,14 @@ +package net.sqlcipher; + +/** + * An exception that indicates there was an error accessing a specific row/column. + */ +public class InvalidRowColumnException extends RuntimeException +{ + public InvalidRowColumnException() {} + + public InvalidRowColumnException(String error) + { + super(error); + } +} diff --git a/src/net/sqlcipher/MatrixCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java similarity index 98% rename from src/net/sqlcipher/MatrixCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java index 140a291e..6ca0798c 100644 --- a/src/net/sqlcipher/MatrixCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java @@ -274,6 +274,11 @@ public double getDouble(int column) { return Double.parseDouble(value.toString()); } + @Override + public int getType(int column) { + return DatabaseUtils.getTypeOfObject(get(column)); + } + @Override public boolean isNull(int column) { return get(column) == null; diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java new file mode 100644 index 00000000..a4680568 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/RowAllocationException.java @@ -0,0 +1,15 @@ +package net.sqlcipher; + +/** + * An exception that indicates there was an error attempting to allocate a row + * for the CursorWindow. + */ +public class RowAllocationException extends RuntimeException +{ + public RowAllocationException() {} + + public RowAllocationException(String error) + { + super(error); + } +} diff --git a/src/net/sqlcipher/StaleDataException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java similarity index 100% rename from src/net/sqlcipher/StaleDataException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java new file mode 100644 index 00000000..4da359ff --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/UnknownTypeException.java @@ -0,0 +1,14 @@ +package net.sqlcipher; + +/** + * An exception that indicates an unknown type was returned. + */ +public class UnknownTypeException extends RuntimeException +{ + public UnknownTypeException() {} + + public UnknownTypeException(String error) + { + super(error); + } +} diff --git a/src/net/sqlcipher/database/DatabaseObjectNotClosedException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java similarity index 100% rename from src/net/sqlcipher/database/DatabaseObjectNotClosedException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java diff --git a/src/net/sqlcipher/database/SQLiteClosable.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java similarity index 100% rename from src/net/sqlcipher/database/SQLiteClosable.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java diff --git a/src/net/sqlcipher/database/SQLiteCompiledSql.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java similarity index 88% rename from src/net/sqlcipher/database/SQLiteCompiledSql.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java index 7302311a..52787c14 100644 --- a/src/net/sqlcipher/database/SQLiteCompiledSql.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java @@ -36,7 +36,7 @@ /** * Native linkage, do not modify. This comes from the database. */ - /* package */ int nHandle = 0; + /* package */ long nHandle = 0; /** * Native linkage, do not modify. When non-0 this holds a reference to a valid @@ -44,11 +44,10 @@ * checked in this class when the database lock is held to determine if there * is a valid native-side program or not. */ - /* package */ int nStatement = 0; + /* package */ long nStatement = 0; /** the following are for debugging purposes */ private String mSqlStmt = null; - private Throwable mStackTrace = null; /** when in cache and is in use, this member is set */ private boolean mInUse = false; @@ -59,7 +58,6 @@ } mDatabase = db; mSqlStmt = sql; - mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); this.nHandle = db.mNativeHandle; compile(sql, true); } @@ -95,20 +93,15 @@ private void compile(String sql, boolean forceCompilation) { } } - /* package */ void releaseSqlStatement() { + /* package */ synchronized void releaseSqlStatement() { // Note that native_finalize() checks to make sure that nStatement is // non-null before destroying it. if (nStatement != 0) { if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")"); } - try { - mDatabase.lock(); - native_finalize(); - nStatement = 0; - } finally { - mDatabase.unlock(); - } + native_finalize(); + nStatement = 0; } } @@ -145,10 +138,6 @@ protected void finalize() throws Throwable { if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")"); } - int len = mSqlStmt.length(); - Log.w(TAG, "Releasing statement in a finalizer. Please ensure " + - "that you explicitly call close() on your cursor: " + - mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace); releaseSqlStatement(); } finally { super.finalize(); diff --git a/src/net/sqlcipher/database/SQLiteContentHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java similarity index 99% rename from src/net/sqlcipher/database/SQLiteContentHelper.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java index 09c9114e..3300b610 100644 --- a/src/net/sqlcipher/database/SQLiteContentHelper.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java @@ -61,7 +61,7 @@ public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, St fd = (android.os.ParcelFileDescriptor)m.invoke(file); } catch (Exception e) { android.util.Log.i("SQLiteContentHelper", "SQLiteCursor.java: " + e); - } + } AssetFileDescriptor afd = new AssetFileDescriptor(fd, 0, file.length()); return afd; } catch (IOException ex) { @@ -95,7 +95,7 @@ private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String } MemoryFile file = new MemoryFile(null, bytes.length); file.writeBytes(bytes, 0, 0, bytes.length); - + // file.deactivate(); return file; } finally { diff --git a/src/net/sqlcipher/database/SQLiteCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java similarity index 78% rename from src/net/sqlcipher/database/SQLiteCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java index 4f293519..a216d986 100644 --- a/src/net/sqlcipher/database/SQLiteCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java @@ -17,9 +17,10 @@ package net.sqlcipher.database; import net.sqlcipher.AbstractWindowedCursor; +import net.sqlcipher.BuildConfig; import net.sqlcipher.CursorWindow; -import net.sqlcipher.SQLException; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -27,6 +28,7 @@ import android.database.CharArrayBuffer; import android.database.DataSetObserver; +import android.database.SQLException; import android.os.Handler; import android.os.Message; import android.os.Process; @@ -63,14 +65,18 @@ public class SQLiteCursor extends AbstractWindowedCursor { /** The number of rows in the cursor */ private int mCount = NO_COUNT; + private int mCursorWindowCapacity = 0; + + private boolean fillWindowForwardOnly = false; + /** A mapping of column names to column indices, to speed up lookups */ private Map mColumnNameMap; /** Used to find out where a cursor was allocated in case it never got released. */ private Throwable mStackTrace; - - /** - * mMaxRead is the max items that each cursor window reads + + /** + * mMaxRead is the max items that each cursor window reads * default to a very high value */ private int mMaxRead = Integer.MAX_VALUE; @@ -78,13 +84,17 @@ public class SQLiteCursor extends AbstractWindowedCursor { private int mCursorState = 0; private ReentrantLock mLock = null; private boolean mPendingData = false; - + + public void setFillWindowForwardOnly(boolean value) { + fillWindowForwardOnly = value; + } + /** * support for a cursor variant that doesn't always read all results - * initialRead is the initial number of items that cursor window reads + * initialRead is the initial number of items that cursor window reads * if query contains more than this number of items, a thread will be - * created and handle the left over items so that caller can show - * results as soon as possible + * created and handle the left over items so that caller can show + * results as soon as possible * @param initialRead initial number of items that cursor read * @param maxRead leftover items read at maxRead items per time * @hide @@ -94,20 +104,20 @@ public void setLoadStyle(int initialRead, int maxRead) { mInitialRead = initialRead; mLock = new ReentrantLock(true); } - + private void queryThreadLock() { if (mLock != null) { - mLock.lock(); + mLock.lock(); } } - + private void queryThreadUnlock() { if (mLock != null) { - mLock.unlock(); + mLock.unlock(); } } - - + + /** * @hide */ @@ -123,7 +133,7 @@ private void sendMessage() { } else { mPendingData = true; } - + } public void run() { // use cached mWindow, to avoid get null mWindow @@ -131,6 +141,9 @@ public void run() { Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND); // the cursor's state doesn't change while (true) { + if(mLock == null){ + mLock = new ReentrantLock(true); + } mLock.lock(); if (mCursorState != mThreadState) { mLock.unlock(); @@ -143,7 +156,7 @@ public void run() { if (count == NO_COUNT){ mCount += mMaxRead; sendMessage(); - } else { + } else { mCount = count; sendMessage(); break; @@ -158,33 +171,41 @@ public void run() { mLock.unlock(); } } - } + } } - - + + /** * @hide - */ - protected class MainThreadNotificationHandler extends Handler { + */ + protected static class MainThreadNotificationHandler extends Handler { + + private final WeakReference wrappedCursor; + + MainThreadNotificationHandler(SQLiteCursor cursor) { + wrappedCursor = new WeakReference(cursor); + } + public void handleMessage(Message msg) { - - notifyDataSetChange(); - + SQLiteCursor cursor = wrappedCursor.get(); + if(cursor != null){ + cursor.notifyDataSetChange(); + } } } - + /** * @hide */ - protected MainThreadNotificationHandler mNotificationHandler; - + protected MainThreadNotificationHandler mNotificationHandler; + public void registerDataSetObserver(DataSetObserver observer) { super.registerDataSetObserver(observer); - if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && + if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && mNotificationHandler == null) { queryThreadLock(); try { - mNotificationHandler = new MainThreadNotificationHandler(); + mNotificationHandler = new MainThreadNotificationHandler(this); if (mPendingData) { notifyDataSetChange(); mPendingData = false; @@ -193,9 +214,9 @@ public void registerDataSetObserver(DataSetObserver observer) { queryThreadUnlock(); } } - + } - + /** * Execute a query and provide access to its result set through a Cursor * interface. For a query such as: {@code SELECT name, birth, phone FROM @@ -232,11 +253,11 @@ public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, for (int i = 0; i < columnCount; i++) { String columnName = mQuery.columnNameLocked(i); mColumns[i] = columnName; - if (Config.LOGV) { + if(BuildConfig.DEBUG){ Log.v("DatabaseWindow", "mColumns[" + i + "] is " + mColumns[i]); } - + // Make note of the row ID column index for quick access to it if ("_id".equals(columnName)) { mRowIdColumnIndex = i; @@ -273,7 +294,8 @@ public int getCount() { return mCount; } - private void fillWindow (int startPos) { + private void fillWindow (int requiredPos) { + int startPos = 0; if (mWindow == null) { // If there isn't a window set already it will only be accessed locally mWindow = new CursorWindow(true /* the window is local only */); @@ -286,14 +308,29 @@ private void fillWindow (int startPos) { queryThreadUnlock(); } } + if(fillWindowForwardOnly) { + startPos = requiredPos; + } else { + startPos = mCount == NO_COUNT + ? cursorPickFillWindowStartPosition(requiredPos, 0) + : cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity); + } mWindow.setStartPosition(startPos); + mWindow.setRequiredPosition(requiredPos); + if(BuildConfig.DEBUG){ + Log.v(TAG, String.format("Filling cursor window with start position:%d required position:%d", + startPos, requiredPos)); + } mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); + if(mCursorWindowCapacity == 0) { + mCursorWindowCapacity = mWindow.getNumRows(); + } // return -1 means not finished if (mCount == NO_COUNT){ mCount = startPos + mInitialRead; Thread t = new Thread(new QueryThread(mCursorState), "query thread"); t.start(); - } + } } @Override @@ -313,8 +350,10 @@ public int getColumnIndex(String columnName) { final int periodIndex = columnName.lastIndexOf('.'); if (periodIndex != -1) { Exception e = new Exception(); - Log.e(TAG, "requesting column name with table name -- " + columnName, e); - columnName = columnName.substring(periodIndex + 1); + if(BuildConfig.DEBUG){ + Log.e(TAG, "requesting column name with table name -- " + columnName, e); + columnName = columnName.substring(periodIndex + 1); + } } Integer i = mColumnNameMap.get(columnName); @@ -335,10 +374,12 @@ public boolean deleteRow() { // Only allow deletes if there is an ID column, and the ID has been read from it if (mRowIdColumnIndex == -1 || mCurrentRowID == null) { + if(BuildConfig.DEBUG){ Log.e(TAG, - "Could not delete row because either the row ID column is not available or it" + - "has not been read."); - return false; + "Could not delete row because either the row ID column is not available or it" + + "has not been read."); + } + return false; } boolean success; @@ -402,9 +443,11 @@ public boolean supportsUpdates() { public boolean commitUpdates(Map> additionalValues) { if (!supportsUpdates()) { + if(BuildConfig.DEBUG){ Log.e(TAG, "commitUpdates not supported on this cursor, did you " - + "include the _id column?"); - return false; + + "include the _id column?"); + } + return false; } /* @@ -487,13 +530,13 @@ public boolean commitUpdates(Map 100) ? 100 : len), mStackTrace); + } close(); SQLiteDebug.notifyActiveCursorFinalized(); } else { - if (Config.LOGV) { - Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() + - ", table = " + mEditTable + ", query = " + mQuery.mSql); + if(BuildConfig.DEBUG) { + Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() + + ", table = " + mEditTable + ", query = " + mQuery.mSql); } } } finally { @@ -606,42 +651,50 @@ protected void finalize() { } - + @Override - public void fillWindow(int startPos, android.database.CursorWindow window) { - - /* - window.setStartPosition(startPos); - mCount = mQuery.fillWindow((net.sqlcipher.database.CursorWindow)window, mInitialRead, 0); - // return -1 means not finished - if (mCount == NO_COUNT){ - mCount = startPos + mInitialRead; - Thread t = new Thread(new QueryThread(mCursorState), "query thread"); - t.start(); - } */ - - if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); - } else { - mCursorState++; - queryThreadLock(); - try { - mWindow.clear(); - } finally { - queryThreadUnlock(); - } - } - mWindow.setStartPosition(startPos); - mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); - // return -1 means not finished - if (mCount == NO_COUNT){ - mCount = startPos + mInitialRead; - Thread t = new Thread(new QueryThread(mCursorState), "query thread"); - t.start(); + public void fillWindow(int requiredPos, android.database.CursorWindow window) { + int startPos = 0; + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } else { + mCursorState++; + queryThreadLock(); + try { + mWindow.clear(); + } finally { + queryThreadUnlock(); } - - + } + if(fillWindowForwardOnly) { + startPos = requiredPos; + } else { + startPos = mCount == NO_COUNT + ? cursorPickFillWindowStartPosition(requiredPos, 0) + : cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity); + } + mWindow.setStartPosition(startPos); + mWindow.setRequiredPosition(requiredPos); + if(BuildConfig.DEBUG) { + Log.v(TAG, String.format("Filling cursor window with start position:%d required position:%d", + startPos, requiredPos)); + } + mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); + if(mCursorWindowCapacity == 0) { + mCursorWindowCapacity = mWindow.getNumRows(); + } + // return -1 means not finished + if (mCount == NO_COUNT){ + mCount = startPos + mInitialRead; + Thread t = new Thread(new QueryThread(mCursorState), "query thread"); + t.start(); + } } + public int cursorPickFillWindowStartPosition( + int cursorPosition, int cursorWindowCapacity) { + return Math.max(cursorPosition - cursorWindowCapacity / 3, 0); + } + } diff --git a/src/net/sqlcipher/database/SQLiteCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java similarity index 95% rename from src/net/sqlcipher/database/SQLiteCursorDriver.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java index 89624d84..1ea66aba 100644 --- a/src/net/sqlcipher/database/SQLiteCursorDriver.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java @@ -26,12 +26,12 @@ public interface SQLiteCursorDriver { /** * Executes the query returning a Cursor over the result set. - * + * * @param factory The CursorFactory to use when creating the Cursors, or * null if standard SQLiteCursors should be returned. * @return a Cursor over the result set */ - android.database.Cursor query(CursorFactory factory, String[] bindArgs); + Cursor query(CursorFactory factory, String[] bindArgs); /** * Called by a SQLiteCursor when it is released. @@ -40,7 +40,7 @@ public interface SQLiteCursorDriver { /** * Called by a SQLiteCursor when it is requeryed. - * + * * @return The new count value. */ void cursorRequeried(android.database.Cursor cursor); diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java new file mode 100644 index 00000000..a4419653 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java @@ -0,0 +1,3272 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.BuildConfig; +import net.sqlcipher.Cursor; +import net.sqlcipher.CrossProcessCursorWrapper; +import net.sqlcipher.DatabaseUtils; +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.DefaultDatabaseErrorHandler; +import net.sqlcipher.database.SQLiteStatement; +import net.sqlcipher.database.SQLiteDebug.DbStats; +import net.sqlcipher.database.SQLiteDatabaseHook; +import net.sqlcipher.database.SQLiteQueryStats; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; +import java.util.zip.ZipInputStream; + +import android.content.ContentValues; + +import android.content.Context; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabaseCorruptException; +import android.database.sqlite.SQLiteException; + +import android.os.CancellationSignal; +import android.os.Debug; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Config; +import android.util.Log; +import android.util.Pair; + +import java.io.UnsupportedEncodingException; +import androidx.sqlite.db.SupportSQLiteDatabase; +import androidx.sqlite.db.SupportSQLiteQuery; + +/** + * Exposes methods to manage a SQLCipher database. + *

SQLiteDatabase has methods to create, delete, execute SQL commands, and + * perform other common database management tasks. + *

A call to loadLibs(…) should occur before attempting to + * create or open a database connection. + *

Database names must be unique within an application, not across all + * applications. + * + */ +public class SQLiteDatabase extends SQLiteClosable implements + SupportSQLiteDatabase { + private static final String TAG = "Database"; + private static final int EVENT_DB_OPERATION = 52000; + private static final int EVENT_DB_CORRUPT = 75004; + private static final String KEY_ENCODING = "UTF-8"; + + private enum SQLiteDatabaseTransactionType { + Deferred, + Immediate, + Exclusive, + } + + /** + * The version number of the SQLCipher for Android Java client library. + */ + public static final String SQLCIPHER_ANDROID_VERSION = BuildConfig.VERSION_NAME; + + // Stores reference to all databases opened in the current process. + // (The referent Object is not used at this time.) + // INVARIANT: Guarded by sActiveDatabases. + private static WeakHashMap sActiveDatabases = + new WeakHashMap(); + + public int status(int operation, boolean reset){ + return native_status(operation, reset); + } + + /** + * Change the password of the open database using sqlite3_rekey(). + * + * @param password new database password + * + * @throws SQLiteException if there is an issue changing the password internally + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + */ + public void changePassword(String password) throws SQLiteException { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + if (password != null) { + byte[] keyMaterial = getBytes(password.toCharArray()); + rekey(keyMaterial); + Arrays.fill(keyMaterial, (byte) 0); + } + } + + /** + * Change the password of the open database using sqlite3_rekey(). + * + * @param password new database password (char array) + * + * @throws SQLiteException if there is an issue changing the password internally + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + */ + public void changePassword(char[] password) throws SQLiteException { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + if (password != null) { + byte[] keyMaterial = getBytes(password); + rekey(keyMaterial); + Arrays.fill(keyMaterial, (byte) 0); + } + } + + private static void loadICUData(Context context, File workingDir) { + OutputStream out = null; + ZipInputStream in = null; + File icuDir = new File(workingDir, "icu"); + File icuDataFile = new File(icuDir, "icudt46l.dat"); + try { + if(!icuDir.exists()) icuDir.mkdirs(); + if(!icuDataFile.exists()) { + in = new ZipInputStream(context.getAssets().open("icudt46l.zip")); + in.getNextEntry(); + out = new FileOutputStream(icuDataFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + } + catch (Exception ex) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error copying icu dat file", ex); + } + if(icuDataFile.exists()){ + icuDataFile.delete(); + } + throw new RuntimeException(ex); + } + finally { + try { + if(in != null){ + in.close(); + } + if(out != null){ + out.flush(); + out.close(); + } + } catch (IOException ioe){ + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error in closing streams IO streams after expanding ICU dat file", ioe); + } + throw new RuntimeException(ioe); + } + } + } + + /** + * Implement this interface to provide custom strategy for loading jni libraries. + */ + public interface LibraryLoader { + /** + * Load jni libraries by given names. + * Straightforward implementation will be calling {@link System#loadLibrary(String name)} + * for every provided library name. + * + * @param libNames library names that sqlcipher need to load + */ + void loadLibraries(String... libNames); + } + + /** + * Loads the native SQLCipher library into the application process. + */ + public static synchronized void loadLibs (Context context) { + loadLibs(context, context.getFilesDir()); + } + + /** + * Loads the native SQLCipher library into the application process. + */ + public static synchronized void loadLibs (Context context, File workingDir) { + loadLibs(context, workingDir, new LibraryLoader() { + @Override + public void loadLibraries(String... libNames) { + for (String libName : libNames) { + System.loadLibrary(libName); + } + } + }); + } + + /** + * Loads the native SQLCipher library into the application process. + */ + public static synchronized void loadLibs(Context context, LibraryLoader libraryLoader) { + loadLibs(context, context.getFilesDir(), libraryLoader); + } + + /** + * Loads the native SQLCipher library into the application process. + */ + public static synchronized void loadLibs (Context context, File workingDir, LibraryLoader libraryLoader) { + libraryLoader.loadLibraries("sqlcipher"); + + // System.loadLibrary("stlport_shared"); + // System.loadLibrary("sqlcipher_android"); + // System.loadLibrary("database_sqlcipher"); + + // boolean systemICUFileExists = new File("/system/usr/icu/icudt46l.dat").exists(); + + // String icuRootPath = systemICUFileExists ? "/system/usr" : workingDir.getAbsolutePath(); + // setICURoot(icuRootPath); + // if(!systemICUFileExists){ + // loadICUData(context, workingDir); + // } + } + + /** + * Algorithms used in ON CONFLICT clause + * http://www.sqlite.org/lang_conflict.html + */ + /** + * When a constraint violation occurs, an immediate ROLLBACK occurs, + * thus ending the current transaction, and the command aborts with a + * return code of SQLITE_CONSTRAINT. If no transaction is active + * (other than the implied transaction that is created on every command) + * then this algorithm works the same as ABORT. + */ + public static final int CONFLICT_ROLLBACK = 1; + + /** + * When a constraint violation occurs,no ROLLBACK is executed + * so changes from prior commands within the same transaction + * are preserved. This is the default behavior. + */ + public static final int CONFLICT_ABORT = 2; + + /** + * When a constraint violation occurs, the command aborts with a return + * code SQLITE_CONSTRAINT. But any changes to the database that + * the command made prior to encountering the constraint violation + * are preserved and are not backed out. + */ + public static final int CONFLICT_FAIL = 3; + + /** + * When a constraint violation occurs, the one row that contains + * the constraint violation is not inserted or changed. + * But the command continues executing normally. Other rows before and + * after the row that contained the constraint violation continue to be + * inserted or updated normally. No error is returned. + */ + public static final int CONFLICT_IGNORE = 4; + + /** + * When a UNIQUE constraint violation occurs, the pre-existing rows that + * are causing the constraint violation are removed prior to inserting + * or updating the current row. Thus the insert or update always occurs. + * The command continues executing normally. No error is returned. + * If a NOT NULL constraint violation occurs, the NULL value is replaced + * by the default value for that column. If the column has no default + * value, then the ABORT algorithm is used. If a CHECK constraint + * violation occurs then the IGNORE algorithm is used. When this conflict + * resolution strategy deletes rows in order to satisfy a constraint, + * it does not invoke delete triggers on those rows. + * This behavior might change in a future release. + */ + public static final int CONFLICT_REPLACE = 5; + + /** + * use the following when no conflict action is specified. + */ + public static final int CONFLICT_NONE = 0; + private static final String[] CONFLICT_VALUES = new String[] + {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + + /** + * Maximum Length Of A LIKE Or GLOB Pattern + * The pattern matching algorithm used in the default LIKE and GLOB implementation + * of SQLite can exhibit O(N^2) performance (where N is the number of characters in + * the pattern) for certain pathological cases. To avoid denial-of-service attacks + * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes. + * The default value of this limit is 50000. A modern workstation can evaluate + * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly. + * The denial of service problem only comes into play when the pattern length gets + * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns + * are at most a few dozen bytes in length, paranoid application developers may + * want to reduce this parameter to something in the range of a few hundred + * if they know that external users are able to generate arbitrary patterns. + */ + public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; + + /** + * Flag for {@link #openDatabase} to open the database for reading and writing. + * If the disk is full, this may fail even before you actually write anything. + * + * {@more} Note that the value of this flag is 0, so it is the default. + */ + public static final int OPEN_READWRITE = 0x00000000; // update native code if changing + + /** + * Flag for {@link #openDatabase} to open the database for reading only. + * This is the only reliable way to open a database if the disk may be full. + */ + public static final int OPEN_READONLY = 0x00000001; // update native code if changing + + private static final int OPEN_READ_MASK = 0x00000001; // update native code if changing + + /** + * Flag for {@link #openDatabase} to open the database without support for localized collators. + * + * {@more} This causes the collator LOCALIZED not to be created. + * You must be consistent when using this flag to use the setting the database was + * created with. If this is set, {@link #setLocale} will do nothing. + */ + public static final int NO_LOCALIZED_COLLATORS = 0x00000010; // update native code if changing + + /** + * Flag for {@link #openDatabase} to create the database file if it does not already exist. + */ + public static final int CREATE_IF_NECESSARY = 0x10000000; // update native code if changing + + /** + * SQLite memory database name + */ + public static final String MEMORY = ":memory:"; + + /** + * Indicates whether the most-recently started transaction has been marked as successful. + */ + private boolean mInnerTransactionIsSuccessful; + + /** + * Valid during the life of a transaction, and indicates whether the entire transaction (the + * outer one and all of the inner ones) so far has been successful. + */ + private boolean mTransactionIsSuccessful; + + /** + * Valid during the life of a transaction. + */ + private SQLiteTransactionListener mTransactionListener; + + /** Synchronize on this when accessing the database */ + private final ReentrantLock mLock = new ReentrantLock(true); + + private long mLockAcquiredWallTime = 0L; + private long mLockAcquiredThreadTime = 0L; + + // limit the frequency of complaints about each database to one within 20 sec + // unless run command adb shell setprop log.tag.Database VERBOSE + private static final int LOCK_WARNING_WINDOW_IN_MS = 20000; + /** If the lock is held this long then a warning will be printed when it is released. */ + private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300; + private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100; + private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000; + + private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000; + + // The pattern we remove from database filenames before + // potentially logging them. + private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); + + private long mLastLockMessageTime = 0L; + + // Things related to query logging/sampling for debugging + // slow/frequent queries during development. Always log queries + // which take (by default) 500ms+; shorter queries are sampled + // accordingly. Commit statements, which are typically slow, are + // logged together with the most recently executed SQL statement, + // for disambiguation. The 500ms value is configurable via a + // SystemProperty, but developers actively debugging database I/O + // should probably use the regular log tunable, + // LOG_SLOW_QUERIES_PROPERTY, defined below. + private static int sQueryLogTimeInMillis = 0; // lazily initialized + private static final int QUERY_LOG_SQL_LENGTH = 64; + private static final String COMMIT_SQL = "COMMIT;"; + private String mLastSqlStatement = null; + + // String prefix for slow database query EventLog records that show + // lock acquistions of the database. + /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:"; + + /** Used by native code, do not rename */ + /* package */ long mNativeHandle = 0; + + /** Used to make temp table names unique */ + /* package */ int mTempTableSequence = 0; + + /** The path for the database file */ + private String mPath; + + /** The anonymized path for the database file for logging purposes */ + private String mPathForLogs = null; // lazily populated + + /** The flags passed to open/create */ + private int mFlags; + + /** The optional factory to use when creating new Cursors */ + private CursorFactory mFactory; + + private WeakHashMap mPrograms; + + /** + * for each instance of this class, a cache is maintained to store + * the compiled query statement ids returned by sqlite database. + * key = sql statement with "?" for bind args + * value = {@link SQLiteCompiledSql} + * If an application opens the database and keeps it open during its entire life, then + * there will not be an overhead of compilation of sql statements by sqlite. + * + * why is this cache NOT static? because sqlite attaches compiledsql statements to the + * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is + * invoked. + * + * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method + * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because + * most of the apps don't use "?" syntax in their sql, caching is not useful for them. + */ + /* package */ Map mCompiledQueries = new HashMap(); + /** + * @hide + */ + public static final int MAX_SQL_CACHE_SIZE = 250; + private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance + private int mCacheFullWarnings; + private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1; + + /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors + * Corruption + * */ + private final DatabaseErrorHandler mErrorHandler; + + /** maintain stats about number of cache hits and misses */ + private int mNumCacheHits; + private int mNumCacheMisses; + + /** the following 2 members maintain the time when a database is opened and closed */ + private String mTimeOpened = null; + private String mTimeClosed = null; + + /** Used to find out where this object was created in case it never got closed. */ + private Throwable mStackTrace = null; + + // System property that enables logging of slow queries. Specify the threshold in ms. + private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold"; + private final int mSlowQueryThreshold; + + /** + * @param closable + */ + void addSQLiteClosable(SQLiteClosable closable) { + lock(); + try { + mPrograms.put(closable, null); + } finally { + unlock(); + } + } + + void removeSQLiteClosable(SQLiteClosable closable) { + lock(); + try { + mPrograms.remove(closable); + } finally { + unlock(); + } + } + + @Override + protected void onAllReferencesReleased() { + if (isOpen()) { + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } + dbclose(); + + synchronized (sActiveDatabases) { + sActiveDatabases.remove(this); + } + } + } + + /** + * Attempts to release memory that SQLite holds but does not require to + * operate properly. Typically this memory will come from the page cache. + * + * @return the number of bytes actually released + */ + static public native int releaseMemory(); + + /** + * Control whether or not the SQLiteDatabase is made thread-safe by using locks + * around critical sections. This is pretty expensive, so if you know that your + * DB will only be used by a single thread then you should set this to false. + * The default is true. + * @param lockingEnabled set to true to enable locks, false otherwise + */ + public void setLockingEnabled(boolean lockingEnabled) { + mLockingEnabled = lockingEnabled; + } + + /** + * If set then the SQLiteDatabase is made thread-safe by using locks + * around critical sections + */ + private boolean mLockingEnabled = true; + + /* package */ + void onCorruption() { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Calling error handler for corrupt database (detected) " + mPath); + } + + // NOTE: DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database + mErrorHandler.onCorruption(this); + } + + /** + * Locks the database for exclusive access. The database lock must be held when + * touch the native sqlite3* object since it is single threaded and uses + * a polling lock contention algorithm. The lock is recursive, and may be acquired + * multiple times by the same thread. This is a no-op if mLockingEnabled is false. + * + * @see #unlock() + */ + /* package */ void lock() { + if (!mLockingEnabled) return; + mLock.lock(); + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + // Use elapsed real-time since the CPU may sleep when waiting for IO + mLockAcquiredWallTime = SystemClock.elapsedRealtime(); + mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); + } + } + } + + /** + * Locks the database for exclusive access. The database lock must be held when + * touch the native sqlite3* object since it is single threaded and uses + * a polling lock contention algorithm. The lock is recursive, and may be acquired + * multiple times by the same thread. + * + * @see #unlockForced() + */ + private void lockForced() { + mLock.lock(); + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + // Use elapsed real-time since the CPU may sleep when waiting for IO + mLockAcquiredWallTime = SystemClock.elapsedRealtime(); + mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); + } + } + } + + /** + * Releases the database lock. This is a no-op if mLockingEnabled is false. + * + * @see #unlock() + */ + /* package */ void unlock() { + if (!mLockingEnabled) return; + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + checkLockHoldTime(); + } + } + mLock.unlock(); + } + + /** + * Releases the database lock. + * + * @see #unlockForced() + */ + private void unlockForced() { + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { + if (mLock.getHoldCount() == 1) { + checkLockHoldTime(); + } + } + mLock.unlock(); + } + + private void checkLockHoldTime() { + // Use elapsed real-time since the CPU may sleep when waiting for IO + long elapsedTime = SystemClock.elapsedRealtime(); + long lockedTime = elapsedTime - mLockAcquiredWallTime; + if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT && + !Log.isLoggable(TAG, Log.VERBOSE) && + (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) { + return; + } + if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) { + int threadTime = (int) + ((Debug.threadCpuTimeNanos() - mLockAcquiredThreadTime) / 1000000); + if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS || + lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) { + mLastLockMessageTime = elapsedTime; + String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was " + + threadTime + "ms"; + if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) { + if(BuildConfig.DEBUG){ + Log.d(TAG, msg, new Exception()); + } + } else { + if(BuildConfig.DEBUG){ + Log.d(TAG, msg); + } + } + } + } + } + + /** + * Performs a PRAGMA integrity_check; command against the database. + * @return true if the integrity check is ok, otherwise false + */ + public boolean isDatabaseIntegrityOk() { + Pair result = getResultFromPragma("PRAGMA integrity_check;"); + return result.first ? result.second.equals("ok") : result.first; + } + + /** + * Returns a list of attached databases including the main database + * by executing PRAGMA database_list + * @return a list of pairs of database name and filename + */ + public List> getAttachedDbs() { + return getAttachedDbs(this); + } + + /** + * Sets the journal mode of the database to WAL + * @return true if successful, false otherwise + */ + public boolean enableWriteAheadLogging() { + if(inTransaction()) { + String message = "Write Ahead Logging cannot be enabled while in a transaction"; + throw new IllegalStateException(message); + } + List> attachedDbs = getAttachedDbs(this); + if(attachedDbs != null && attachedDbs.size() > 1) return false; + if(isReadOnly() || getPath().equals(MEMORY)) return false; + String command = "PRAGMA journal_mode = WAL;"; + rawExecSQL(command); + return true; + } + + /** + * Sets the journal mode of the database to DELETE (the default mode) + */ + public void disableWriteAheadLogging() { + if(inTransaction()) { + String message = "Write Ahead Logging cannot be disabled while in a transaction"; + throw new IllegalStateException(message); + } + String command = "PRAGMA journal_mode = DELETE;"; + rawExecSQL(command); + } + + /** + * @return true if the journal mode is set to WAL, otherwise false + */ + public boolean isWriteAheadLoggingEnabled() { + Pair result = getResultFromPragma("PRAGMA journal_mode;"); + return result.first ? result.second.equals("wal") : result.first; + } + + /** + * Enables or disables foreign key constraints + * @param enable used to determine whether or not foreign key constraints are on + */ + public void setForeignKeyConstraintsEnabled(boolean enable) { + if(inTransaction()) { + String message = "Foreign key constraints may not be changed while in a transaction"; + throw new IllegalStateException(message); + } + String command = String.format("PRAGMA foreign_keys = %s;", + enable ? "ON" : "OFF"); + execSQL(command); + } + + /** + * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of + * the work done in that transaction and all of the nested transactions will be committed or + * rolled back. The changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. + * + *

Here is the standard idiom for transactions: + * + *

+   *   db.beginTransaction();
+   *   try {
+   *     ...
+   *     db.setTransactionSuccessful();
+   *   } finally {
+   *     db.endTransaction();
+   *   }
+   * 
+ * + * @throws IllegalStateException if the database is not open + */ + public void beginTransaction() { + beginTransactionWithListener((SQLiteTransactionListener)null /* transactionStatusCallback */); + } + + /** + * Begins a transaction in Exlcusive mode. Transactions can be nested. When + * the outer transaction is ended all of the work done in that transaction + * and all of the nested transactions will be committed or rolled back. The + * changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they + * will be committed. + * + *

Here is the standard idiom for transactions: + * + *

+   *   db.beginTransactionWithListener(listener);
+   *   try {
+   *     ...
+   *     db.setTransactionSuccessful();
+   *   } finally {
+   *     db.endTransaction();
+   *   }
+   * 
+ * @param transactionListener listener that should be notified when the transaction begins, + * commits, or is rolled back, either explicitly or by a call to + * {@link #yieldIfContendedSafely}. + * + * @throws IllegalStateException if the database is not open + */ + public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { + beginTransactionWithListenerInternal(transactionListener, + SQLiteDatabaseTransactionType.Exclusive); + } + + /** + * Begins a transaction in Immediate mode + */ + public void beginTransactionNonExclusive() { + beginTransactionWithListenerInternal(null, + SQLiteDatabaseTransactionType.Immediate); + } + + /** + * Begins a transaction in Immediate mode + * @param transactionListener is the listener used to report transaction events + */ + public void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener) { + beginTransactionWithListenerInternal(transactionListener, + SQLiteDatabaseTransactionType.Immediate); + } + + /** + * End a transaction. See beginTransaction for notes about how to use this and when transactions + * are committed and rolled back. + * + * @throws IllegalStateException if the database is not open or is not locked by the current thread + */ + public void endTransaction() { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("no transaction pending"); + } + try { + if (mInnerTransactionIsSuccessful) { + mInnerTransactionIsSuccessful = false; + } else { + mTransactionIsSuccessful = false; + } + if (mLock.getHoldCount() != 1) { + return; + } + RuntimeException savedException = null; + if (mTransactionListener != null) { + try { + if (mTransactionIsSuccessful) { + mTransactionListener.onCommit(); + } else { + mTransactionListener.onRollback(); + } + } catch (RuntimeException e) { + savedException = e; + mTransactionIsSuccessful = false; + } + } + if (mTransactionIsSuccessful) { + execSQL(COMMIT_SQL); + } else { + try { + execSQL("ROLLBACK;"); + if (savedException != null) { + throw savedException; + } + } catch (SQLException e) { + if(BuildConfig.DEBUG){ + Log.d(TAG, "exception during rollback, maybe the DB previously " + + "performed an auto-rollback"); + } + } + } + } finally { + mTransactionListener = null; + unlockForced(); + if(BuildConfig.DEBUG){ + Log.v(TAG, "unlocked " + Thread.currentThread() + + ", holdCount is " + mLock.getHoldCount()); + } + } + } + + /** + * Marks the current transaction as successful. Do not do any more database work between + * calling this and calling endTransaction. Do as little non-database work as possible in that + * situation too. If any errors are encountered between this and endTransaction the transaction + * will still be committed. + * + * @throws IllegalStateException if the database is not open, the current thread is not in a transaction, + * or the transaction is already marked as successful. + */ + public void setTransactionSuccessful() { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("no transaction pending"); + } + if (mInnerTransactionIsSuccessful) { + throw new IllegalStateException( + "setTransactionSuccessful may only be called once per call to beginTransaction"); + } + mInnerTransactionIsSuccessful = true; + } + + /** + * return true if there is a transaction pending + */ + public boolean inTransaction() { + return mLock.getHoldCount() > 0; + } + + /** + * Checks if the database lock is held by this thread. + * + * @return true, if this thread is holding the database lock. + */ + public boolean isDbLockedByCurrentThread() { + return mLock.isHeldByCurrentThread(); + } + + /** + * Checks if the database is locked by another thread. This is + * just an estimate, since this status can change at any time, + * including after the call is made but before the result has + * been acted upon. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + */ + public boolean isDbLockedByOtherThreads() { + return !mLock.isHeldByCurrentThread() && mLock.isLocked(); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. + * + * @return true if the transaction was yielded + * + * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock + * will not be yielded. Use yieldIfContendedSafely instead. + */ + @Deprecated + public boolean yieldIfContended() { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(false /* do not check yielding */, + -1 /* sleepAfterYieldDelay */); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + */ + public boolean yieldIfContendedSafely() { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(true /* check yielding */, -1 /* sleepAfterYieldDelay*/); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * throw an exception if that is not the case. + * + * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if + * the lock was actually yielded. This will allow other background threads to make some + * more progress than they would if we started the transaction immediately. + * + * @return true if the transaction was yielded, false if queue was empty or database was not open + * + * @throws IllegalStateException if the database is locked more than once by the current thread + * @throws InterruptedException if the thread was interrupted while sleeping + */ + public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { + /* safeguard: */ + if (!isOpen()) return false; + + return yieldIfContendedHelper(true /* check yielding */, sleepAfterYieldDelay); + } + + private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) { + if (mLock.getQueueLength() == 0) { + // Reset the lock acquire time since we know that the thread was willing to yield + // the lock at this time. + mLockAcquiredWallTime = SystemClock.elapsedRealtime(); + mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); + return false; + } + setTransactionSuccessful(); + SQLiteTransactionListener transactionListener = mTransactionListener; + endTransaction(); + if (checkFullyYielded) { + if (this.isDbLockedByCurrentThread()) { + throw new IllegalStateException( + "Db locked more than once. yielfIfContended cannot yield"); + } + } + if (sleepAfterYieldDelay > 0) { + // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to + // check if anyone is using the database. If the database is not contended, + // retake the lock and return. + long remainingDelay = sleepAfterYieldDelay; + while (remainingDelay > 0) { + try { + Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ? + remainingDelay : SLEEP_AFTER_YIELD_QUANTUM); + } catch (InterruptedException e) { + Thread.interrupted(); + } + remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM; + if (mLock.getQueueLength() == 0) { + break; + } + } + } + beginTransactionWithListener(transactionListener); + return true; + } + + /** Maps table names to info about what to which _sync_time column to set + * to NULL on an update. This is used to support syncing. */ + private final Map mSyncUpdateInfo = + new HashMap(); + + public Map getSyncedTables() { + synchronized(mSyncUpdateInfo) { + HashMap tables = new HashMap(); + for (String table : mSyncUpdateInfo.keySet()) { + SyncUpdateInfo info = mSyncUpdateInfo.get(table); + if (info.deletedTable != null) { + tables.put(table, info.deletedTable); + } + } + return tables; + } + } + + /** + * Internal class used to keep track what needs to be marked as changed + * when an update occurs. This is used for syncing, so the sync engine + * knows what data has been updated locally. + */ + static private class SyncUpdateInfo { + /** + * Creates the SyncUpdateInfo class. + * + * @param masterTable The table to set _sync_time to NULL in + * @param deletedTable The deleted table that corresponds to the + * master table + * @param foreignKey The key that refers to the primary key in table + */ + SyncUpdateInfo(String masterTable, String deletedTable, + String foreignKey) { + this.masterTable = masterTable; + this.deletedTable = deletedTable; + this.foreignKey = foreignKey; + } + + /** The table containing the _sync_time column */ + String masterTable; + + /** The deleted table that corresponds to the master table */ + String deletedTable; + + /** The key in the local table the row in table. It may be _id, if table + * is the local table. */ + String foreignKey; + } + + /** + * Used to allow returning sub-classes of {@link Cursor} when calling query. + */ + public interface CursorFactory { + /** + * See + * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver, + * String, SQLiteQuery)}. + */ + public Cursor newCursor(SQLiteDatabase db, + SQLiteCursorDriver masterQuery, String editTable, + SQLiteQuery query); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) { + return openDatabase(path, password, factory, flags, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags) { + return openDatabase(path, password, factory, flags, null, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) { + return openDatabase(path, password, factory, flags, hook, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) { + return openDatabase(path, password, factory, flags, hook, null); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + return openDatabase(path, password == null ? null : password.toCharArray(), factory, flags, hook, errorHandler); + } + +/** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (char array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + byte[] keyMaterial = getBytes(password); + return openDatabase(path, keyMaterial, factory, flags, hook, errorHandler); + } + + /** + * Open the database according to the flags {@link #OPEN_READWRITE} + * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS} + * with optional hook to run on pre/post key events. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path to database file to open and/or create + * @param password to use to open and/or create database file (byte array) + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called, or null for default + * @param flags to control database access mode and other options + * @param hook to run on pre/post key events (may be null) + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @return the newly opened database + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public static SQLiteDatabase openDatabase(String path, byte[] password, CursorFactory factory, int flags, + SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + SQLiteDatabase sqliteDatabase = null; + DatabaseErrorHandler myErrorHandler = (errorHandler != null) ? errorHandler : new DefaultDatabaseErrorHandler(); + + try { + // Open the database. + sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler); + sqliteDatabase.openDatabaseInternal(password, hook); + } catch (SQLiteDatabaseCorruptException e) { + // Try to recover from this, if possible. + // FUTURE TBD: should we consider this for other open failures? + + if(BuildConfig.DEBUG){ + Log.e(TAG, "Calling error handler for corrupt database " + path, e); + } + + // NOTE: if this errorHandler.onCorruption() throws the exception _should_ + // bubble back to the original caller. + // DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database + myErrorHandler.onCorruption(sqliteDatabase); + + // try *once* again: + sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler); + sqliteDatabase.openDatabaseInternal(password, hook); + } + + if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { + sqliteDatabase.enableSqlTracing(path); + } + if (SQLiteDebug.DEBUG_SQL_TIME) { + sqliteDatabase.enableSqlProfiling(path); + } + + synchronized (sActiveDatabases) { + sActiveDatabases.put(sqliteDatabase, null); + } + + return sqliteDatabase; + } + + /** + * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY, databaseHook). + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openOrCreateDatabase(file, password, factory, databaseHook, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook). + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openOrCreateDatabase(file == null ? null : file.getPath(), password, factory, databaseHook, errorHandler); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, null); + } + + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } + + /** + * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) { + return openOrCreateDatabase(file, password, factory, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); + } + + /** + * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null, null); + } + + /** + * Create a memory backed SQLite database. Its contents will be destroyed + * when the database is closed. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called + * @param password to use to open and/or create database file + * + * @return a SQLiteDatabase object, or null if the database can't be created + * + * @throws SQLiteException if the database cannot be opened + */ + public static SQLiteDatabase create(CursorFactory factory, String password) { + // This is a magic string with special meaning for SQLite. + return openDatabase(MEMORY, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY); + } + + /** + * Create a memory backed SQLite database. Its contents will be destroyed + * when the database is closed. + * + *

Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param factory an optional factory class that is called to instantiate a + * cursor when query is called + * @param password to use to open and/or create database file (char array) + * + * @return a SQLiteDatabase object, or null if the database can't be created + * + * @throws SQLiteException if the database cannot be opened + */ + public static SQLiteDatabase create(CursorFactory factory, char[] password) { + return openDatabase(MEMORY, password, factory, CREATE_IF_NECESSARY); + } + + + /** + * Close the database. + */ + public void close() { + + if (!isOpen()) { + return; // already closed + } + lock(); + try { + closeClosable(); + // close this database instance - regardless of its reference count value + onAllReferencesReleased(); + } finally { + unlock(); + } + } + + private void closeClosable() { + /* deallocate all compiled sql statement objects from mCompiledQueries cache. + * this should be done before de-referencing all {@link SQLiteClosable} objects + * from this database object because calling + * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database + * to be closed. sqlite doesn't let a database close if there are + * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries. + */ + deallocCachedSqlStatements(); + + Iterator> iter = mPrograms.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + SQLiteClosable program = entry.getKey(); + if (program != null) { + program.onAllReferencesReleasedFromContainer(); + } + } + } + + /** + * Native call to close the database. + */ + private native void dbclose(); + + /** + * Gets the database version. + * + * @return the database version + * + * @throws IllegalStateException if the database is not open + */ + public int getVersion() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, "PRAGMA user_version;"); + long version = prog.simpleQueryForLong(); + return (int) version; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the database version. + * + * @param version the new database version + * + * @throws SQLiteException if there is an issue executing the sql internally + * @throws IllegalStateException if the database is not open + */ + public void setVersion(int version) { + execSQL("PRAGMA user_version = " + version); + } + + /** + * Returns the maximum size the database may grow to. + * + * @return the new maximum database size + */ + public long getMaximumSize() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, + "PRAGMA max_page_count;"); + long pageCount = prog.simpleQueryForLong(); + return pageCount * getPageSize(); + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the maximum size the database will grow to. The maximum size cannot + * be set below the current size. + * + * @param numBytes the maximum database size, in bytes + * @return the new maximum database size + */ + public long setMaximumSize(long numBytes) { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long pageSize = getPageSize(); + long numPages = numBytes / pageSize; + // If numBytes isn't a multiple of pageSize, bump up a page + if ((numBytes % pageSize) != 0) { + numPages++; + } + prog = new SQLiteStatement(this, + "PRAGMA max_page_count = " + numPages); + long newPageCount = prog.simpleQueryForLong(); + return newPageCount * pageSize; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Returns the current database page size, in bytes. + * + * @return the database page size, in bytes + */ + public long getPageSize() { + SQLiteStatement prog = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + prog = new SQLiteStatement(this, + "PRAGMA page_size;"); + long size = prog.simpleQueryForLong(); + return size; + } finally { + if (prog != null) prog.close(); + unlock(); + } + } + + /** + * Sets the database page size. The page size must be a power of two. This + * method does not work if any data has been written to the database file, + * and must be called right after the database has been created. + * + * @param numBytes the database page size, in bytes + */ + public void setPageSize(long numBytes) { + execSQL("PRAGMA page_size = " + numBytes); + } + + /** + * Mark this table as syncable. When an update occurs in this table the +* _sync_dirty field will be set to ensure proper syncing operation. + * + * @param table the table to mark as syncable + * @param deletedTable The deleted table that corresponds to the + * syncable table + * + * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable + * OR if the database is not open + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + * + * NOTE: This method was deprecated by the AOSP in Android API 11. + */ + public void markTableSyncable(String table, String deletedTable) { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + + markTableSyncable(table, "_id", table, deletedTable); + } + + /** + * Mark this table as syncable, with the _sync_dirty residing in another + * table. When an update occurs in this table the _sync_dirty field of the + * row in updateTable with the _id in foreignKey will be set to + * ensure proper syncing operation. + * + * @param table an update on this table will trigger a sync time removal + * @param foreignKey this is the column in table whose value is an _id in + * updateTable + * @param updateTable this is the table that will have its _sync_dirty + * + * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable + * + * FUTURE @todo throw IllegalStateException if the database is not open and + * update the test suite + * + * NOTE: This method was deprecated by the AOSP in Android API 11. + */ + public void markTableSyncable(String table, String foreignKey, + String updateTable) { + /* safeguard: */ + if (!isOpen()) { + throw new SQLiteException("database not open"); + } + + markTableSyncable(table, foreignKey, updateTable, null); + } + + /** + * Mark this table as syncable, with the _sync_dirty residing in another + * table. When an update occurs in this table the _sync_dirty field of the + * row in updateTable with the _id in foreignKey will be set to + * ensure proper syncing operation. + * + * @param table an update on this table will trigger a sync time removal + * @param foreignKey this is the column in table whose value is an _id in + * updateTable + * @param updateTable this is the table that will have its _sync_dirty + * @param deletedTable The deleted table that corresponds to the + * updateTable + * + * @throws SQLiteException if there is an issue executing the sql + */ + private void markTableSyncable(String table, String foreignKey, + String updateTable, String deletedTable) { + lock(); + try { + native_execSQL("SELECT _sync_dirty FROM " + updateTable + + " LIMIT 0"); + native_execSQL("SELECT " + foreignKey + " FROM " + table + + " LIMIT 0"); + } finally { + unlock(); + } + + SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable, + foreignKey); + synchronized (mSyncUpdateInfo) { + mSyncUpdateInfo.put(table, info); + } + } + + /** + * Call for each row that is updated in a cursor. + * + * @param table the table the row is in + * @param rowId the row ID of the updated row + */ + /* package */ void rowUpdated(String table, long rowId) { + SyncUpdateInfo info; + synchronized (mSyncUpdateInfo) { + info = mSyncUpdateInfo.get(table); + } + if (info != null) { + execSQL("UPDATE " + info.masterTable + + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey + + " FROM " + table + " WHERE _id=" + rowId + ")"); + } + } + + /** + * Finds the name of the first table, which is editable. + * + * @param tables a list of tables + * @return the first table listed + */ + public static String findEditTable(String tables) { + if (!TextUtils.isEmpty(tables)) { + // find the first word terminated by either a space or a comma + int spacepos = tables.indexOf(' '); + int commapos = tables.indexOf(','); + + if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { + return tables.substring(0, spacepos); + } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { + return tables.substring(0, commapos); + } + return tables; + } else { + throw new IllegalStateException("Invalid tables"); + } + } + + /** + * Compiles an SQL statement into a reusable pre-compiled statement object. + * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the + * statement and fill in those values with {@link SQLiteProgram#bindString} + * and {@link SQLiteProgram#bindLong} each time you want to run the + * statement. Statements may not return result sets larger than 1x1. + * + * @param sql The raw SQL statement, may contain ? for unknown values to be + * bound later. + * + * @return A pre-compiled {@link SQLiteStatement} object. Note that + * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public SQLiteStatement compileStatement(String sql) throws SQLException { + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + return new SQLiteStatement(this, sql); + } finally { + unlock(); + } + } + + /** + * Query the given URL, returning a {@link Cursor} over the result set. + * + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see Cursor + */ + public Cursor query(boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit); + } + + /** + * Query the given URL, returning a {@link Cursor} over the result set. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @see Cursor + */ + public Cursor queryWithFactory(CursorFactory cursorFactory, + boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + String sql = SQLiteQueryBuilder.buildQueryString( + distinct, table, columns, selection, groupBy, having, orderBy, limit); + + return rawQueryWithFactory( + cursorFactory, sql, selectionArgs, findEditTable(table)); + } + + /** + * Query the given table, returning a {@link Cursor} over the result set. + * + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see Cursor + */ + public Cursor query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, null /* limit */); + } + + /** + * Query the given table, returning a {@link Cursor} over the result set. + * + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + * + * @see Cursor + */ + public Cursor query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy, String limit) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, limit); + } + + /** + * Runs the provided SQL and returns a {@link Cursor} over the result set. + * + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public Cursor rawQuery(String sql, String[] selectionArgs) { + return rawQueryWithFactory(null, sql, selectionArgs, null); + } + + /** + * Determines the total size in bytes of the query results, and the largest + * single row in bytes for the query. + * + * @param sql the SQL query. The SQL string must a SELECT statement + * @param args the argments to bind to the query + * + * @return A {@link SQLiteQueryStats} based the provided SQL query. + */ + public SQLiteQueryStats getQueryStats(String sql, Object[] args){ + long totalPayload = 0L; + long largestIndividualPayload = 0L; + try { + String query = String.format("CREATE TABLE tempstat AS %s", sql); + execSQL(query, args); + Cursor cursor = rawQuery("SELECT sum(payload) FROM dbstat WHERE name = 'tempstat';", new Object[]{}); + if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + cursor.moveToFirst(); + totalPayload = cursor.getLong(0); + cursor.close(); + cursor = rawQuery("SELECT max(mx_payload) FROM dbstat WHERE name = 'tempstat';", new Object[]{}); + if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + cursor.moveToFirst(); + largestIndividualPayload = cursor.getLong(0); + cursor.close(); + execSQL("DROP TABLE tempstat;"); + } catch(SQLiteException ex) { + execSQL("DROP TABLE IF EXISTS tempstat;"); + throw ex; + } + return new SQLiteQueryStats(totalPayload, largestIndividualPayload); + } + + /** + * Runs the provided SQL and returns a {@link Cursor} over the result set. + * + * @param sql the SQL query. The SQL string must not be ; terminated + * @param args You may include ?s in where clause in the query, + * which will be replaced by the values from args. The + * values will be bound by their type. + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public Cursor rawQuery(String sql, Object[] args) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long timeStart = 0; + if (Config.LOGV || mSlowQueryThreshold != -1) { + timeStart = System.currentTimeMillis(); + } + SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null); + Cursor cursor = null; + try { + cursor = driver.query(mFactory, args); + } finally { + if (Config.LOGV || mSlowQueryThreshold != -1) { + // Force query execution + int count = -1; + if (cursor != null) { + count = cursor.getCount(); + } + + long duration = System.currentTimeMillis() - timeStart; + + if (BuildConfig.DEBUG || duration >= mSlowQueryThreshold) { + Log.v(TAG, + "query (" + duration + " ms): " + driver.toString() + + ", args are , count is " + count); + } + } + } + return new CrossProcessCursorWrapper(cursor); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param editTable the name of the first table, which is editable + * + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid + * @throws IllegalStateException if the database is not open + */ + public Cursor rawQueryWithFactory( + CursorFactory cursorFactory, String sql, String[] selectionArgs, + String editTable) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + long timeStart = 0; + + if (Config.LOGV || mSlowQueryThreshold != -1) { + timeStart = System.currentTimeMillis(); + } + + SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable); + + Cursor cursor = null; + try { + cursor = driver.query( + cursorFactory != null ? cursorFactory : mFactory, + selectionArgs); + } finally { + if (Config.LOGV || mSlowQueryThreshold != -1) { + + // Force query execution + int count = -1; + if (cursor != null) { + count = cursor.getCount(); + } + + long duration = System.currentTimeMillis() - timeStart; + + if (BuildConfig.DEBUG || duration >= mSlowQueryThreshold) { + Log.v(TAG, + "query (" + duration + " ms): " + driver.toString() + + ", args are , count is " + count); + } + } + } + return new CrossProcessCursorWrapper(cursor); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * The cursor will read an initial set of rows and the return to the caller. + * It will continue to read in batches and send data changed notifications + * when the later batches are ready. + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param initialRead set the initial count of items to read from the cursor + * @param maxRead set the count of items to read on each iteration after the first + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * + * This work is incomplete and not fully tested or reviewed, so currently + * hidden. + * @hide + */ + public Cursor rawQuery(String sql, String[] selectionArgs, + int initialRead, int maxRead) { + net.sqlcipher.CursorWrapper cursorWrapper = (net.sqlcipher.CursorWrapper)rawQueryWithFactory(null, sql, selectionArgs, null); + ((SQLiteCursor)cursorWrapper.getWrappedCursor()).setLoadStyle(initialRead, maxRead); + return cursorWrapper; + } + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param values this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long insert(String table, String nullColumnHack, ContentValues values) { + try { + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); + } catch (SQLException e) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error inserting into " + table, e); + } + return -1; + } + } + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param values this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @throws SQLException + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long insertOrThrow(String table, String nullColumnHack, ContentValues values) + throws SQLException { + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); + } + + /** + * Convenience method for replacing a row in the database. + * + * @param table the table in which to replace the row + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this row will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for + * the row. The key + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long replace(String table, String nullColumnHack, ContentValues initialValues) { + try { + return insertWithOnConflict(table, nullColumnHack, initialValues, + CONFLICT_REPLACE); + } catch (SQLException e) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error inserting into " + table, e); + } + return -1; + } + } + + /** + * Convenience method for replacing a row in the database. + * + * @param table the table in which to replace the row + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this row will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for + * the row. The key + * @throws SQLException + * @return the row ID of the newly inserted row, or -1 if an error occurred + */ + public long replaceOrThrow(String table, String nullColumnHack, + ContentValues initialValues) throws SQLException { + return insertWithOnConflict(table, nullColumnHack, initialValues, + CONFLICT_REPLACE); + } + + /** + * General method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @param conflictAlgorithm for insert conflict resolver + * + * @return the row ID of the newly inserted row + * OR the primary key of the existing row if the input param 'conflictAlgorithm' = + * {@link #CONFLICT_IGNORE} + * OR -1 if any error + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public long insertWithOnConflict(String table, String nullColumnHack, + ContentValues initialValues, int conflictAlgorithm) { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + + // Measurements show most sql lengths <= 152 + StringBuilder sql = new StringBuilder(152); + sql.append("INSERT"); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(" INTO "); + sql.append(table); + // Measurements show most values lengths < 40 + StringBuilder values = new StringBuilder(40); + + Set> entrySet = null; + if (initialValues != null && initialValues.size() > 0) { + entrySet = initialValues.valueSet(); + Iterator> entriesIter = entrySet.iterator(); + sql.append('('); + + boolean needSeparator = false; + while (entriesIter.hasNext()) { + if (needSeparator) { + sql.append(", "); + values.append(", "); + } + needSeparator = true; + Map.Entry entry = entriesIter.next(); + sql.append(entry.getKey()); + values.append('?'); + } + + sql.append(')'); + } else { + sql.append("(" + nullColumnHack + ") "); + values.append("NULL"); + } + + sql.append(" VALUES("); + sql.append(values); + sql.append(");"); + + lock(); + SQLiteStatement statement = null; + try { + statement = compileStatement(sql.toString()); + + // Bind the values + if (entrySet != null) { + int size = entrySet.size(); + Iterator> entriesIter = entrySet.iterator(); + for (int i = 0; i < size; i++) { + Map.Entry entry = entriesIter.next(); + DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue()); + + } + } + + // Run the program and then cleanup + statement.execute(); + + long insertedRowId = lastChangeCount() > 0 ? lastInsertRow() : -1; + if (insertedRowId == -1) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error inserting using into " + table); + } + } else { + if (BuildConfig.DEBUG && Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Inserting row " + insertedRowId + + " from using into " + table); + } + } + return insertedRowId; + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Convenience method for deleting rows in the database. + * + * @param table the table to delete from + * @param whereClause the optional WHERE clause to apply when deleting. + * Passing null will delete all rows. + * + * @return the number of rows affected if a whereClause is passed in, 0 + * otherwise. To remove all rows and get a count pass "1" as the + * whereClause. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int delete(String table, String whereClause, String[] whereArgs) { + return delete(table, whereClause, (Object[])whereArgs); + } + + /** + * Convenience method for deleting rows in the database. + * + * @param table the table to delete from + * @param whereClause the optional WHERE clause to apply when deleting. + * Passing null will delete all rows. + * + * @return the number of rows affected if a whereClause is passed in, 0 + * otherwise. To remove all rows and get a count pass "1" as the + * whereClause. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int delete(String table, String whereClause, Object[] whereArgs) { + SQLiteStatement statement = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement("DELETE FROM " + table + + (!TextUtils.isEmpty(whereClause) + ? " WHERE " + whereClause : "")); + if (whereArgs != null) { + int numArgs = whereArgs.length; + for (int i = 0; i < numArgs; i++) { + DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]); + } + } + statement.execute(); + return lastChangeCount(); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * + * @return the number of rows affected + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); + } + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * @param conflictAlgorithm for update conflict resolver + * + * @return the number of rows affected + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public int updateWithOnConflict(String table, ContentValues values, + String whereClause, String[] whereArgs, int conflictAlgorithm) { + if (values == null || values.size() == 0) { + throw new IllegalArgumentException("Empty values"); + } + + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(table); + sql.append(" SET "); + + Set> entrySet = values.valueSet(); + Iterator> entriesIter = entrySet.iterator(); + + while (entriesIter.hasNext()) { + Map.Entry entry = entriesIter.next(); + sql.append(entry.getKey()); + sql.append("=?"); + if (entriesIter.hasNext()) { + sql.append(", "); + } + } + + if (!TextUtils.isEmpty(whereClause)) { + sql.append(" WHERE "); + sql.append(whereClause); + } + SQLiteStatement statement = null; + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement(sql.toString()); + + // Bind the values + int size = entrySet.size(); + entriesIter = entrySet.iterator(); + int bindArg = 1; + for (int i = 0; i < size; i++) { + Map.Entry entry = entriesIter.next(); + DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue()); + bindArg++; + } + + if (whereArgs != null) { + size = whereArgs.length; + for (int i = 0; i < size; i++) { + statement.bindString(bindArg, whereArgs[i]); + bindArg++; + } + } + + // Run the program and then cleanup + statement.execute(); + int numChangedRows = lastChangeCount(); + if (BuildConfig.DEBUG && Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Updated " + numChangedRows + + " rows using and for " + table); + } + return numChangedRows; + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } catch (SQLException e) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "Error updating using for " + table); + } + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + /** + * Execute a single SQL statement that is not a query. For example, CREATE + * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not + * supported. it takes a write lock + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public void execSQL(String sql) throws SQLException { + long timeStart = SystemClock.uptimeMillis(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + native_execSQL(sql); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + unlock(); + } + } + + public void rawExecSQL(String sql){ + long timeStart = SystemClock.uptimeMillis(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + native_rawExecSQL(sql); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + unlock(); + } + } + + /** + * Execute a single SQL statement that is not a query. For example, CREATE + * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not + * supported. it takes a write lock, + * + * @param sql + * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. + * + * @throws SQLException If the SQL string is invalid for some reason + * @throws IllegalStateException if the database is not open + */ + public void execSQL(String sql, Object[] bindArgs) throws SQLException { + SQLiteStatement statement = null; + if (bindArgs == null) { + throw new IllegalArgumentException("Empty bindArgs"); + } + long timeStart = SystemClock.uptimeMillis(); + lock(); + try { + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + statement = compileStatement(sql); + if (bindArgs != null) { + int numArgs = bindArgs.length; + for (int i = 0; i < numArgs; i++) { + DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]); + } + } + statement.execute(); + } catch (SQLiteDatabaseCorruptException e) { + onCorruption(); + throw e; + } finally { + if (statement != null) { + statement.close(); + } + unlock(); + } + } + + @Override + protected void finalize() { + if (isOpen()) { + if(BuildConfig.DEBUG){ + Log.e(TAG, "close() was never explicitly called on database '" + + mPath + "' ", mStackTrace); + } + closeClosable(); + onAllReferencesReleased(); + } + } + + /** + * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path The full path to the database + * @param password to use to open and/or create a database file (char array) + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already + * exists, mFlags will be updated appropriately. + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, null); + } + + /** + * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}. + * + *

Sets the locale of the database to the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + * @param path The full path to the database + * @param password to use to open and/or create a database file (char array) + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already + * exists, mFlags will be updated appropriately. + * @param databaseHook to run on pre/post key events + * + * @throws SQLiteException if the database cannot be opened + * @throws IllegalArgumentException if the database path is null + */ + public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, databaseHook); + } + + public SQLiteDatabase(String path, byte[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { + this(path, factory, flags, null); + this.openDatabaseInternal(password, databaseHook); + } + + /** + * Private constructor (without database password) which DOES NOT attempt to open the database. + * + * @param path The full path to the database + * @param factory The factory to use when creating cursors, may be NULL. + * @param flags to control database access mode and other options + * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption (or null for default). + * + * @throws IllegalArgumentException if the database path is null + */ + private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) { + if (path == null) { + throw new IllegalArgumentException("path should not be null"); + } + + mFlags = flags; + mPath = path; + + mSlowQueryThreshold = -1;//SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1); + mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + mFactory = factory; + mPrograms = new WeakHashMap(); + + mErrorHandler = errorHandler; + } + + private void openDatabaseInternal(final char[] password, SQLiteDatabaseHook hook) { + final byte[] keyMaterial = getBytes(password); + openDatabaseInternal(keyMaterial, hook); + } + + private void openDatabaseInternal(final byte[] password, SQLiteDatabaseHook hook) { + boolean shouldCloseConnection = true; + dbopen(mPath, mFlags); + try { + keyDatabase(hook, new Runnable() { + public void run() { + if(password != null && password.length > 0) { + key(password); + } + } + }); + shouldCloseConnection = false; + + } catch(RuntimeException ex) { + + final char[] keyMaterial = getChars(password); + if(containsNull(keyMaterial)) { + keyDatabase(hook, new Runnable() { + public void run() { + if(password != null) { + key_mutf8(keyMaterial); + } + } + }); + if(password != null && password.length > 0) { + rekey(password); + } + shouldCloseConnection = false; + } else { + throw ex; + } + if(keyMaterial != null && keyMaterial.length > 0) { + Arrays.fill(keyMaterial, (char)0); + } + + } finally { + if(shouldCloseConnection) { + dbclose(); + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } + } + } + + } + + private boolean containsNull(char[] data) { + char defaultValue = '\u0000'; + boolean status = false; + if(data != null && data.length > 0) { + for(char datum : data) { + if(datum == defaultValue) { + status = true; + break; + } + } + } + return status; + } + + private void keyDatabase(SQLiteDatabaseHook databaseHook, Runnable keyOperation) { + if(databaseHook != null) { + databaseHook.preKey(this); + } + if(keyOperation != null){ + keyOperation.run(); + } + if(databaseHook != null){ + databaseHook.postKey(this); + } + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeOpened = getTime(); + } + try { + Cursor cursor = rawQuery("select count(*) from sqlite_master;", new String[]{}); + if(cursor != null){ + cursor.moveToFirst(); + int count = cursor.getInt(0); + cursor.close(); + } + } catch (RuntimeException e) { + if(BuildConfig.DEBUG){ + Log.e(TAG, e.getMessage(), e); + } + throw e; + } + } + + private String getTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.US).format(System.currentTimeMillis()); + } + + /** + * return whether the DB is opened as read only. + * @return true if DB is opened as read only + */ + public boolean isReadOnly() { + return (mFlags & OPEN_READ_MASK) == OPEN_READONLY; + } + + /** + * @return true if the DB is currently open (has not been closed) + */ + public boolean isOpen() { + return mNativeHandle != 0; + } + + public boolean needUpgrade(int newVersion) { + /* NOTE: getVersion() will throw if database is not open. */ + return newVersion > getVersion(); + } + + /** + * Getter for the path to the database file. + * + * @return the path to our database file. + */ + public final String getPath() { + return mPath; + } + + /** + * Removes email addresses from database filenames before they're + * logged to the EventLog where otherwise apps could potentially + * read them. + */ + private String getPathForLogs() { + if (mPathForLogs != null) { + return mPathForLogs; + } + if (mPath == null) { + return null; + } + if (mPath.indexOf('@') == -1) { + mPathForLogs = mPath; + } else { + mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY"); + } + return mPathForLogs; + } + + /** + * Sets the locale for this database. Does nothing if this database has + * the NO_LOCALIZED_COLLATORS flag set or was opened read only. + * + * @throws SQLException if the locale could not be set. The most common reason + * for this is that there is no collator available for the locale you requested. + * In this case the database remains unchanged. + */ + public void setLocale(Locale locale) { + lock(); + try { + native_setLocale(locale.toString(), mFlags); + } finally { + unlock(); + } + } + + /* + * ============================================================================ + * + * The following methods deal with compiled-sql cache + * ============================================================================ + */ + /** + * adds the given sql and its compiled-statement-id-returned-by-sqlite to the + * cache of compiledQueries attached to 'this'. + * + * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql, + * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current + * mapping is NOT replaced with the new mapping). + */ + /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE && BuildConfig.DEBUG) { + Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); + } + return; + } + + SQLiteCompiledSql compiledSql = null; + synchronized(mCompiledQueries) { + // don't insert the new mapping if a mapping already exists + compiledSql = mCompiledQueries.get(sql); + if (compiledSql != null) { + return; + } + // add this to the cache + if (mCompiledQueries.size() == mMaxSqlCacheSize) { + /* + * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. + * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times + * chances are it is NOT using ? for bindargs - so caching is useless. + * TODO: either let the callers set max cchesize for their app, or intelligently + * figure out what should be cached for a given app. + */ + if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION && BuildConfig.DEBUG) { + Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + + getPath() + "; i.e., NO space for this sql statement in cache: " + + sql + ". Please change your sql statements to use '?' for " + + "bindargs, instead of using actual values"); + } + // don't add this entry to cache + } else { + // cache is NOT full. add this to cache. + mCompiledQueries.put(sql, compiledStatement); + if (SQLiteDebug.DEBUG_SQL_CACHE && BuildConfig.DEBUG) { + Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + + mCompiledQueries.size() + "|" + sql); + } + } + } + return; + } + + + private void deallocCachedSqlStatements() { + synchronized (mCompiledQueries) { + for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { + compiledSql.releaseSqlStatement(); + } + mCompiledQueries.clear(); + } + } + + /** + * from the compiledQueries cache, returns the compiled-statement-id for the given sql. + * returns null, if not found in the cache. + */ + /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { + SQLiteCompiledSql compiledStatement = null; + boolean cacheHit; + synchronized(mCompiledQueries) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE && BuildConfig.DEBUG) { + Log.v(TAG, "|cache NOT found|" + getPath()); + } + return null; + } + cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; + } + if (cacheHit) { + mNumCacheHits++; + } else { + mNumCacheMisses++; + } + + if (SQLiteDebug.DEBUG_SQL_CACHE && BuildConfig.DEBUG) { + Log.v(TAG, "|cache_stats|" + + getPath() + "|" + mCompiledQueries.size() + + "|" + mNumCacheHits + "|" + mNumCacheMisses + + "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql); + } + return compiledStatement; + } + + /** + * returns true if the given sql is cached in compiled-sql cache. + * @hide + */ + public boolean isInCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + return mCompiledQueries.containsKey(sql); + } + } + + /** + * purges the given sql from the compiled-sql cache. + * @hide + */ + public void purgeFromCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + mCompiledQueries.remove(sql); + } + } + + /** + * remove everything from the compiled sql cache + * @hide + */ + public void resetCompiledSqlCache() { + deallocCachedSqlStatements(); + } + + /** + * return the current maxCacheSqlCacheSize + * @hide + */ + public synchronized int getMaxSqlCacheSize() { + return mMaxSqlCacheSize; + } + + /** + * set the max size of the compiled sql cache for this database after purging the cache. + * (size of the cache = number of compiled-sql-statements stored in the cache). + * + * max cache size can ONLY be increased from its current size (default = 0). + * if this method is called with smaller size than the current value of mMaxSqlCacheSize, + * then IllegalStateException is thrown + * + * synchronized because we don't want t threads to change cache size at the same time. + * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) + * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or + * < the value set with previous setMaxSqlCacheSize() call. + * + * @hide + */ + public synchronized void setMaxSqlCacheSize(int cacheSize) { + if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { + throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); + } else if (cacheSize < mMaxSqlCacheSize) { + throw new IllegalStateException("cannot set cacheSize to a value less than the value " + + "set with previous setMaxSqlCacheSize() call."); + } + mMaxSqlCacheSize = cacheSize; + } + + public static byte[] getBytes(char[] data) { + if(data == null || data.length == 0) return null; + CharBuffer charBuffer = CharBuffer.wrap(data); + ByteBuffer byteBuffer = Charset.forName(KEY_ENCODING).encode(charBuffer); + byte[] result = new byte[byteBuffer.limit()]; + byteBuffer.get(result); + return result; + } + + public static char[] getChars(byte[] data){ + if(data == null || data.length == 0) return null; + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + CharBuffer charBuffer = Charset.forName(KEY_ENCODING).decode(byteBuffer); + char[] result = new char[charBuffer.limit()]; + charBuffer.get(result); + return result; + } + + /* begin SQLiteSupportDatabase methods */ + + @Override + public android.database.Cursor query(String query) { + return rawQuery(query, null); + } + + @Override + public android.database.Cursor query(String query, Object[] bindArgs) { + return rawQuery(query, bindArgs); + } + + @Override + public android.database.Cursor query(SupportSQLiteQuery query) { + return query(query, null); + } + + @Override + public android.database.Cursor query(final SupportSQLiteQuery supportQuery, + CancellationSignal cancellationSignal) { + String sql = supportQuery.getSql(); + int argumentCount = supportQuery.getArgCount(); + Object[] args = new Object[argumentCount]; + SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null); + SQLiteQuery query = new SQLiteQuery(this, sql, 0, args); + supportQuery.bindTo(query); + return new CrossProcessCursorWrapper(new SQLiteCursor(this, driver, null, query)); + } + + @Override + public long insert(String table, int conflictAlgorithm, + ContentValues values) + throws android.database.SQLException { + return insertWithOnConflict(table, null, values, conflictAlgorithm); + } + + @Override + public int update(String table, int conflictAlgorithm, ContentValues values, + String whereClause, Object[] whereArgs) { + int whereArgsLength = whereArgs == null + ? 0 + : whereArgs.length; + String[] args = new String[whereArgsLength]; + for (int i = 0; i < whereArgsLength; i++) { + args[i] = whereArgs[i].toString(); + } + return updateWithOnConflict(table, values, whereClause, args, conflictAlgorithm); + } + + @Override + public void beginTransactionWithListener( + final android.database.sqlite.SQLiteTransactionListener transactionListener) { + beginTransactionWithListener(new SQLiteTransactionListener() { + @Override + public void onBegin() { + transactionListener.onBegin(); + } + + @Override + public void onCommit() { + transactionListener.onCommit(); + } + + @Override + public void onRollback() { + transactionListener.onRollback(); + } + }); + } + + @Override + public void beginTransactionWithListenerNonExclusive( + final android.database.sqlite.SQLiteTransactionListener transactionListener) { + beginTransactionWithListenerNonExclusive( + new SQLiteTransactionListener() { + @Override + public void onBegin() { + transactionListener.onBegin(); + } + + @Override + public void onCommit() { + transactionListener.onCommit(); + } + + @Override + public void onRollback() { + transactionListener.onRollback(); + } + }); + } + + /* end SQLiteSupportDatabase methods */ + + private void beginTransactionWithListenerInternal(SQLiteTransactionListener transactionListener, + SQLiteDatabaseTransactionType transactionType) { + lockForced(); + if (!isOpen()) { + throw new IllegalStateException("database not open"); + } + boolean ok = false; + try { + // If this thread already had the lock then get out + if (mLock.getHoldCount() > 1) { + if (mInnerTransactionIsSuccessful) { + String msg = "Cannot call beginTransaction between " + + "calling setTransactionSuccessful and endTransaction"; + IllegalStateException e = new IllegalStateException(msg); + if(BuildConfig.DEBUG){ + Log.e(TAG, "beginTransaction() failed", e); + } + throw e; + } + ok = true; + return; + } + // This thread didn't already have the lock, so begin a database + // transaction now. + if(transactionType == SQLiteDatabaseTransactionType.Exclusive) { + execSQL("BEGIN EXCLUSIVE;"); + } else if(transactionType == SQLiteDatabaseTransactionType.Immediate) { + execSQL("BEGIN IMMEDIATE;"); + } else if(transactionType == SQLiteDatabaseTransactionType.Deferred) { + execSQL("BEGIN DEFERRED;"); + } else { + String message = String.format("%s is an unsupported transaction type", + transactionType); + throw new IllegalArgumentException(message); + } + mTransactionListener = transactionListener; + mTransactionIsSuccessful = true; + mInnerTransactionIsSuccessful = false; + if (transactionListener != null) { + try { + transactionListener.onBegin(); + } catch (RuntimeException e) { + execSQL("ROLLBACK;"); + throw e; + } + } + ok = true; + } finally { + if (!ok) { + // beginTransaction is called before the try block so we must release the lock in + // the case of failure. + unlockForced(); + } + } + } + + /** + * this method is used to collect data about ALL open databases in the current process. + * bugreport is a user of this data. + */ + /* package */ static ArrayList getDbStats() { + ArrayList dbStatsList = new ArrayList(); + + for (SQLiteDatabase db : getActiveDatabases()) { + if (db == null || !db.isOpen()) { + continue; + } + + // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db + int lookasideUsed = db.native_getDbLookaside(); + + // get the lastnode of the dbname + String path = db.getPath(); + int indx = path.lastIndexOf("/"); + String lastnode = path.substring((indx != -1) ? ++indx : 0); + + // get list of attached dbs and for each db, get its size and pagesize + ArrayList> attachedDbs = getAttachedDbs(db); + if (attachedDbs == null) { + continue; + } + for (int i = 0; i < attachedDbs.size(); i++) { + Pair p = attachedDbs.get(i); + long pageCount = getPragmaVal(db, p.first + ".page_count;"); + + // first entry in the attached db list is always the main database + // don't worry about prefixing the dbname with "main" + String dbName; + if (i == 0) { + dbName = lastnode; + } else { + // lookaside is only relevant for the main db + lookasideUsed = 0; + dbName = " (attached) " + p.first; + // if the attached db has a path, attach the lastnode from the path to above + if (p.second.trim().length() > 0) { + int idx = p.second.lastIndexOf("/"); + dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0); + } + } + if (pageCount > 0) { + dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), + lookasideUsed)); + } + } + } + return dbStatsList; + } + + private static ArrayList getActiveDatabases() { + ArrayList databases = new ArrayList(); + synchronized (sActiveDatabases) { + databases.addAll(sActiveDatabases.keySet()); + } + return databases; + } + + /** + * get the specified pragma value from sqlite for the specified database. + * only handles pragma's that return int/long. + * NO JAVA locks are held in this method. + * TODO: use this to do all pragma's in this class + */ + private static long getPragmaVal(SQLiteDatabase db, String pragma) { + if (!db.isOpen()) { + return 0; + } + SQLiteStatement prog = null; + try { + prog = new SQLiteStatement(db, "PRAGMA " + pragma); + long val = prog.simpleQueryForLong(); + return val; + } finally { + if (prog != null) prog.close(); + } + } + + /** + * returns list of full pathnames of all attached databases + * including the main database + * TODO: move this to {@link DatabaseUtils} + */ + private static ArrayList> getAttachedDbs(SQLiteDatabase dbObj) { + if (!dbObj.isOpen()) { + return null; + } + ArrayList> attachedDbs = new ArrayList>(); + Cursor c = dbObj.rawQuery("pragma database_list;", null); + while (c.moveToNext()) { + attachedDbs.add(new Pair(c.getString(1), c.getString(2))); + } + c.close(); + return attachedDbs; + } + + private Pair getResultFromPragma(String command) { + Pair result = new Pair(false, ""); + Cursor cursor = rawQuery(command, new Object[]{}); + if(cursor == null) return result; + if(cursor.moveToFirst()){ + String value = cursor.getString(0); + result = new Pair(true, value); + } + cursor.close(); + return result; + } + + + /** + * Sets the root directory to search for the ICU data file + */ + public static native void setICURoot(String path); + + /** + * Native call to open the database. + * + * @param path The full path to the database + */ + private native void dbopen(String path, int flags); + + /** + * Native call to setup tracing of all sql statements + * + * @param path the full path to the database + */ + private native void enableSqlTracing(String path); + + /** + * Native call to setup profiling of all sql statements. + * currently, sqlite's profiling = printing of execution-time + * (wall-clock time) of each of the sql statements, as they + * are executed. + * + * @param path the full path to the database + */ + private native void enableSqlProfiling(String path); + + /** + * Native call to execute a raw SQL statement. {@link #lock} must be held + * when calling this method. + * + * @param sql The raw SQL string + * + * @throws SQLException + */ + /* package */ native void native_execSQL(String sql) throws SQLException; + + /** + * Native call to set the locale. {@link #lock} must be held when calling + * this method. + * + * @throws SQLException + */ + /* package */ native void native_setLocale(String loc, int flags); + + /** + * Returns the row ID of the last row inserted into the database. + * + * @return the row ID of the last row inserted into the database. + */ + /* package */ native long lastInsertRow(); + + /** + * Returns the number of changes made in the last statement executed. + * + * @return the number of changes made in the last statement executed. + */ + /* package */ native int lastChangeCount(); + + /** + * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here + * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html + * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED + */ + private native int native_getDbLookaside(); + + private native void native_rawExecSQL(String sql); + + private native int native_status(int operation, boolean reset); + + private native void key(byte[] key) throws SQLException; + private native void key_mutf8(char[] key) throws SQLException; + private native void rekey(byte[] key) throws SQLException; +} diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java new file mode 100644 index 00000000..a5014b14 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java @@ -0,0 +1,15 @@ +package net.sqlcipher.database; + +/** + * An interface to perform pre and post key operations against a database. + */ +public interface SQLiteDatabaseHook { + /** + * Called immediately before opening the database. + */ + void preKey(SQLiteDatabase database); + /** + * Called immediately after opening the database. + */ + void postKey(SQLiteDatabase database); +} diff --git a/src/net/sqlcipher/database/SQLiteDebug.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java similarity index 100% rename from src/net/sqlcipher/database/SQLiteDebug.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java diff --git a/src/net/sqlcipher/database/SQLiteDirectCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java similarity index 72% rename from src/net/sqlcipher/database/SQLiteDirectCursorDriver.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java index 2b6cffc7..36ae59a4 100644 --- a/src/net/sqlcipher/database/SQLiteDirectCursorDriver.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java @@ -16,18 +16,18 @@ package net.sqlcipher.database; +import net.sqlcipher.Cursor; import net.sqlcipher.database.SQLiteDatabase.CursorFactory; -import android.database.Cursor; /** * A cursor driver that uses the given query directly. - * + * * @hide */ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { - private String mEditTable; + private String mEditTable; private SQLiteDatabase mDatabase; - private android.database.Cursor mCursor; + private Cursor mCursor; private String mSql; private SQLiteQuery mQuery; @@ -37,7 +37,25 @@ public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) mSql = sql; } - public android.database.Cursor query(CursorFactory factory, String[] selectionArgs) { + public Cursor query(CursorFactory factory, Object[] args) { + SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, args); + try { + query.bindArguments(args); + if (factory == null) { + mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query); + } else { + mCursor = factory.newCursor(mDatabase, this, mEditTable, query); + } + mQuery = query; + query = null; + return mCursor; + } finally { + // Make sure this object is cleaned up if something happens + if (query != null) query.close(); + } + } + + public Cursor query(CursorFactory factory, String[] selectionArgs) { // Compile the query SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs); @@ -51,7 +69,7 @@ public android.database.Cursor query(CursorFactory factory, String[] selectionAr // Create the cursor if (factory == null) { mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query); - + } else { mCursor = factory.newCursor(mDatabase, this, mEditTable, query); } @@ -76,11 +94,13 @@ public void setBindArguments(String[] bindArgs) { } } + @Override public void cursorDeactivated() { // Do nothing } - public void cursorRequeried(Cursor cursor) { + @Override + public void cursorRequeried(android.database.Cursor cursor) { // Do nothing } diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java new file mode 100644 index 00000000..e3a24f43 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.io.File; + +import android.content.Context; +import android.database.sqlite.SQLiteException; +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.DefaultDatabaseErrorHandler; +import net.sqlcipher.database.SQLiteDatabaseHook; +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; +import android.util.Log; + +/** + * A helper class to manage database creation and version management. + * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and + * optionally {@link #onOpen}, and this class takes care of opening the database + * if it exists, creating it if it does not, and upgrading it as necessary. + * Transactions are used to make sure the database is always in a sensible state. + *

For an example, see the NotePadProvider class in the NotePad sample application, + * in the samples/ directory of the SDK.

+ */ +public abstract class SQLiteOpenHelper { + private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); + + private final Context mContext; + private final String mName; + private final CursorFactory mFactory; + private final int mNewVersion; + private final SQLiteDatabaseHook mHook; + private final DatabaseErrorHandler mErrorHandler; + private boolean mEnableWriteAheadLogging; + private boolean mDeferSetWriteAheadLoggingEnabled; + + private SQLiteDatabase mDatabase = null; + private boolean mIsInitializing = false; + + /** + * Create a helper object to create, open, and/or manage a database. + * This method always returns very quickly. The database is not actually + * created or opened until one of {@link #getWritableDatabase} or + * {@link #getReadableDatabase} is called. + * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { + this(context, name, factory, version, null, new DefaultDatabaseErrorHandler()); + } + + /** + * Create a helper object to create, open, and/or manage a database. + * The database is not actually created or opened until one of + * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. + * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + * @param hook to run on pre/post key events + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, + int version, SQLiteDatabaseHook hook) { + this(context, name, factory, version, hook, new DefaultDatabaseErrorHandler()); + } + + /** + * Create a helper object to create, open, and/or manage a database. + * The database is not actually created or opened until one of + * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. + * + *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be + * used to handle corruption when sqlite reports database corruption.

+ * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database + * @param hook to run on pre/post key events + * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption. + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, + int version, SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) { + if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); + if (errorHandler == null) { + throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null."); + } + + mContext = context; + mName = name; + mFactory = factory; + mNewVersion = version; + mHook = hook; + mErrorHandler = errorHandler; + } + + /** + * Create and/or open a database that will be used for reading and writing. + * Once opened successfully, the database is cached, so you can call this + * method every time you need to write to the database. Make sure to call + * {@link #close} when you no longer need it. + * + *

Errors such as bad permissions or a full disk may cause this operation + * to fail, but future attempts may succeed if the problem is fixed.

+ * + * @throws SQLiteException if the database cannot be opened for writing + * @return a read/write database object valid until {@link #close} is called + */ + + public synchronized SQLiteDatabase getWritableDatabase(String password) { + return getWritableDatabase(password == null ? null : password.toCharArray()); + } + + public synchronized SQLiteDatabase getWritableDatabase(char[] password) { + return getWritableDatabase(password == null ? null : SQLiteDatabase.getBytes(password)); + } + + public synchronized SQLiteDatabase getWritableDatabase(byte[] password) { + if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { + return mDatabase; // The database is already open for business + } + + if (mIsInitializing) { + throw new IllegalStateException("getWritableDatabase called recursively"); + } + + // If we have a read-only database open, someone could be using it + // (though they shouldn't), which would cause a lock to be held on + // the file, and our attempts to open the database read-write would + // fail waiting for the file lock. To prevent that, we acquire the + // lock on the read-only database, which shuts out other users. + + boolean success = false; + SQLiteDatabase db = null; + if (mDatabase != null) mDatabase.lock(); + try { + mIsInitializing = true; + if (mName == null) { + db = SQLiteDatabase.create(null, ""); + } else { + String path = mContext.getDatabasePath(mName).getPath(); + File dbPathFile = new File (path); + if (!dbPathFile.exists()) { + dbPathFile.getParentFile().mkdirs(); + } + db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory, mHook, mErrorHandler); + } + if(mDeferSetWriteAheadLoggingEnabled) { + mEnableWriteAheadLogging = db.enableWriteAheadLogging(); + } + onConfigure(db); + int version = db.getVersion(); + if (version != mNewVersion) { + db.beginTransaction(); + try { + if (version == 0) { + onCreate(db); + } else { + if(version > mNewVersion) { + onDowngrade(db, version, mNewVersion); + } else { + onUpgrade(db, version, mNewVersion); + } + } + db.setVersion(mNewVersion); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + onOpen(db); + success = true; + return db; + } finally { + mIsInitializing = false; + if (success) { + if (mDatabase != null) { + try { mDatabase.close(); } catch (Exception e) { } + mDatabase.unlock(); + } + mDatabase = db; + } else { + if (mDatabase != null) mDatabase.unlock(); + if (db != null) db.close(); + } + } + } + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + * @throws SQLiteException if the database cannot be opened + * @return a database object valid until {@link #getWritableDatabase} + * or {@link #close} is called. + */ + public synchronized SQLiteDatabase getReadableDatabase(String password) { + return getReadableDatabase(password == null ? null : password.toCharArray()); + } + + public synchronized SQLiteDatabase getReadableDatabase(char[] password) { + return getReadableDatabase(password == null ? null : SQLiteDatabase.getBytes(password)); + } + + public synchronized SQLiteDatabase getReadableDatabase(byte[] password) { + if (mDatabase != null && mDatabase.isOpen()) { + return mDatabase; // The database is already open for business + } + + if (mIsInitializing) { + throw new IllegalStateException("getReadableDatabase called recursively"); + } + + try { + return getWritableDatabase(password); + } catch (SQLiteException e) { + if (mName == null) throw e; // Can't open a temp database read-only! + Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); + } + + SQLiteDatabase db = null; + try { + mIsInitializing = true; + String path = mContext.getDatabasePath(mName).getPath(); + File databasePath = new File(path); + File databasesDirectory = new File(mContext.getDatabasePath(mName).getParent()); + + if(!databasesDirectory.exists()){ + databasesDirectory.mkdirs(); + } + if(!databasePath.exists()){ + mIsInitializing = false; + db = getWritableDatabase(password); + mIsInitializing = true; + db.close(); + } + db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY, mHook, mErrorHandler); + if (db.getVersion() != mNewVersion) { + throw new SQLiteException("Can't upgrade read-only database from version " + + db.getVersion() + " to " + mNewVersion + ": " + path); + } + + onOpen(db); + Log.w(TAG, "Opened " + mName + " in read-only mode"); + mDatabase = db; + return mDatabase; + } finally { + mIsInitializing = false; + if (db != null && db != mDatabase) db.close(); + } + } + + /** + * Close any open database object. + */ + public synchronized void close() { + if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); + + if (mDatabase != null && mDatabase.isOpen()) { + mDatabase.close(); + mDatabase = null; + } + } + + /** + * Return the name of the SQLite database being opened, as given to + * the constructor. + */ + public String getDatabaseName() { + return mName; + } + + /** + * Enables or disables the use of write-ahead logging for the database. + * + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + * @param enabled True if write-ahead logging should be enabled, false if it + * should be disabled. + * + * @see SQLiteDatabase#enableWriteAheadLogging() + */ + public void setWriteAheadLoggingEnabled(boolean enabled) { + synchronized (this) { + if (mEnableWriteAheadLogging != enabled) { + if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { + if (enabled) { + mDatabase.enableWriteAheadLogging(); + } else { + mDatabase.disableWriteAheadLogging(); + } + mEnableWriteAheadLogging = enabled; + } else { + mDeferSetWriteAheadLoggingEnabled = enabled; + } + } + } + } + + /** + * Called when the database needs to be downgraded. This is strictly similar to + * {@link #onUpgrade} method, but is called whenever current version is newer than requested one. + * However, this method is not abstract, so it is not mandatory for a customer to + * implement it. If not overridden, default implementation will reject downgrade and + * throws SQLiteException + * + *

+ * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + *

+ * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + throw new SQLiteException("Can't downgrade database from version " + + oldVersion + " to " + newVersion); + } + + /** + * Called when the database connection is being configured, to enable features + * such as write-ahead logging or foreign key support. + *

+ * This method is called before {@link #onCreate}, {@link #onUpgrade}, + * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify + * the database except to configure the database connection as required. + *

+ * This method should only call methods that configure the parameters of the + * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging} + * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, + * {@link SQLiteDatabase#setLocale}, or executing PRAGMA statements. + *

+ * + * @param db The database. + */ + public void onConfigure(SQLiteDatabase db) {} + + /** + * Called when the database is created for the first time. This is where the + * creation of tables and the initial population of the tables should happen. + * + * @param db The database. + */ + public abstract void onCreate(SQLiteDatabase db); + + /** + * Called when the database needs to be upgraded. The implementation + * should use this method to drop tables, add tables, or do anything else it + * needs to upgrade to the new schema version. + * + *

The SQLite ALTER TABLE documentation can be found + * here. If you add new columns + * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns + * you can use ALTER TABLE to rename the old table, then create the new table and then + * populate the new table with the contents of the old table. + * + * @param db The database. + * @param oldVersion The old database version. + * @param newVersion The new database version. + */ + public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); + + /** + * Called when the database has been opened. + * Override method should check {@link SQLiteDatabase#isReadOnly} before + * updating the database. + * + * @param db The database. + */ + public void onOpen(SQLiteDatabase db) {} +} diff --git a/src/net/sqlcipher/database/SQLiteProgram.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java similarity index 88% rename from src/net/sqlcipher/database/SQLiteProgram.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java index 658b0b90..e43826d9 100644 --- a/src/net/sqlcipher/database/SQLiteProgram.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java @@ -17,6 +17,7 @@ package net.sqlcipher.database; import android.util.Log; +import androidx.sqlite.db.SupportSQLiteProgram; /** * A base class for compiled SQLite programs. @@ -24,7 +25,8 @@ * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple * threads should perform its own synchronization when using the SQLiteProgram. */ -public abstract class SQLiteProgram extends SQLiteClosable { +public abstract class SQLiteProgram extends SQLiteClosable implements + SupportSQLiteProgram { private static final String TAG = "SQLiteProgram"; @@ -43,7 +45,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @deprecated do not use this */ @Deprecated - protected int nHandle = 0; + protected long nHandle = 0; /** * the SQLiteCompiledSql object for the given sql statement. @@ -56,7 +58,12 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @deprecated do not use this */ @Deprecated - protected int nStatement = 0; + protected long nStatement = 0; + + /** + * Indicates whether {@link #close()} has been called. + */ + boolean mClosed = false; /* package */ SQLiteProgram(SQLiteDatabase db, String sql) { mDatabase = db; @@ -64,9 +71,10 @@ public abstract class SQLiteProgram extends SQLiteClosable { db.acquireReference(); db.addSQLiteClosable(this); this.nHandle = db.mNativeHandle; + int crudPrefixLength = 6; // only cache CRUD statements - String prefixSql = mSql.substring(0, 6); + String prefixSql = mSql.length() >= crudPrefixLength ? mSql.substring(0, crudPrefixLength) : mSql; if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") && !prefixSql.equalsIgnoreCase("REPLAC") && !prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) { @@ -95,7 +103,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { // it is already in compiled-sql cache. // try to acquire the object. if (!mCompiledSql.acquire()) { - int last = mCompiledSql.nStatement; + long last = mCompiledSql.nStatement; // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object. // we can't have two different SQLiteProgam objects can't share the same // CompiledSql object. create a new one. @@ -141,7 +149,7 @@ private void releaseCompiledSqlIfNotInCache() { // it is in compiled-sql cache. reset its CompiledSql#mInUse flag mCompiledSql.release(); } - } + } } /** @@ -149,7 +157,7 @@ private void releaseCompiledSqlIfNotInCache() { * * @return a unique identifier for this program */ - public final int getUniqueId() { + public final long getUniqueId() { return nStatement; } @@ -175,7 +183,11 @@ protected void compile(String sql, boolean forceCompilation) { * * @param index The 1-based index to the parameter to bind null to */ + @Override public void bindNull(int index) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -194,7 +206,11 @@ public void bindNull(int index) { * @param index The 1-based index to the parameter to bind * @param value The value to bind */ + @Override public void bindLong(int index, long value) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -213,7 +229,11 @@ public void bindLong(int index, long value) { * @param index The 1-based index to the parameter to bind * @param value The value to bind */ + @Override public void bindDouble(int index, double value) { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -232,10 +252,14 @@ public void bindDouble(int index, double value) { * @param index The 1-based index to the parameter to bind * @param value The value to bind */ + @Override public void bindString(int index, String value) { if (value == null) { throw new IllegalArgumentException("the bind value at index " + index + " is null"); } + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -254,10 +278,14 @@ public void bindString(int index, String value) { * @param index The 1-based index to the parameter to bind * @param value The value to bind */ + @Override public void bindBlob(int index, byte[] value) { if (value == null) { throw new IllegalArgumentException("the bind value at index " + index + " is null"); } + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -272,7 +300,11 @@ public void bindBlob(int index, byte[] value) { /** * Clears all existing bindings. Unset bindings are treated as NULL. */ + @Override public void clearBindings() { + if (mClosed) { + throw new IllegalStateException("program already closed"); + } if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -288,6 +320,9 @@ public void clearBindings() { * Release this program's resources, making it invalid. */ public void close() { + if (mClosed) { + return; + } if (!mDatabase.isOpen()) { return; } @@ -297,6 +332,7 @@ public void close() { } finally { mDatabase.unlock(); } + mClosed = true; } /** diff --git a/src/net/sqlcipher/database/SQLiteQuery.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java similarity index 67% rename from src/net/sqlcipher/database/SQLiteQuery.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java index e0bb0d47..c87bd590 100644 --- a/src/net/sqlcipher/database/SQLiteQuery.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java @@ -17,6 +17,8 @@ package net.sqlcipher.database; import net.sqlcipher.*; +import android.database.sqlite.SQLiteDatabaseCorruptException; +import android.database.sqlite.SQLiteMisuseException; import android.os.SystemClock; import android.util.Log; @@ -32,18 +34,17 @@ public class SQLiteQuery extends SQLiteProgram { /** The index of the unbound OFFSET parameter */ private int mOffsetIndex; - + /** Args to bind on requery */ private String[] mBindArgs; - - private boolean mClosed = false; + private Object[] mObjectBindArgs; /** * Create a persistent query object. - * + * * @param db The database that this query object is associated with - * @param query The SQL string for this query. - * @param offsetIndex The 1-based index to the OFFSET parameter, + * @param query The SQL string for this query. + * @param offsetIndex The 1-based index to the OFFSET parameter, */ /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) { super(db, query); @@ -52,17 +53,25 @@ public class SQLiteQuery extends SQLiteProgram { mBindArgs = bindArgs; } + SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, Object[] bindArgs) { + super(db, query); + mOffsetIndex = offsetIndex; + mObjectBindArgs = bindArgs; + int length = mObjectBindArgs != null ? mObjectBindArgs.length : 0; + mBindArgs = new String[length]; + } + /** * Reads rows into a buffer. This method acquires the database lock. * * @param window The window to fill into * @return number of total rows in the query */ - /* package */ int fillWindow(CursorWindow window, - int maxRead, int lastPos) { + /* package */ + int fillWindow(CursorWindow window, + int maxRead, int lastPos) { long timeStart = SystemClock.uptimeMillis(); mDatabase.lock(); - mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX); try { acquireReference(); try { @@ -70,14 +79,16 @@ public class SQLiteQuery extends SQLiteProgram { // if the start pos is not equal to 0, then most likely window is // too small for the data set, loading by another thread // is not safe in this situation. the native code will ignore maxRead - int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, - maxRead, lastPos); + int numRows = native_fill_window(window, + window.getStartPosition(), + window.getRequiredPosition(), + mOffsetIndex, + maxRead, lastPos); // Logging if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { Log.d(TAG, "fillWindow(): " + mSql); } - mDatabase.logTimeStat(mSql, timeStart); return numRows; } catch (IllegalStateException e){ // simply ignore it @@ -98,7 +109,7 @@ public class SQLiteQuery extends SQLiteProgram { * Get the column count for the statement. Only valid on query based * statements. The database must be locked * when calling this method. - * + * * @return The number of column in the statement's result set. */ /* package */ int columnCountLocked() { @@ -113,7 +124,7 @@ public class SQLiteQuery extends SQLiteProgram { /** * Retrieves the column name for the given column index. The database must be locked * when calling this method. - * + * * @param columnIndex the index of the column to get the name for * @return The requested column's name */ @@ -125,17 +136,11 @@ public class SQLiteQuery extends SQLiteProgram { releaseReference(); } } - + @Override public String toString() { return "SQLiteQuery: " + mSql; } - - @Override - public void close() { - super.close(); - mClosed = true; - } /** * Called by SQLiteCursor when it is requeried. @@ -144,8 +149,12 @@ public void close() { if (mBindArgs != null) { int len = mBindArgs.length; try { - for (int i = 0; i < len; i++) { - super.bindString(i + 1, mBindArgs[i]); + if(mObjectBindArgs != null) { + bindArguments(mObjectBindArgs); + } else { + for (int i = 0; i < len; i++) { + super.bindString(i + 1, mBindArgs[i]); + } } } catch (SQLiteMisuseException e) { StringBuilder errMsg = new StringBuilder("mSql " + mSql); @@ -156,7 +165,7 @@ public void close() { errMsg.append(" "); IllegalStateException leakProgram = new IllegalStateException( errMsg.toString(), e); - throw leakProgram; + throw leakProgram; } } } @@ -185,8 +194,37 @@ public void bindString(int index, String value) { if (!mClosed) super.bindString(index, value); } - private final native int native_fill_window(CursorWindow window, - int startPos, int offsetParam, int maxRead, int lastPos); + public void bindArguments(Object[] args){ + if(args != null && args.length > 0){ + for(int i = 0; i < args.length; i++){ + Object value = args[i]; + if(value == null){ + bindNull(i + 1); + } else if (value instanceof Double) { + bindDouble(i + 1, (Double)value); + } else if (value instanceof Float) { + float number = ((Number)value).floatValue(); + bindDouble(i + 1, Double.valueOf(number)); + } else if (value instanceof Long) { + bindLong(i + 1, (Long)value); + } else if(value instanceof Integer) { + int number = ((Number) value).intValue(); + bindLong(i + 1, Long.valueOf(number)); + } else if (value instanceof Boolean) { + bindLong(i + 1, (Boolean)value ? 1 : 0); + } else if (value instanceof byte[]) { + bindBlob(i + 1, (byte[])value); + } else { + bindString(i + 1, value.toString()); + } + } + } + } + + private final native int native_fill_window(CursorWindow window, + int startPos, int requiredPos, + int offsetParam, int maxRead, + int lastPos); private final native int native_column_count(); diff --git a/src/net/sqlcipher/database/SQLiteQueryBuilder.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java similarity index 99% rename from src/net/sqlcipher/database/SQLiteQueryBuilder.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java index 3191b626..d47f5593 100644 --- a/src/net/sqlcipher/database/SQLiteQueryBuilder.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java @@ -15,6 +15,7 @@ */ package net.sqlcipher.database; + import net.sqlcipher.*; import android.provider.BaseColumns; @@ -273,7 +274,7 @@ public static void appendColumns(StringBuilder s, String[] columns) { * @see android.content.ContentResolver#query(android.net.Uri, String[], * String, String[], String) */ - public android.database.Cursor query(SQLiteDatabase db, String[] projectionIn, + public Cursor query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder) { return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, @@ -312,7 +313,7 @@ public android.database.Cursor query(SQLiteDatabase db, String[] projectionIn, * @see android.content.ContentResolver#query(android.net.Uri, String[], * String, String[], String) */ - public android.database.Cursor query(SQLiteDatabase db, String[] projectionIn, + public Cursor query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) { if (mTables == null) { diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java new file mode 100644 index 00000000..4b36c05f --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java @@ -0,0 +1,20 @@ +package net.sqlcipher.database; + +public class SQLiteQueryStats { + long totalQueryResultSize = 0L; + long largestIndividualRowSize = 0L; + + public SQLiteQueryStats(long totalQueryResultSize, + long largestIndividualRowSize) { + this.totalQueryResultSize = totalQueryResultSize; + this.largestIndividualRowSize = largestIndividualRowSize; + } + + public long getTotalQueryResultSize(){ + return totalQueryResultSize; + } + + public long getLargestIndividualRowSize(){ + return largestIndividualRowSize; + } +} diff --git a/src/net/sqlcipher/database/SQLiteStatement.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java similarity index 87% rename from src/net/sqlcipher/database/SQLiteStatement.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java index cb8e17fe..84b7b4c2 100644 --- a/src/net/sqlcipher/database/SQLiteStatement.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java @@ -17,6 +17,7 @@ package net.sqlcipher.database; import android.os.SystemClock; +import androidx.sqlite.db.SupportSQLiteStatement; /** * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. @@ -27,7 +28,8 @@ * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple * threads should perform its own synchronization when using the SQLiteStatement. */ -public class SQLiteStatement extends SQLiteProgram +public class SQLiteStatement extends SQLiteProgram implements + SupportSQLiteStatement { /** * Don't use SQLiteStatement constructor directly, please use @@ -46,6 +48,7 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.SQLException If the SQL string is invalid for * some reason */ + @Override public void execute() { if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); @@ -56,7 +59,6 @@ public void execute() { acquireReference(); try { native_execute(); - mDatabase.logTimeStat(mSql, timeStart); } finally { releaseReference(); mDatabase.unlock(); @@ -72,6 +74,7 @@ public void execute() { * @throws android.database.SQLException If the SQL string is invalid for * some reason */ + @Override public long executeInsert() { if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); @@ -82,7 +85,6 @@ public long executeInsert() { acquireReference(); try { native_execute(); - mDatabase.logTimeStat(mSql, timeStart); return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1; } finally { releaseReference(); @@ -90,6 +92,24 @@ public long executeInsert() { } } + @Override + public int executeUpdateDelete() { + if (!mDatabase.isOpen()) { + throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); + } + long timeStart = SystemClock.uptimeMillis(); + mDatabase.lock(); + + acquireReference(); + try { + native_execute(); + return mDatabase.lastChangeCount(); + } finally { + releaseReference(); + mDatabase.unlock(); + } + } + /** * Execute a statement that returns a 1 by 1 table with a numeric value. * For example, SELECT COUNT(*) FROM table; @@ -98,6 +118,7 @@ public long executeInsert() { * * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ + @Override public long simpleQueryForLong() { if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); @@ -108,7 +129,6 @@ public long simpleQueryForLong() { acquireReference(); try { long retValue = native_1x1_long(); - mDatabase.logTimeStat(mSql, timeStart); return retValue; } finally { releaseReference(); @@ -124,6 +144,7 @@ public long simpleQueryForLong() { * * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ + @Override public String simpleQueryForString() { if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); @@ -134,7 +155,6 @@ public String simpleQueryForString() { acquireReference(); try { String retValue = native_1x1_string(); - mDatabase.logTimeStat(mSql, timeStart); return retValue; } finally { releaseReference(); diff --git a/src/net/sqlcipher/database/SQLiteTransactionListener.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java similarity index 100% rename from src/net/sqlcipher/database/SQLiteTransactionListener.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java diff --git a/src/net/sqlcipher/database/SqliteWrapper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java similarity index 96% rename from src/net/sqlcipher/database/SqliteWrapper.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java index 9772b063..1d15f99e 100644 --- a/src/net/sqlcipher/database/SqliteWrapper.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java @@ -20,8 +20,10 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; + import net.sqlcipher.*; + +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.util.Log; import android.widget.Toast; @@ -64,7 +66,7 @@ public static Cursor query(Context context, ContentResolver resolver, Uri uri, } } - public static boolean requery(Context context, Cursor cursor) { + public static boolean requery(Context context, android.database.Cursor cursor) { try { return cursor.requery(); } catch (SQLiteException e) { diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java new file mode 100644 index 00000000..2be2c2b2 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Mark L. Murphy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import androidx.sqlite.db.SupportSQLiteOpenHelper; + +public class SupportFactory implements SupportSQLiteOpenHelper.Factory { + private final byte[] passphrase; + private final SQLiteDatabaseHook hook; + private final boolean clearPassphrase; + + public SupportFactory(byte[] passphrase) { + this(passphrase, (SQLiteDatabaseHook)null); + } + + public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook) { + this(passphrase, hook, true); + } + + public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook, + boolean clearPassphrase) { + this.passphrase = passphrase; + this.hook = hook; + this.clearPassphrase = clearPassphrase; + } + + @Override + public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) { + return new SupportHelper(configuration, passphrase, hook, clearPassphrase); + } +} diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java new file mode 100644 index 00000000..26960617 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java @@ -0,0 +1,118 @@ + /* + * Copyright (C) 2019 Mark L. Murphy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import android.database.sqlite.SQLiteException; +import androidx.sqlite.db.SupportSQLiteDatabase; +import androidx.sqlite.db.SupportSQLiteOpenHelper; + +public class SupportHelper implements SupportSQLiteOpenHelper { + private SQLiteOpenHelper standardHelper; + private byte[] passphrase; + private final boolean clearPassphrase; + + SupportHelper(final SupportSQLiteOpenHelper.Configuration configuration, + byte[] passphrase, final SQLiteDatabaseHook hook, + boolean clearPassphrase) { + SQLiteDatabase.loadLibs(configuration.context); + this.passphrase = passphrase; + this.clearPassphrase = clearPassphrase; + + standardHelper = + new SQLiteOpenHelper(configuration.context, configuration.name, + null, configuration.callback.version, hook) { + @Override + public void onCreate(SQLiteDatabase db) { + configuration.callback.onCreate(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, + int newVersion) { + configuration.callback.onUpgrade(db, oldVersion, + newVersion); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, + int newVersion) { + configuration.callback.onDowngrade(db, oldVersion, + newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + configuration.callback.onOpen(db); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + configuration.callback.onConfigure(db); + } + }; + } + + @Override + public String getDatabaseName() { + return standardHelper.getDatabaseName(); + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + standardHelper.setWriteAheadLoggingEnabled(enabled); + } + + @Override + public SupportSQLiteDatabase getWritableDatabase() { + SQLiteDatabase result; + try { + result = standardHelper.getWritableDatabase(passphrase); + } catch (SQLiteException ex){ + if(passphrase != null){ + boolean isCleared = true; + for(byte b : passphrase){ + isCleared = isCleared && (b == (byte)0); + } + if (isCleared) { + throw new IllegalStateException("The passphrase appears to be cleared. This happens by " + + "default the first time you use the factory to open a database, so we can remove the " + + "cleartext passphrase from memory. If you close the database yourself, please use a " + + "fresh SupportFactory to reopen it. If something else (e.g., Room) closed the " + + "database, and you cannot control that, use SupportFactory boolean constructor option " + + "to opt out of the automatic password clearing step. See the project README for more information.", ex); + } + } + throw ex; + } + if(clearPassphrase && passphrase != null) { + for (int i = 0; i < passphrase.length; i++) { + passphrase[i] = (byte)0; + } + } + return result; + } + + @Override + public SupportSQLiteDatabase getReadableDatabase() { + return getWritableDatabase(); + } + + @Override + public void close() { + standardHelper.close(); + } +} diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/package-info.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/package-info.java new file mode 100644 index 00000000..84c8b7be --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains the SQLCipher database managements classes that an application would use to manage its own private database. + */ +package net.sqlcipher.database; diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/package-info.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/package-info.java new file mode 100644 index 00000000..c21dbf16 --- /dev/null +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes to explore data returned from a SQLCipher database. + */ +package net.sqlcipher; diff --git a/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml b/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml new file mode 100644 index 00000000..ddedb587 --- /dev/null +++ b/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml @@ -0,0 +1,12 @@ + + + Zetetic, LLC + https://www.zetetic.net/sqlcipher/ + SQLCipher for Android + Android SQLite API based on SQLCipher + https://www.zetetic.net/sqlcipher/ + ${clientVersionNumber} + true + https://github.com/sqlcipher/android-database-sqlcipher + https://www.zetetic.net/sqlcipher/license/ + diff --git a/assets/icudt46l.zip b/assets/icudt46l.zip deleted file mode 100644 index 91dc7f71..00000000 Binary files a/assets/icudt46l.zip and /dev/null differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..0751426a --- /dev/null +++ b/build.gradle @@ -0,0 +1,99 @@ +buildscript { + repositories { + google() + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath 'com.android.tools.build:gradle:7.3.1' + classpath "gradle.plugin.org.ec4j.gradle:editorconfig-gradle-plugin:0.0.3" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +ext { + if(project.hasProperty('sqlcipherAndroidClientVersion')) { + clientVersionNumber = "${sqlcipherAndroidClientVersion}" + } else { + clientVersionNumber = "UndefinedBuildNumber" + } + mavenPackaging = "aar" + mavenGroup = "net.zetetic" + mavenArtifactId = "android-database-sqlcipher" + mavenLocalRepositoryPrefix = "file://" + if(project.hasProperty('publishLocal') && publishLocal.toBoolean()){ + mavenSnapshotRepositoryUrl = "outputs/snapshot" + mavenReleaseRepositoryUrl = "outputs/release" + } else { + mavenLocalRepositoryPrefix = "" + mavenSnapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots" + mavenReleaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" + } + if(project.hasProperty('publishSnapshot') && publishSnapshot.toBoolean()){ + mavenVersionName = "${clientVersionNumber}-SNAPSHOT" + } else { + mavenVersionName = "${clientVersionNumber}" + } + if(project.hasProperty('nexusUsername')){ + nexusUsername = "${nexusUsername}" + } + if(project.hasProperty('nexusPassword')){ + nexusPassword = "${nexusPassword}" + } + mavenPomDescription = "SQLCipher for Android is a plugin to SQLite that provides full database encryption." + mavenPomUrl = "https://www.zetetic.net/sqlcipher" + mavenScmUrl = "https://github.com/sqlcipher/android-database-sqlcipher.git" + mavenScmConnection = "scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git" + mavenScmDeveloperConnection = "scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git" + mavenLicenseUrl = "https://www.zetetic.net/sqlcipher/license/" + mavenDeveloperName = "Zetetic Support" + mavenDeveloperEmail = "support@zetetic.net" + mavenDeveloperOrganization = "Zetetic LLC" + mavenDeveloperUrl = "https://www.zetetic.net" + minimumAndroidSdkVersion = 21 + minimumAndroid64BitSdkVersion = 21 + targetAndroidSdkVersion = 26 + compileAndroidSdkVersion = 26 + mainProjectName = "android-database-sqlcipher" + nativeRootOutputDir = "${projectDir}/${mainProjectName}/src/main" + if(project.hasProperty('sqlcipherRoot')) { + sqlcipherDir = "${sqlcipherRoot}" + } + if(project.hasProperty('opensslAndroidNativeRoot') && "${opensslAndroidNativeRoot}") { + androidNativeRootDir = "${opensslAndroidNativeRoot}" + } else { + androidNativeRootDir = "${nativeRootOutputDir}/external/android-libs" + } + if(project.hasProperty('opensslRoot')) { + opensslDir = "${opensslRoot}" + } + if(project.hasProperty('debugBuild') && debugBuild.toBoolean()) { + otherSqlcipherCFlags = "-fstack-protector-all" + ndkBuildType="NDK_DEBUG=1" + } else { + otherSqlcipherCFlags = "-DLOG_NDEBUG -fstack-protector-all" + ndkBuildType="NDK_DEBUG=0" + } + if(project.hasProperty('sqlcipherCFlags') + && project.sqlcipherCFlags?.trim() + && project.sqlcipherCFlags?.contains('SQLITE_HAS_CODEC') + && project.sqlcipherCFlags?.contains('SQLITE_TEMP_STORE')) { + sqlcipherCFlags = "${sqlcipherCFlags}" + } else { + if(!project.gradle.startParameter.taskNames.toString().contains('clean')){ + throw new InvalidUserDataException("SQLCIPHER_CFLAGS environment variable must be specified and include at least '-DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2'") + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/dist/SQLCipherForAndroid-SDK/LICENSE b/dist/SQLCipherForAndroid-SDK/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/dist/SQLCipherForAndroid-SDK/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/dist/SQLCipherForAndroid-SDK/README b/dist/SQLCipherForAndroid-SDK/README deleted file mode 100644 index 8a46bd8d..00000000 --- a/dist/SQLCipherForAndroid-SDK/README +++ /dev/null @@ -1,63 +0,0 @@ - -SQLCipher for Android v1 (0.0.6-FINAL) -2011/11/29 - -CHANGELOG -- includes icu44 data file for devices without it built-in ( < 2.2) -- complete support for original Android SQLite/database package APIs -- improved platform support through unified library (less lib .so files) -- added support for CrossProcess Cursors -- now support Observers/callbacks - -CURRENT TESTED PLATFORM SUPPORT - -Android 2.1 (SDK Level 7) -Android 2.2 (SDK Level 8) -Android 2.3 (SDK Level 9) -Android 2.3.3 (SDK Level 10) -Android 3.0 (SDK Level 11) -Android 3.1 (SDK Level 12) -Android 3.2 (SDK Level 13) -Android 4.0 (Not Tested) - -HOW TO - -1) copy the libs folder into your project. If you are targeting 2.2 and below, include the 'icudt44l.zip' in the assets folder as well. - -2) Update your import path from android.database to info.guardianproject.database and you are almost there! - -3) then when you open your database, just pass a variable argument to the open database method with your password: - - SQLiteDatabase.loadLibs(this); //first init the db libraries with the context - SQLiteOpenHelper.getWritableDatabase("thisismysecret"): - -4) run your app to ensure all the libraries are being loaded correctly, and that your app runs. - -5) You will need to implement a passcode entry user interface to your application, as well. -The longer the passcode, the better. - -ABOUT - -More info and source code at: -https://guardianproject.info/code/sqlcipher/ -http://sqlcipher.net - -Or contact us: -root@guardianproject.info -#guardianproject on freenode - -EXPORT CONTROL - -This distribution includes cryptographic software. The country in which you currently reside may have restrictions -on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any -encryption software, please check your country's laws, regulations and policies concerning the import, possession, -or use, and re-export of encryption software, to see if this is permitted. See for more -information. - -The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as -Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing -cryptographic functions with asymmetric algorithms. The form and manner of this Apache Software Foundation distribution -makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception -(see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. - -More information is available here: https://guardianproject.info/home/export-information/ diff --git a/dist/SQLCipherForAndroid-SDK/SQLCIPHER_LICENSE b/dist/SQLCipherForAndroid-SDK/SQLCIPHER_LICENSE deleted file mode 100644 index 21566c58..00000000 --- a/dist/SQLCipherForAndroid-SDK/SQLCIPHER_LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -http://sqlcipher.net - - Copyright (c) 2010 Zetetic LLC - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/dist/SQLCipherForAndroid-SDK/assets/icudt44l.zip b/dist/SQLCipherForAndroid-SDK/assets/icudt44l.zip deleted file mode 100644 index 3b6372e5..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/assets/icudt44l.zip and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libdatabase_sqlcipher.so b/dist/SQLCipherForAndroid-SDK/libs/armeabi/libdatabase_sqlcipher.so deleted file mode 100755 index 1cc874d5..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libdatabase_sqlcipher.so and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libsqlcipher_android.so b/dist/SQLCipherForAndroid-SDK/libs/armeabi/libsqlcipher_android.so deleted file mode 100755 index cbdff2ff..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libsqlcipher_android.so and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libstlport_shared.so b/dist/SQLCipherForAndroid-SDK/libs/armeabi/libstlport_shared.so deleted file mode 100755 index 165ca68f..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/armeabi/libstlport_shared.so and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/commons-codec.jar b/dist/SQLCipherForAndroid-SDK/libs/commons-codec.jar deleted file mode 100644 index 957b6752..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/commons-codec.jar and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/guava-r09.jar b/dist/SQLCipherForAndroid-SDK/libs/guava-r09.jar deleted file mode 100644 index f8da8b1c..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/guava-r09.jar and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/libs/sqlcipher.jar b/dist/SQLCipherForAndroid-SDK/libs/sqlcipher.jar deleted file mode 100644 index b90c98ba..00000000 Binary files a/dist/SQLCipherForAndroid-SDK/libs/sqlcipher.jar and /dev/null differ diff --git a/dist/SQLCipherForAndroid-SDK/samples/basic/example/EventDataSQLHelper.java b/dist/SQLCipherForAndroid-SDK/samples/basic/example/EventDataSQLHelper.java deleted file mode 100644 index 9580615a..00000000 --- a/dist/SQLCipherForAndroid-SDK/samples/basic/example/EventDataSQLHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -package example; - -import info.guardianproject.database.sqlcipher.SQLiteDatabase; -import info.guardianproject.database.sqlcipher.SQLiteOpenHelper; -import android.content.Context; -import android.provider.BaseColumns; -import android.util.Log; - -/** Helper to the database, manages versions and creation */ -public class EventDataSQLHelper extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "events.db"; - private static final int DATABASE_VERSION = 1; - - // Table name - public static final String TABLE = "events"; - - // Columns - public static final String TIME = "time"; - public static final String TITLE = "title"; - - public EventDataSQLHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - String sql = "create table " + TABLE + "( " + BaseColumns._ID - + " integer primary key autoincrement, " + TIME + " integer, " - + TITLE + " text not null);"; - Log.d("EventsData", "onCreate: " + sql); - db.execSQL(sql); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion >= newVersion) - return; - - String sql = null; - if (oldVersion == 1) - sql = "alter table " + TABLE + " add note text;"; - if (oldVersion == 2) - sql = ""; - - Log.d("EventsData", "onUpgrade : " + sql); - if (sql != null) - db.execSQL(sql); - } - -} diff --git a/dist/SQLCipherForAndroid-SDK/samples/basic/example/SQLDemoActivity.java b/dist/SQLCipherForAndroid-SDK/samples/basic/example/SQLDemoActivity.java deleted file mode 100644 index 245f271b..00000000 --- a/dist/SQLCipherForAndroid-SDK/samples/basic/example/SQLDemoActivity.java +++ /dev/null @@ -1,76 +0,0 @@ -package example; - -import android.database.Cursor; -import info.guardianproject.database.sqlcipher.SQLiteDatabase; -import android.app.Activity; -import android.content.ContentValues; -import android.os.Bundle; -import android.util.Log; - -public class SQLDemoActivity extends Activity { - EventDataSQLHelper eventsData; - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - //you must set Context on SQLiteDatabase first - SQLiteDatabase.loadLibs(this); - - String password = "foo123"; - - eventsData = new EventDataSQLHelper(this); - - //then you can open the database using a password - SQLiteDatabase db = eventsData.getWritableDatabase(password); - - for (int i = 1; i < 100; i++) - addEvent("Hello Android Event: " + i, db); - - db.close(); - - db = eventsData.getReadableDatabase(password); - - Cursor cursor = getEvents(db); - showEvents(cursor); - - db.close(); - - } - - @Override - public void onDestroy() { - eventsData.close(); - } - - private void addEvent(String title, SQLiteDatabase db) { - - ContentValues values = new ContentValues(); - values.put(EventDataSQLHelper.TIME, System.currentTimeMillis()); - values.put(EventDataSQLHelper.TITLE, title); - db.insert(EventDataSQLHelper.TABLE, null, values); - } - - private Cursor getEvents(SQLiteDatabase db) { - - Cursor cursor = db.query(EventDataSQLHelper.TABLE, null, null, null, null, - null, null); - - startManagingCursor(cursor); - return cursor; - } - - private void showEvents(Cursor cursor) { - StringBuilder ret = new StringBuilder("Saved Events:\n\n"); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - long time = cursor.getLong(1); - String title = cursor.getString(2); - ret.append(id + ": " + time + ": " + title + "\n"); - } - - Log.i("sqldemo",ret.toString()); - } -} \ No newline at end of file diff --git a/dist/SQLCipherForAndroid-SDK/samples/notepadbot b/dist/SQLCipherForAndroid-SDK/samples/notepadbot deleted file mode 160000 index 28eee1b8..00000000 --- a/dist/SQLCipherForAndroid-SDK/samples/notepadbot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 28eee1b847359ce432a81ac5a675802927b953a7 diff --git a/external/Android.mk b/external/Android.mk deleted file mode 100644 index d7d25724..00000000 --- a/external/Android.mk +++ /dev/null @@ -1,388 +0,0 @@ -# -# Before building using this do: -# make -f Android.mk build-local-hack -# ndk-build -# ndk-build -# make -f Android.mk copy-libs-hack - -PROJECT_ROOT_PATH := $(call my-dir) -LOCAL_PATH := $(PROJECT_ROOT_PATH) -LOCAL_PRELINK_MODULE := false - -# how on earth to you make this damn Android build system run cmd line progs?!?! -build-local-hack: sqlcipher/sqlite3.c ../obj/local/armeabi/libcrypto.so - -sqlcipher/sqlite3.c: - cd sqlcipher && ./configure --enable-tempstore=yes CFLAGS="-DSQL_HAS_CODEC" LDFLAGS="-lcrypto" - make -C sqlcipher sqlite3.c - -# TODO include this Android.mk to integrate this into the build -../obj/local/armeabi/libcrypto.so: - cd openssl && ndk-build -j4 - mkdir -p ../obj/local/armeabi - install -p openssl/libs/armeabi/libcrypto.so openssl/libs/armeabi/libssl.so \ - ../obj/local/armeabi/ - -copy-libs-hack: build-local-hack - install -p -m644 openssl/libs/armeabi/*.so ../obj/local/armeabi/ - install -p -m644 libs/armeabi/*.so ../obj/local/armeabi/ - -project_ldflags:= -Llibs/armeabi/ -Landroid-libs/ - -#------------------------------------------------------------------------------# -# libsqlite3 - -# NOTE the following flags, -# SQLITE_TEMP_STORE=3 causes all TEMP files to go into RAM. and thats the behavior we want -# SQLITE_ENABLE_FTS3 enables usage of FTS3 - NOT FTS1 or 2. -# SQLITE_DEFAULT_AUTOVACUUM=1 causes the databases to be subject to auto-vacuum -android_sqlite_cflags := -DHAVE_USLEEP=1 -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 -DSQLITE_THREADSAFE=1 -DNDEBUG=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_DEFAULT_AUTOVACUUM=1 -DSQLITE_TEMP_STORE=3 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_BACKWARDS -DSQLITE_ENABLE_LOAD_EXTENSION - -sqlcipher_files := \ - sqlcipher/sqlite3.c - -sqlcipher_cflags := -DSQLITE_HAS_CODEC -DHAVE_FDATASYNC=0 -Dfdatasync=fsync - -include $(CLEAR_VARS) - -LOCAL_CFLAGS += $(android_sqlite_cflags) $(sqlcipher_cflags) -LOCAL_C_INCLUDES := includes openssl/include sqlcipher -LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDLIBS += -lcrypto -LOCAL_MODULE := libsqlcipher -LOCAL_SRC_FILES := $(sqlcipher_files) - -include $(BUILD_STATIC_LIBRARY) - -#------------------------------------------------------------------------------# -# libsqlcipher_android (our version of Android's libsqlite_android) - -# these are all files from various external git repos -libsqlite3_android_local_src_files := \ - android-sqlite/android/sqlite3_android.cpp \ - android-sqlite/android/PhonebookIndex.cpp \ - android-sqlite/android/PhoneNumberUtils.cpp \ - android-sqlite/android/OldPhoneNumberUtils.cpp \ - android-sqlite/android/PhoneticStringUtils.cpp \ - String16.cpp \ - String8.cpp -# android-sqlite/android/PhoneNumberUtilsTest.cpp \ -# android-sqlite/android/PhoneticStringUtilsTest.cpp \ - -include $(CLEAR_VARS) - -## this might save us linking against the private android shared libraries like -## libnativehelper.so, libutils.so, libcutils.so, libicuuc, libicui18n.so -LOCAL_ALLOW_UNDEFINED_SYMBOLS := false - -# TODO this needs to depend on libsqlcipher being built, how to do that? -#LOCAL_REQUIRED_MODULES += libsqlcipher libicui18n libicuuc -LOCAL_STATIC_LIBRARIES := libsqlcipher libicui18n libicuuc - -LOCAL_CFLAGS += $(android_sqlite_cflags) $(sqlite_cflags) -DOS_PATH_SEPARATOR="'/'" - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/includes \ - $(LOCAL_PATH)/sqlcipher \ - $(LOCAL_PATH)/icu4c/i18n \ - $(LOCAL_PATH)/icu4c/common \ - $(LOCAL_PATH)/platform-system-core/include \ - $(LOCAL_PATH)/platform-frameworks-base/include - -LOCAL_LDFLAGS += -L$(LOCAL_PATH)/android-libs/ -L$(LOCAL_PATH)/libs/armeabi/ -LOCAL_LDLIBS := -llog -lutils -lcutils -lcrypto -LOCAL_MODULE := libsqlcipher_android -LOCAL_MODULE_FILENAME := libsqlcipher_android -LOCAL_SRC_FILES := $(libsqlite3_android_local_src_files) - -include $(BUILD_SHARED_LIBRARY) - -#------------------------- -# start icu project import -#------------------------- - -#include $(LOCAL_PATH)/icu4c/Android.mk - -#LOCAL_PATH := $(call my-dir) -#include $(CLEAR_VARS) - -#include $(CLEAR_VARS) - -ICU_COMMON_PATH := icu4c/common - -# new icu common build begin - -icu_src_files := \ - $(ICU_COMMON_PATH)/cmemory.c $(ICU_COMMON_PATH)/cstring.c \ - $(ICU_COMMON_PATH)/cwchar.c $(ICU_COMMON_PATH)/locmap.c \ - $(ICU_COMMON_PATH)/punycode.c $(ICU_COMMON_PATH)/putil.c \ - $(ICU_COMMON_PATH)/uarrsort.c $(ICU_COMMON_PATH)/ubidi.c \ - $(ICU_COMMON_PATH)/ubidiln.c $(ICU_COMMON_PATH)/ubidi_props.c \ - $(ICU_COMMON_PATH)/ubidiwrt.c $(ICU_COMMON_PATH)/ucase.c \ - $(ICU_COMMON_PATH)/ucasemap.c $(ICU_COMMON_PATH)/ucat.c \ - $(ICU_COMMON_PATH)/uchar.c $(ICU_COMMON_PATH)/ucln_cmn.c \ - $(ICU_COMMON_PATH)/ucmndata.c \ - $(ICU_COMMON_PATH)/ucnv2022.c $(ICU_COMMON_PATH)/ucnv_bld.c \ - $(ICU_COMMON_PATH)/ucnvbocu.c $(ICU_COMMON_PATH)/ucnv.c \ - $(ICU_COMMON_PATH)/ucnv_cb.c $(ICU_COMMON_PATH)/ucnv_cnv.c \ - $(ICU_COMMON_PATH)/ucnvdisp.c $(ICU_COMMON_PATH)/ucnv_err.c \ - $(ICU_COMMON_PATH)/ucnv_ext.c $(ICU_COMMON_PATH)/ucnvhz.c \ - $(ICU_COMMON_PATH)/ucnv_io.c $(ICU_COMMON_PATH)/ucnvisci.c \ - $(ICU_COMMON_PATH)/ucnvlat1.c $(ICU_COMMON_PATH)/ucnv_lmb.c \ - $(ICU_COMMON_PATH)/ucnvmbcs.c $(ICU_COMMON_PATH)/ucnvscsu.c \ - $(ICU_COMMON_PATH)/ucnv_set.c $(ICU_COMMON_PATH)/ucnv_u16.c \ - $(ICU_COMMON_PATH)/ucnv_u32.c $(ICU_COMMON_PATH)/ucnv_u7.c \ - $(ICU_COMMON_PATH)/ucnv_u8.c \ - $(ICU_COMMON_PATH)/udatamem.c \ - $(ICU_COMMON_PATH)/udataswp.c $(ICU_COMMON_PATH)/uenum.c \ - $(ICU_COMMON_PATH)/uhash.c $(ICU_COMMON_PATH)/uinit.c \ - $(ICU_COMMON_PATH)/uinvchar.c $(ICU_COMMON_PATH)/uloc.c \ - $(ICU_COMMON_PATH)/umapfile.c $(ICU_COMMON_PATH)/umath.c \ - $(ICU_COMMON_PATH)/umutex.c $(ICU_COMMON_PATH)/unames.c \ - $(ICU_COMMON_PATH)/unorm_it.c $(ICU_COMMON_PATH)/uresbund.c \ - $(ICU_COMMON_PATH)/ures_cnv.c $(ICU_COMMON_PATH)/uresdata.c \ - $(ICU_COMMON_PATH)/usc_impl.c $(ICU_COMMON_PATH)/uscript.c \ - $(ICU_COMMON_PATH)/ushape.c $(ICU_COMMON_PATH)/ustrcase.c \ - $(ICU_COMMON_PATH)/ustr_cnv.c $(ICU_COMMON_PATH)/ustrfmt.c \ - $(ICU_COMMON_PATH)/ustring.c $(ICU_COMMON_PATH)/ustrtrns.c \ - $(ICU_COMMON_PATH)/ustr_wcs.c $(ICU_COMMON_PATH)/utf_impl.c \ - $(ICU_COMMON_PATH)/utrace.c $(ICU_COMMON_PATH)/utrie.c \ - $(ICU_COMMON_PATH)/utypes.c $(ICU_COMMON_PATH)/wintz.c \ - $(ICU_COMMON_PATH)/utrie2_builder.c $(ICU_COMMON_PATH)/icuplug.c \ - $(ICU_COMMON_PATH)/propsvec.c $(ICU_COMMON_PATH)/ulist.c \ - $(ICU_COMMON_PATH)/uloc_tag.c - -icu_src_files += \ - $(ICU_COMMON_PATH)/bmpset.cpp $(ICU_COMMON_PATH)/unisetspan.cpp \ - $(ICU_COMMON_PATH)/brkeng.cpp $(ICU_COMMON_PATH)/brkiter.cpp \ - $(ICU_COMMON_PATH)/caniter.cpp $(ICU_COMMON_PATH)/chariter.cpp \ - $(ICU_COMMON_PATH)/dictbe.cpp $(ICU_COMMON_PATH)/locbased.cpp \ - $(ICU_COMMON_PATH)/locid.cpp $(ICU_COMMON_PATH)/locutil.cpp \ - $(ICU_COMMON_PATH)/normlzr.cpp $(ICU_COMMON_PATH)/parsepos.cpp \ - $(ICU_COMMON_PATH)/propname.cpp $(ICU_COMMON_PATH)/rbbi.cpp \ - $(ICU_COMMON_PATH)/rbbidata.cpp $(ICU_COMMON_PATH)/rbbinode.cpp \ - $(ICU_COMMON_PATH)/rbbirb.cpp $(ICU_COMMON_PATH)/rbbiscan.cpp \ - $(ICU_COMMON_PATH)/rbbisetb.cpp $(ICU_COMMON_PATH)/rbbistbl.cpp \ - $(ICU_COMMON_PATH)/rbbitblb.cpp $(ICU_COMMON_PATH)/resbund_cnv.cpp \ - $(ICU_COMMON_PATH)/resbund.cpp $(ICU_COMMON_PATH)/ruleiter.cpp \ - $(ICU_COMMON_PATH)/schriter.cpp $(ICU_COMMON_PATH)/serv.cpp \ - $(ICU_COMMON_PATH)/servlk.cpp $(ICU_COMMON_PATH)/servlkf.cpp \ - $(ICU_COMMON_PATH)/servls.cpp $(ICU_COMMON_PATH)/servnotf.cpp \ - $(ICU_COMMON_PATH)/servrbf.cpp $(ICU_COMMON_PATH)/servslkf.cpp \ - $(ICU_COMMON_PATH)/triedict.cpp $(ICU_COMMON_PATH)/ubrk.cpp \ - $(ICU_COMMON_PATH)/uchriter.cpp $(ICU_COMMON_PATH)/uhash_us.cpp \ - $(ICU_COMMON_PATH)/uidna.cpp $(ICU_COMMON_PATH)/uiter.cpp \ - $(ICU_COMMON_PATH)/unifilt.cpp $(ICU_COMMON_PATH)/unifunct.cpp \ - $(ICU_COMMON_PATH)/uniset.cpp $(ICU_COMMON_PATH)/uniset_props.cpp \ - $(ICU_COMMON_PATH)/unistr_case.cpp $(ICU_COMMON_PATH)/unistr_cnv.cpp \ - $(ICU_COMMON_PATH)/unistr.cpp $(ICU_COMMON_PATH)/unistr_props.cpp \ - $(ICU_COMMON_PATH)/unormcmp.cpp $(ICU_COMMON_PATH)/unorm.cpp \ - $(ICU_COMMON_PATH)/uobject.cpp $(ICU_COMMON_PATH)/uset.cpp \ - $(ICU_COMMON_PATH)/usetiter.cpp $(ICU_COMMON_PATH)/uset_props.cpp \ - $(ICU_COMMON_PATH)/usprep.cpp $(ICU_COMMON_PATH)/ustack.cpp \ - $(ICU_COMMON_PATH)/ustrenum.cpp $(ICU_COMMON_PATH)/utext.cpp \ - $(ICU_COMMON_PATH)/util.cpp $(ICU_COMMON_PATH)/util_props.cpp \ - $(ICU_COMMON_PATH)/uvector.cpp $(ICU_COMMON_PATH)/uvectr32.cpp \ - $(ICU_COMMON_PATH)/errorcode.cpp \ - $(ICU_COMMON_PATH)/bytestream.cpp $(ICU_COMMON_PATH)/stringpiece.cpp \ - $(ICU_COMMON_PATH)/mutex.cpp $(ICU_COMMON_PATH)/dtintrv.cpp \ - $(ICU_COMMON_PATH)/ucnvsel.cpp $(ICU_COMMON_PATH)/uvectr64.cpp \ - $(ICU_COMMON_PATH)/locavailable.cpp $(ICU_COMMON_PATH)/locdispnames.cpp \ - $(ICU_COMMON_PATH)/loclikely.cpp $(ICU_COMMON_PATH)/locresdata.cpp \ - $(ICU_COMMON_PATH)/normalizer2impl.cpp $(ICU_COMMON_PATH)/normalizer2.cpp \ - $(ICU_COMMON_PATH)/filterednormalizer2.cpp $(ICU_COMMON_PATH)/ucol_swp.cpp \ - $(ICU_COMMON_PATH)/uprops.cpp $(ICU_COMMON_PATH)/utrie2.cpp \ - $(ICU_COMMON_PATH)/charstr.cpp $(ICU_COMMON_PATH)/uts46.cpp \ - $(ICU_COMMON_PATH)/udata.cpp - -# This is the empty compiled-in icu data structure -# that we need to satisfy the linker. -icu_src_files += $(ICU_COMMON_PATH)/../stubdata/stubdata.c - -# new icu common build end - -icu_c_includes := \ - $(ICU_COMMON_PATH)/ \ - $(ICU_COMMON_PATH)//../i18n - -# We make the ICU data directory relative to $ANDROID_ROOT on Android, so both -# device and sim builds can use the same codepath, and it's hard to break one -# without noticing because the other still works. - -icu_local_cflags += -D_REENTRANT -DU_COMMON_IMPLEMENTATION -O3 -DHAVE_ANDROID_OS=1 -fvisibility=hidden -icu_local_cflags += '-DICU_DATA_DIR_PREFIX_ENV_VAR="SQLCIPHER_ICU_PREFIX"' -icu_local_cflags += '-DICU_DATA_DIR="/icu"' -icu_local_ldlibs := -lc -lpthread -lm - -# -# Build for the target (device). -# - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(icu_src_files) -LOCAL_C_INCLUDES := $(icu_c_includes) -LOCAL_CFLAGS := $(icu_local_cflags) -DPIC -fPIC -LOCAL_RTTI_FLAG := -frtti -LOCAL_SHARED_LIBRARIES += libgabi++ -LOCAL_LDLIBS += $(icu_local_ldlibs) -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := libicuuc -include $(BUILD_STATIC_LIBRARY) - -#---------- -# end icuuc -#---------- - -#-------------- -# start icui18n -#-------------- -include $(CLEAR_VARS) -LOCAL_PATH := $(PROJECT_ROOT_PATH) -#ICU_I18N_PATH := $(LOCAL_PATH)/icu4c/i18n -ICU_I18N_PATH := icu4c/i18n - -# start new icu18n - -src_files := \ - $(ICU_I18N_PATH)/bocsu.c $(ICU_I18N_PATH)/ucln_in.c $(ICU_I18N_PATH)/decContext.c \ - $(ICU_I18N_PATH)/ulocdata.c $(ICU_I18N_PATH)/utmscale.c $(ICU_I18N_PATH)/decNumber.c - -src_files += \ - $(ICU_I18N_PATH)/indiancal.cpp $(ICU_I18N_PATH)/dtptngen.cpp $(ICU_I18N_PATH)/dtrule.cpp \ - $(ICU_I18N_PATH)/persncal.cpp $(ICU_I18N_PATH)/rbtz.cpp $(ICU_I18N_PATH)/reldtfmt.cpp \ - $(ICU_I18N_PATH)/taiwncal.cpp $(ICU_I18N_PATH)/tzrule.cpp $(ICU_I18N_PATH)/tztrans.cpp \ - $(ICU_I18N_PATH)/udatpg.cpp $(ICU_I18N_PATH)/vtzone.cpp \ - $(ICU_I18N_PATH)/anytrans.cpp $(ICU_I18N_PATH)/astro.cpp $(ICU_I18N_PATH)/buddhcal.cpp \ - $(ICU_I18N_PATH)/basictz.cpp $(ICU_I18N_PATH)/calendar.cpp $(ICU_I18N_PATH)/casetrn.cpp \ - $(ICU_I18N_PATH)/choicfmt.cpp $(ICU_I18N_PATH)/coleitr.cpp $(ICU_I18N_PATH)/coll.cpp \ - $(ICU_I18N_PATH)/cpdtrans.cpp $(ICU_I18N_PATH)/csdetect.cpp $(ICU_I18N_PATH)/csmatch.cpp \ - $(ICU_I18N_PATH)/csr2022.cpp $(ICU_I18N_PATH)/csrecog.cpp $(ICU_I18N_PATH)/csrmbcs.cpp \ - $(ICU_I18N_PATH)/csrsbcs.cpp $(ICU_I18N_PATH)/csrucode.cpp $(ICU_I18N_PATH)/csrutf8.cpp \ - $(ICU_I18N_PATH)/curramt.cpp $(ICU_I18N_PATH)/currfmt.cpp $(ICU_I18N_PATH)/currunit.cpp \ - $(ICU_I18N_PATH)/datefmt.cpp $(ICU_I18N_PATH)/dcfmtsym.cpp $(ICU_I18N_PATH)/decimfmt.cpp \ - $(ICU_I18N_PATH)/digitlst.cpp $(ICU_I18N_PATH)/dtfmtsym.cpp $(ICU_I18N_PATH)/esctrn.cpp \ - $(ICU_I18N_PATH)/fmtable_cnv.cpp $(ICU_I18N_PATH)/fmtable.cpp $(ICU_I18N_PATH)/format.cpp \ - $(ICU_I18N_PATH)/funcrepl.cpp $(ICU_I18N_PATH)/gregocal.cpp $(ICU_I18N_PATH)/gregoimp.cpp \ - $(ICU_I18N_PATH)/hebrwcal.cpp $(ICU_I18N_PATH)/inputext.cpp $(ICU_I18N_PATH)/islamcal.cpp \ - $(ICU_I18N_PATH)/japancal.cpp $(ICU_I18N_PATH)/measfmt.cpp $(ICU_I18N_PATH)/measure.cpp \ - $(ICU_I18N_PATH)/msgfmt.cpp $(ICU_I18N_PATH)/name2uni.cpp $(ICU_I18N_PATH)/nfrs.cpp \ - $(ICU_I18N_PATH)/nfrule.cpp $(ICU_I18N_PATH)/nfsubs.cpp $(ICU_I18N_PATH)/nortrans.cpp \ - $(ICU_I18N_PATH)/nultrans.cpp $(ICU_I18N_PATH)/numfmt.cpp $(ICU_I18N_PATH)/olsontz.cpp \ - $(ICU_I18N_PATH)/quant.cpp $(ICU_I18N_PATH)/rbnf.cpp $(ICU_I18N_PATH)/rbt.cpp \ - $(ICU_I18N_PATH)/rbt_data.cpp $(ICU_I18N_PATH)/rbt_pars.cpp $(ICU_I18N_PATH)/rbt_rule.cpp \ - $(ICU_I18N_PATH)/rbt_set.cpp $(ICU_I18N_PATH)/regexcmp.cpp $(ICU_I18N_PATH)/regexst.cpp \ - $(ICU_I18N_PATH)/rematch.cpp $(ICU_I18N_PATH)/remtrans.cpp $(ICU_I18N_PATH)/repattrn.cpp \ - $(ICU_I18N_PATH)/search.cpp $(ICU_I18N_PATH)/simpletz.cpp $(ICU_I18N_PATH)/smpdtfmt.cpp \ - $(ICU_I18N_PATH)/sortkey.cpp $(ICU_I18N_PATH)/strmatch.cpp $(ICU_I18N_PATH)/strrepl.cpp \ - $(ICU_I18N_PATH)/stsearch.cpp $(ICU_I18N_PATH)/tblcoll.cpp $(ICU_I18N_PATH)/timezone.cpp \ - $(ICU_I18N_PATH)/titletrn.cpp $(ICU_I18N_PATH)/tolowtrn.cpp $(ICU_I18N_PATH)/toupptrn.cpp \ - $(ICU_I18N_PATH)/translit.cpp $(ICU_I18N_PATH)/transreg.cpp $(ICU_I18N_PATH)/tridpars.cpp \ - $(ICU_I18N_PATH)/ucal.cpp $(ICU_I18N_PATH)/ucol_bld.cpp $(ICU_I18N_PATH)/ucol_cnt.cpp \ - $(ICU_I18N_PATH)/ucol.cpp $(ICU_I18N_PATH)/ucoleitr.cpp $(ICU_I18N_PATH)/ucol_elm.cpp \ - $(ICU_I18N_PATH)/ucol_res.cpp $(ICU_I18N_PATH)/ucol_sit.cpp $(ICU_I18N_PATH)/ucol_tok.cpp \ - $(ICU_I18N_PATH)/ucsdet.cpp $(ICU_I18N_PATH)/ucurr.cpp $(ICU_I18N_PATH)/udat.cpp \ - $(ICU_I18N_PATH)/umsg.cpp $(ICU_I18N_PATH)/unesctrn.cpp $(ICU_I18N_PATH)/uni2name.cpp \ - $(ICU_I18N_PATH)/unum.cpp $(ICU_I18N_PATH)/uregexc.cpp $(ICU_I18N_PATH)/uregex.cpp \ - $(ICU_I18N_PATH)/usearch.cpp $(ICU_I18N_PATH)/utrans.cpp $(ICU_I18N_PATH)/windtfmt.cpp \ - $(ICU_I18N_PATH)/winnmfmt.cpp $(ICU_I18N_PATH)/zonemeta.cpp $(ICU_I18N_PATH)/zstrfmt.cpp \ - $(ICU_I18N_PATH)/numsys.cpp $(ICU_I18N_PATH)/chnsecal.cpp \ - $(ICU_I18N_PATH)/cecal.cpp $(ICU_I18N_PATH)/coptccal.cpp $(ICU_I18N_PATH)/ethpccal.cpp \ - $(ICU_I18N_PATH)/brktrans.cpp $(ICU_I18N_PATH)/wintzimpl.cpp $(ICU_I18N_PATH)/plurrule.cpp \ - $(ICU_I18N_PATH)/plurfmt.cpp $(ICU_I18N_PATH)/dtitvfmt.cpp $(ICU_I18N_PATH)/dtitvinf.cpp \ - $(ICU_I18N_PATH)/tmunit.cpp $(ICU_I18N_PATH)/tmutamt.cpp $(ICU_I18N_PATH)/tmutfmt.cpp \ - $(ICU_I18N_PATH)/colldata.cpp $(ICU_I18N_PATH)/bmsearch.cpp $(ICU_I18N_PATH)/bms.cpp \ - $(ICU_I18N_PATH)/currpinf.cpp $(ICU_I18N_PATH)/uspoof.cpp $(ICU_I18N_PATH)/uspoof_impl.cpp \ - $(ICU_I18N_PATH)/uspoof_build.cpp \ - $(ICU_I18N_PATH)/regextxt.cpp $(ICU_I18N_PATH)/selfmt.cpp $(ICU_I18N_PATH)/uspoof_conf.cpp \ - $(ICU_I18N_PATH)/uspoof_wsconf.cpp $(ICU_I18N_PATH)/ztrans.cpp $(ICU_I18N_PATH)/zrule.cpp \ - $(ICU_I18N_PATH)/vzone.cpp $(ICU_I18N_PATH)/fphdlimp.cpp $(ICU_I18N_PATH)/fpositer.cpp\ - $(ICU_I18N_PATH)/locdspnm.cpp $(ICU_I18N_PATH)/decnumstr.cpp $(ICU_I18N_PATH)/ucol_wgt.cpp - -# end new icu18n - -c_includes = \ - $(ICU_I18N_PATH)/ \ - $(ICU_I18N_PATH)/../common - -# -# Build for the target (device). -# - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(src_files) -LOCAL_C_INCLUDES := $(c_includes) \ - abi/cpp/include -LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_I18N_IMPLEMENTATION -fPIC -fvisibility=hidden -LOCAL_CFLAGS += -O3 -LOCAL_RTTI_FLAG := -frtti -LOCAL_SHARED_LIBRARIES += libgabi++ -LOCAL_STATIC_LIBRARIES += libicuuc -LOCAL_LDLIBS += -lc -lpthread -lm -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := libicui18n - -include $(BUILD_STATIC_LIBRARY) - -#------------ -# end icui18n -#------------ - -#--------------- -# start stubdata -#--------------- - -# Build configuration: -# -# "Large" includes all the supported locales. -# Japanese includes US and Japan. -# US-Euro is needed for IT or PL builds -# Default is suitable for CS, DE, EN, ES, FR, NL -# US has only EN and ES - -config := $(word 1, \ - $(if $(findstring ar,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring da,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring el,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring fi,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring he,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring hr,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring hu,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring id,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring ko,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring nb,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring pt,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring ro,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring ru,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring sk,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring sr,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring sv,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring th,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring tr,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring uk,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring zh,$(PRODUCT_LOCALES)),large) \ - $(if $(findstring ja,$(PRODUCT_LOCALES)),us-japan) \ - $(if $(findstring it,$(PRODUCT_LOCALES)),us-euro) \ - $(if $(findstring pl,$(PRODUCT_LOCALES)),us-euro) \ - $(if $(findstring cs,$(PRODUCT_LOCALES)),default) \ - $(if $(findstring de,$(PRODUCT_LOCALES)),default) \ - $(if $(findstring fr,$(PRODUCT_LOCALES)),default) \ - $(if $(findstring nl,$(PRODUCT_LOCALES)),default) \ - us) - -#include $(LOCAL_PATH)/root.mk -# derive a string like 'icudt44l' from a local file like 'external/icu4c/stubdata/icudt44l-all.dat' -stubdata_path:= $(PROJECT_ROOT_PATH)/icu4c/stubdata -root_dat_path := $(wildcard $(stubdata_path)/*-all.dat) -root := $(patsubst $(stubdata_path)/%,%,$(patsubst %-all.dat,%,$(root_dat_path))) - - -PRODUCT_COPY_FILES += $(LOCAL_PATH)/$(root)-$(config).dat:/system/usr/icu/$(root).dat - -ifeq ($(WITH_HOST_DALVIK),true) - $(eval $(call copy-one-file,$(LOCAL_PATH)/$(root)-$(config).dat,$(HOST_OUT)/usr/icu/$(root).dat)) -endif - -#------------- -# end stubdata -#------------- diff --git a/external/String16.cpp b/external/String16.cpp deleted file mode 100644 index 38baf531..00000000 --- a/external/String16.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include -#include -#include -#include - -#include - -#ifdef HAVE_WINSOCK -# undef nhtol -# undef htonl -# undef nhtos -# undef htons - -# ifdef HAVE_LITTLE_ENDIAN -# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) -# define htonl(x) ntohl(x) -# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) ) -# define htons(x) ntohs(x) -# else -# define ntohl(x) (x) -# define htonl(x) (x) -# define ntohs(x) (x) -# define htons(x) (x) -# endif -#else -//# include -# include -#endif - -#include -#include -#include - -// --------------------------------------------------------------------------- - -int strcmp16(const char16_t *s1, const char16_t *s2) -{ - char16_t ch; - int d = 0; - - while ( 1 ) { - d = (int)(ch = *s1++) - (int)*s2++; - if ( d || !ch ) - break; - } - - return d; -} - -int strncmp16(const char16_t *s1, const char16_t *s2, size_t n) -{ - char16_t ch; - int d = 0; - - while ( n-- ) { - d = (int)(ch = *s1++) - (int)*s2++; - if ( d || !ch ) - break; - } - - return d; -} - -char16_t *strcpy16(char16_t *dst, const char16_t *src) -{ - char16_t *q = dst; - const char16_t *p = src; - char16_t ch; - - do { - *q++ = ch = *p++; - } while ( ch ); - - return dst; -} - -size_t strlen16(const char16_t *s) -{ - const char16_t *ss = s; - while ( *ss ) - ss++; - return ss-s; -} - - -char16_t *strncpy16(char16_t *dst, const char16_t *src, size_t n) -{ - char16_t *q = dst; - const char16_t *p = src; - char ch; - - while (n) { - n--; - *q++ = ch = *p++; - if ( !ch ) - break; - } - - *q = 0; - - return dst; -} - -size_t strnlen16(const char16_t *s, size_t maxlen) -{ - const char16_t *ss = s; - - /* Important: the maxlen test must precede the reference through ss; - since the byte beyond the maximum may segfault */ - while ((maxlen > 0) && *ss) { - ss++; - maxlen--; - } - return ss-s; -} - -int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2) -{ - const char16_t* e1 = s1+n1; - const char16_t* e2 = s2+n2; - - while (s1 < e1 && s2 < e2) { - const int d = (int)*s1++ - (int)*s2++; - if (d) { - return d; - } - } - - return n1 < n2 - ? (0 - (int)*s2) - : (n1 > n2 - ? ((int)*s1 - 0) - : 0); -} - -int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2) -{ - const char16_t* e1 = s1H+n1; - const char16_t* e2 = s2N+n2; - - while (s1H < e1 && s2N < e2) { - const char16_t c2 = ntohs(*s2N); - const int d = (int)*s1H++ - (int)c2; - s2N++; - if (d) { - return d; - } - } - - return n1 < n2 - ? (0 - (int)ntohs(*s2N)) - : (n1 > n2 - ? ((int)*s1H - 0) - : 0); -} - -static inline size_t -utf8_char_len(uint8_t ch) -{ - return ((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1; -} - -#define UTF8_SHIFT_AND_MASK(unicode, byte) (unicode)<<=6; (unicode) |= (0x3f & (byte)); - -static inline uint32_t -utf8_to_utf32(const uint8_t *src, size_t length) -{ - uint32_t unicode; - - switch (length) - { - case 1: - return src[0]; - case 2: - unicode = src[0] & 0x1f; - UTF8_SHIFT_AND_MASK(unicode, src[1]) - return unicode; - case 3: - unicode = src[0] & 0x0f; - UTF8_SHIFT_AND_MASK(unicode, src[1]) - UTF8_SHIFT_AND_MASK(unicode, src[2]) - return unicode; - case 4: - unicode = src[0] & 0x07; - UTF8_SHIFT_AND_MASK(unicode, src[1]) - UTF8_SHIFT_AND_MASK(unicode, src[2]) - UTF8_SHIFT_AND_MASK(unicode, src[3]) - return unicode; - default: - return 0xffff; - } - - //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result); -} - -void -utf8_to_utf16(const uint8_t *src, size_t srcLen, - char16_t* dst, const size_t dstLen) -{ - const uint8_t* const end = src + srcLen; - const char16_t* const dstEnd = dst + dstLen; - while (src < end && dst < dstEnd) { - size_t len = utf8_char_len(*src); - uint32_t codepoint = utf8_to_utf32((const uint8_t*)src, len); - - // Convert the UTF32 codepoint to one or more UTF16 codepoints - if (codepoint <= 0xFFFF) { - // Single UTF16 character - *dst++ = (char16_t) codepoint; - } else { - // Multiple UTF16 characters with surrogates - codepoint = codepoint - 0x10000; - *dst++ = (char16_t) ((codepoint >> 10) + 0xD800); - *dst++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00); - } - - src += len; - } - if (dst < dstEnd) { - *dst = 0; - } -} - -// --------------------------------------------------------------------------- - -namespace android { - -static SharedBuffer* gEmptyStringBuf = NULL; -static char16_t* gEmptyString = NULL; - -static inline char16_t* getEmptyString() -{ - gEmptyStringBuf->acquire(); - return gEmptyString; -} - -void initialize_string16() -{ - SharedBuffer* buf = SharedBuffer::alloc(sizeof(char16_t)); - char16_t* str = (char16_t*)buf->data(); - *str = 0; - gEmptyStringBuf = buf; - gEmptyString = str; -} - -void terminate_string16() -{ - SharedBuffer::bufferFromData(gEmptyString)->release(); - gEmptyStringBuf = NULL; - gEmptyString = NULL; -} - -// --------------------------------------------------------------------------- - -static char16_t* allocFromUTF8(const char* in, size_t len) -{ - if (len == 0) return getEmptyString(); - - size_t chars = 0; - const char* end = in+len; - const char* p = in; - - while (p < end) { - chars++; - int utf8len = utf8_char_len(*p); - uint32_t codepoint = utf8_to_utf32((const uint8_t*)p, utf8len); - if (codepoint > 0xFFFF) chars++; // this will be a surrogate pair in utf16 - p += utf8len; - } - - size_t bufSize = (chars+1)*sizeof(char16_t); - SharedBuffer* buf = SharedBuffer::alloc(bufSize); - if (buf) { - p = in; - char16_t* str = (char16_t*)buf->data(); - - utf8_to_utf16((const uint8_t*)p, len, str, bufSize); - - //printf("Created UTF-16 string from UTF-8 \"%s\":", in); - //printHexData(1, str, buf->size(), 16, 1); - //printf("\n"); - - return str; - } - - return getEmptyString(); -} - -// --------------------------------------------------------------------------- - -String16::String16() - : mString(getEmptyString()) -{ -} - -String16::String16(const String16& o) - : mString(o.mString) -{ - SharedBuffer::bufferFromData(mString)->acquire(); -} - -String16::String16(const String16& o, size_t len, size_t begin) - : mString(getEmptyString()) -{ - setTo(o, len, begin); -} - -String16::String16(const char16_t* o) -{ - size_t len = strlen16(o); - SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); - LOG_ASSERT(buf, "Unable to allocate shared buffer"); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - strcpy16(str, o); - mString = str; - return; - } - - mString = getEmptyString(); -} - -String16::String16(const char16_t* o, size_t len) -{ - SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); - LOG_ASSERT(buf, "Unable to allocate shared buffer"); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - memcpy(str, o, len*sizeof(char16_t)); - str[len] = 0; - mString = str; - return; - } - - mString = getEmptyString(); -} - -String16::String16(const String8& o) - : mString(allocFromUTF8(o.string(), o.size())) -{ -} - -String16::String16(const char* o) - : mString(allocFromUTF8(o, strlen(o))) -{ -} - -String16::String16(const char* o, size_t len) - : mString(allocFromUTF8(o, len)) -{ -} - -String16::~String16() -{ - SharedBuffer::bufferFromData(mString)->release(); -} - -void String16::setTo(const String16& other) -{ - SharedBuffer::bufferFromData(other.mString)->acquire(); - SharedBuffer::bufferFromData(mString)->release(); - mString = other.mString; -} - -status_t String16::setTo(const String16& other, size_t len, size_t begin) -{ - const size_t N = other.size(); - if (begin >= N) { - SharedBuffer::bufferFromData(mString)->release(); - mString = getEmptyString(); - return NO_ERROR; - } - if ((begin+len) > N) len = N-begin; - if (begin == 0 && len == N) { - setTo(other); - return NO_ERROR; - } - - if (&other == this) { - LOG_ALWAYS_FATAL("Not implemented"); - } - - return setTo(other.string()+begin, len); -} - -status_t String16::setTo(const char16_t* other) -{ - return setTo(other, strlen16(other)); -} - -status_t String16::setTo(const char16_t* other, size_t len) -{ - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((len+1)*sizeof(char16_t)); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - memmove(str, other, len*sizeof(char16_t)); - str[len] = 0; - mString = str; - return NO_ERROR; - } - return NO_MEMORY; -} - -status_t String16::append(const String16& other) -{ - const size_t myLen = size(); - const size_t otherLen = other.size(); - if (myLen == 0) { - setTo(other); - return NO_ERROR; - } else if (otherLen == 0) { - return NO_ERROR; - } - - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((myLen+otherLen+1)*sizeof(char16_t)); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t)); - mString = str; - return NO_ERROR; - } - return NO_MEMORY; -} - -status_t String16::append(const char16_t* chrs, size_t otherLen) -{ - const size_t myLen = size(); - if (myLen == 0) { - setTo(chrs, otherLen); - return NO_ERROR; - } else if (otherLen == 0) { - return NO_ERROR; - } - - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((myLen+otherLen+1)*sizeof(char16_t)); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - memcpy(str+myLen, chrs, otherLen*sizeof(char16_t)); - str[myLen+otherLen] = 0; - mString = str; - return NO_ERROR; - } - return NO_MEMORY; -} - -status_t String16::insert(size_t pos, const char16_t* chrs) -{ - return insert(pos, chrs, strlen16(chrs)); -} - -status_t String16::insert(size_t pos, const char16_t* chrs, size_t len) -{ - const size_t myLen = size(); - if (myLen == 0) { - return setTo(chrs, len); - return NO_ERROR; - } else if (len == 0) { - return NO_ERROR; - } - - if (pos > myLen) pos = myLen; - - #if 0 - printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n", - String8(*this).string(), pos, - len, myLen, String8(chrs, len).string()); - #endif - - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((myLen+len+1)*sizeof(char16_t)); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - if (pos < myLen) { - memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t)); - } - memcpy(str+pos, chrs, len*sizeof(char16_t)); - str[myLen+len] = 0; - mString = str; - #if 0 - printf("Result (%d chrs): %s\n", size(), String8(*this).string()); - #endif - return NO_ERROR; - } - return NO_MEMORY; -} - -ssize_t String16::findFirst(char16_t c) const -{ - const char16_t* str = string(); - const char16_t* p = str; - const char16_t* e = p + size(); - while (p < e) { - if (*p == c) { - return p-str; - } - p++; - } - return -1; -} - -ssize_t String16::findLast(char16_t c) const -{ - const char16_t* str = string(); - const char16_t* p = str; - const char16_t* e = p + size(); - while (p < e) { - e--; - if (*e == c) { - return e-str; - } - } - return -1; -} - -bool String16::startsWith(const String16& prefix) const -{ - const size_t ps = prefix.size(); - if (ps > size()) return false; - return strzcmp16(mString, ps, prefix.string(), ps) == 0; -} - -bool String16::startsWith(const char16_t* prefix) const -{ - const size_t ps = strlen16(prefix); - if (ps > size()) return false; - return strncmp16(mString, prefix, ps) == 0; -} - -status_t String16::makeLower() -{ - const size_t N = size(); - const char16_t* str = string(); - char16_t* edit = NULL; - for (size_t i=0; i= 'A' && v <= 'Z') { - if (!edit) { - SharedBuffer* buf = SharedBuffer::bufferFromData(mString)->edit(); - if (!buf) { - return NO_MEMORY; - } - edit = (char16_t*)buf->data(); - mString = str = edit; - } - edit[i] = tolower((char)v); - } - } - return NO_ERROR; -} - -status_t String16::replaceAll(char16_t replaceThis, char16_t withThis) -{ - const size_t N = size(); - const char16_t* str = string(); - char16_t* edit = NULL; - for (size_t i=0; iedit(); - if (!buf) { - return NO_MEMORY; - } - edit = (char16_t*)buf->data(); - mString = str = edit; - } - edit[i] = withThis; - } - } - return NO_ERROR; -} - -status_t String16::remove(size_t len, size_t begin) -{ - const size_t N = size(); - if (begin >= N) { - SharedBuffer::bufferFromData(mString)->release(); - mString = getEmptyString(); - return NO_ERROR; - } - if ((begin+len) > N) len = N-begin; - if (begin == 0 && len == N) { - return NO_ERROR; - } - - if (begin > 0) { - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((N+1)*sizeof(char16_t)); - if (!buf) { - return NO_MEMORY; - } - char16_t* str = (char16_t*)buf->data(); - memmove(str, str+begin, (N-begin+1)*sizeof(char16_t)); - mString = str; - } - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize((len+1)*sizeof(char16_t)); - if (buf) { - char16_t* str = (char16_t*)buf->data(); - str[len] = 0; - mString = str; - return NO_ERROR; - } - return NO_MEMORY; -} - -TextOutput& operator<<(TextOutput& to, const String16& val) -{ - to << String8(val).string(); - return to; -} - -}; // namespace android diff --git a/external/String8.cpp b/external/String8.cpp deleted file mode 100644 index 1c4f80c1..00000000 --- a/external/String8.cpp +++ /dev/null @@ -1,940 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include -#include -#include - -#include - -#include - -/* - * Functions outside android is below the namespace android, since they use - * functions and constants in android namespace. - */ - -// --------------------------------------------------------------------------- - -namespace android { - -static const char32_t kByteMask = 0x000000BF; -static const char32_t kByteMark = 0x00000080; - -// Surrogates aren't valid for UTF-32 characters, so define some -// constants that will let us screen them out. -static const char32_t kUnicodeSurrogateHighStart = 0x0000D800; -static const char32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; -static const char32_t kUnicodeSurrogateLowStart = 0x0000DC00; -static const char32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; -static const char32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; -static const char32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; -static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF; - -// Mask used to set appropriate bits in first byte of UTF-8 sequence, -// indexed by number of bytes in the sequence. -// 0xxxxxxx -// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000 -// 110yyyyx 10xxxxxx -// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0 -// 1110yyyy 10yxxxxx 10xxxxxx -// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0 -// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx -// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0 -static const char32_t kFirstByteMark[] = { - 0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0 -}; - -// Separator used by resource paths. This is not platform dependent contrary -// to OS_PATH_SEPARATOR. -#define RES_PATH_SEPARATOR '/' - -// Return number of utf8 bytes required for the character. -static size_t utf32_to_utf8_bytes(char32_t srcChar) -{ - size_t bytesToWrite; - - // Figure out how many bytes the result will require. - if (srcChar < 0x00000080) - { - bytesToWrite = 1; - } - else if (srcChar < 0x00000800) - { - bytesToWrite = 2; - } - else if (srcChar < 0x00010000) - { - if ((srcChar < kUnicodeSurrogateStart) - || (srcChar > kUnicodeSurrogateEnd)) - { - bytesToWrite = 3; - } - else - { - // Surrogates are invalid UTF-32 characters. - return 0; - } - } - // Max code point for Unicode is 0x0010FFFF. - else if (srcChar <= kUnicodeMaxCodepoint) - { - bytesToWrite = 4; - } - else - { - // Invalid UTF-32 character. - return 0; - } - - return bytesToWrite; -} - -// Write out the source character to . - -static void utf32_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes) -{ - dstP += bytes; - switch (bytes) - { /* note: everything falls through. */ - case 4: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; - case 3: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; - case 2: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; - case 1: *--dstP = (uint8_t)(srcChar | kFirstByteMark[bytes]); - } -} - -// --------------------------------------------------------------------------- - -static SharedBuffer* gEmptyStringBuf = NULL; -static char* gEmptyString = NULL; - -extern int gDarwinCantLoadAllObjects; -int gDarwinIsReallyAnnoying; - -static inline char* getEmptyString() -{ - gEmptyStringBuf->acquire(); - return gEmptyString; -} - -void initialize_string8() -{ - // HACK: This dummy dependency forces linking libutils Static.cpp, - // which is needed to initialize String8/String16 classes. - // These variables are named for Darwin, but are needed elsewhere too, - // including static linking on any platform. - gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects; - - SharedBuffer* buf = SharedBuffer::alloc(1); - char* str = (char*)buf->data(); - *str = 0; - gEmptyStringBuf = buf; - gEmptyString = str; -} - -void terminate_string8() -{ - SharedBuffer::bufferFromData(gEmptyString)->release(); - gEmptyStringBuf = NULL; - gEmptyString = NULL; -} - -// --------------------------------------------------------------------------- - -static char* allocFromUTF8(const char* in, size_t len) -{ - if (len > 0) { - SharedBuffer* buf = SharedBuffer::alloc(len+1); - LOG_ASSERT(buf, "Unable to allocate shared buffer"); - if (buf) { - char* str = (char*)buf->data(); - memcpy(str, in, len); - str[len] = 0; - return str; - } - return NULL; - } - - return getEmptyString(); -} - -template -static char* allocFromUTF16OrUTF32(const T* in, L len) -{ - if (len == 0) return getEmptyString(); - - size_t bytes = 0; - const T* end = in+len; - const T* p = in; - - while (p < end) { - bytes += utf32_to_utf8_bytes(*p); - p++; - } - - SharedBuffer* buf = SharedBuffer::alloc(bytes+1); - LOG_ASSERT(buf, "Unable to allocate shared buffer"); - if (buf) { - p = in; - char* str = (char*)buf->data(); - char* d = str; - while (p < end) { - const T c = *p++; - size_t len = utf32_to_utf8_bytes(c); - utf32_to_utf8((uint8_t*)d, c, len); - d += len; - } - *d = 0; - - return str; - } - - return getEmptyString(); -} - -static char* allocFromUTF16(const char16_t* in, size_t len) -{ - if (len == 0) return getEmptyString(); - - const size_t bytes = utf8_length_from_utf16(in, len); - - SharedBuffer* buf = SharedBuffer::alloc(bytes+1); - LOG_ASSERT(buf, "Unable to allocate shared buffer"); - if (buf) { - char* str = (char*)buf->data(); - - utf16_to_utf8(in, len, str, bytes+1); - - return str; - } - - return getEmptyString(); -} - -static char* allocFromUTF32(const char32_t* in, size_t len) -{ - return allocFromUTF16OrUTF32(in, len); -} - -// --------------------------------------------------------------------------- - -String8::String8() - : mString(getEmptyString()) -{ -} - -String8::String8(const String8& o) - : mString(o.mString) -{ - SharedBuffer::bufferFromData(mString)->acquire(); -} - -String8::String8(const char* o) - : mString(allocFromUTF8(o, strlen(o))) -{ - if (mString == NULL) { - mString = getEmptyString(); - } -} - -String8::String8(const char* o, size_t len) - : mString(allocFromUTF8(o, len)) -{ - if (mString == NULL) { - mString = getEmptyString(); - } -} - -String8::String8(const String16& o) - : mString(allocFromUTF16(o.string(), o.size())) -{ -} - -String8::String8(const char16_t* o) - : mString(allocFromUTF16(o, strlen16(o))) -{ -} - -String8::String8(const char16_t* o, size_t len) - : mString(allocFromUTF16(o, len)) -{ -} - -String8::String8(const char32_t* o) - : mString(allocFromUTF32(o, strlen32(o))) -{ -} - -String8::String8(const char32_t* o, size_t len) - : mString(allocFromUTF32(o, len)) -{ -} - -String8::~String8() -{ - SharedBuffer::bufferFromData(mString)->release(); -} - -void String8::setTo(const String8& other) -{ - SharedBuffer::bufferFromData(other.mString)->acquire(); - SharedBuffer::bufferFromData(mString)->release(); - mString = other.mString; -} - -status_t String8::setTo(const char* other) -{ - const char *newString = allocFromUTF8(other, strlen(other)); - SharedBuffer::bufferFromData(mString)->release(); - mString = newString; - if (mString) return NO_ERROR; - - mString = getEmptyString(); - return NO_MEMORY; -} - -status_t String8::setTo(const char* other, size_t len) -{ - const char *newString = allocFromUTF8(other, len); - SharedBuffer::bufferFromData(mString)->release(); - mString = newString; - if (mString) return NO_ERROR; - - mString = getEmptyString(); - return NO_MEMORY; -} - -status_t String8::setTo(const char16_t* other, size_t len) -{ - const char *newString = allocFromUTF16(other, len); - SharedBuffer::bufferFromData(mString)->release(); - mString = newString; - if (mString) return NO_ERROR; - - mString = getEmptyString(); - return NO_MEMORY; -} - -status_t String8::setTo(const char32_t* other, size_t len) -{ - const char *newString = allocFromUTF32(other, len); - SharedBuffer::bufferFromData(mString)->release(); - mString = newString; - if (mString) return NO_ERROR; - - mString = getEmptyString(); - return NO_MEMORY; -} - -status_t String8::append(const String8& other) -{ - const size_t otherLen = other.bytes(); - if (bytes() == 0) { - setTo(other); - return NO_ERROR; - } else if (otherLen == 0) { - return NO_ERROR; - } - - return real_append(other.string(), otherLen); -} - -status_t String8::append(const char* other) -{ - return append(other, strlen(other)); -} - -status_t String8::append(const char* other, size_t otherLen) -{ - if (bytes() == 0) { - return setTo(other, otherLen); - } else if (otherLen == 0) { - return NO_ERROR; - } - - return real_append(other, otherLen); -} - -status_t String8::appendFormat(const char* fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - - int result = NO_ERROR; - int n = vsnprintf(NULL, 0, fmt, ap); - if (n != 0) { - size_t oldLength = length(); - char* buf = lockBuffer(oldLength + n); - if (buf) { - vsnprintf(buf + oldLength, n + 1, fmt, ap); - } else { - result = NO_MEMORY; - } - } - - va_end(ap); - return result; -} - -status_t String8::real_append(const char* other, size_t otherLen) -{ - const size_t myLen = bytes(); - - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize(myLen+otherLen+1); - if (buf) { - char* str = (char*)buf->data(); - mString = str; - str += myLen; - memcpy(str, other, otherLen); - str[otherLen] = '\0'; - return NO_ERROR; - } - return NO_MEMORY; -} - -char* String8::lockBuffer(size_t size) -{ - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize(size+1); - if (buf) { - char* str = (char*)buf->data(); - mString = str; - return str; - } - return NULL; -} - -void String8::unlockBuffer() -{ - unlockBuffer(strlen(mString)); -} - -status_t String8::unlockBuffer(size_t size) -{ - if (size != this->size()) { - SharedBuffer* buf = SharedBuffer::bufferFromData(mString) - ->editResize(size+1); - if (! buf) { - return NO_MEMORY; - } - - char* str = (char*)buf->data(); - str[size] = 0; - mString = str; - } - - return NO_ERROR; -} - -ssize_t String8::find(const char* other, size_t start) const -{ - size_t len = size(); - if (start >= len) { - return -1; - } - const char* s = mString+start; - const char* p = strstr(s, other); - return p ? p-mString : -1; -} - -void String8::toLower() -{ - toLower(0, size()); -} - -void String8::toLower(size_t start, size_t length) -{ - const size_t len = size(); - if (start >= len) { - return; - } - if (start+length > len) { - length = len-start; - } - char* buf = lockBuffer(len); - buf += start; - while (length > 0) { - *buf = tolower(*buf); - buf++; - length--; - } - unlockBuffer(len); -} - -void String8::toUpper() -{ - toUpper(0, size()); -} - -void String8::toUpper(size_t start, size_t length) -{ - const size_t len = size(); - if (start >= len) { - return; - } - if (start+length > len) { - length = len-start; - } - char* buf = lockBuffer(len); - buf += start; - while (length > 0) { - *buf = toupper(*buf); - buf++; - length--; - } - unlockBuffer(len); -} - -size_t String8::getUtf32Length() const -{ - return utf32_length(mString, length()); -} - -int32_t String8::getUtf32At(size_t index, size_t *next_index) const -{ - return utf32_at(mString, length(), index, next_index); -} - -size_t String8::getUtf32(char32_t* dst, size_t dst_len) const -{ - return utf8_to_utf32(mString, length(), dst, dst_len); -} - -TextOutput& operator<<(TextOutput& to, const String8& val) -{ - to << val.string(); - return to; -} - -// --------------------------------------------------------------------------- -// Path functions - -void String8::setPathName(const char* name) -{ - setPathName(name, strlen(name)); -} - -void String8::setPathName(const char* name, size_t len) -{ - char* buf = lockBuffer(len); - - memcpy(buf, name, len); - - // remove trailing path separator, if present - if (len > 0 && buf[len-1] == OS_PATH_SEPARATOR) - len--; - - buf[len] = '\0'; - - unlockBuffer(len); -} - -String8 String8::getPathLeaf(void) const -{ - const char* cp; - const char*const buf = mString; - - cp = strrchr(buf, OS_PATH_SEPARATOR); - if (cp == NULL) - return String8(*this); - else - return String8(cp+1); -} - -String8 String8::getPathDir(void) const -{ - const char* cp; - const char*const str = mString; - - cp = strrchr(str, OS_PATH_SEPARATOR); - if (cp == NULL) - return String8(""); - else - return String8(str, cp - str); -} - -String8 String8::walkPath(String8* outRemains) const -{ - const char* cp; - const char*const str = mString; - const char* buf = str; - - cp = strchr(buf, OS_PATH_SEPARATOR); - if (cp == buf) { - // don't include a leading '/'. - buf = buf+1; - cp = strchr(buf, OS_PATH_SEPARATOR); - } - - if (cp == NULL) { - String8 res = buf != str ? String8(buf) : *this; - if (outRemains) *outRemains = String8(""); - return res; - } - - String8 res(buf, cp-buf); - if (outRemains) *outRemains = String8(cp+1); - return res; -} - -/* - * Helper function for finding the start of an extension in a pathname. - * - * Returns a pointer inside mString, or NULL if no extension was found. - */ -char* String8::find_extension(void) const -{ - const char* lastSlash; - const char* lastDot; - int extLen; - const char* const str = mString; - - // only look at the filename - lastSlash = strrchr(str, OS_PATH_SEPARATOR); - if (lastSlash == NULL) - lastSlash = str; - else - lastSlash++; - - // find the last dot - lastDot = strrchr(lastSlash, '.'); - if (lastDot == NULL) - return NULL; - - // looks good, ship it - return const_cast(lastDot); -} - -String8 String8::getPathExtension(void) const -{ - char* ext; - - ext = find_extension(); - if (ext != NULL) - return String8(ext); - else - return String8(""); -} - -String8 String8::getBasePath(void) const -{ - char* ext; - const char* const str = mString; - - ext = find_extension(); - if (ext == NULL) - return String8(*this); - else - return String8(str, ext - str); -} - -String8& String8::appendPath(const char* name) -{ - // TODO: The test below will fail for Win32 paths. Fix later or ignore. - if (name[0] != OS_PATH_SEPARATOR) { - if (*name == '\0') { - // nothing to do - return *this; - } - - size_t len = length(); - if (len == 0) { - // no existing filename, just use the new one - setPathName(name); - return *this; - } - - // make room for oldPath + '/' + newPath - int newlen = strlen(name); - - char* buf = lockBuffer(len+1+newlen); - - // insert a '/' if needed - if (buf[len-1] != OS_PATH_SEPARATOR) - buf[len++] = OS_PATH_SEPARATOR; - - memcpy(buf+len, name, newlen+1); - len += newlen; - - unlockBuffer(len); - - return *this; - } else { - setPathName(name); - return *this; - } -} - -String8& String8::convertToResPath() -{ -#if OS_PATH_SEPARATOR != RES_PATH_SEPARATOR - size_t len = length(); - if (len > 0) { - char * buf = lockBuffer(len); - for (char * end = buf + len; buf < end; ++buf) { - if (*buf == OS_PATH_SEPARATOR) - *buf = RES_PATH_SEPARATOR; - } - unlockBuffer(len); - } -#endif - return *this; -} - -}; // namespace android - -// --------------------------------------------------------------------------- - -size_t strlen32(const char32_t *s) -{ - const char32_t *ss = s; - while ( *ss ) - ss++; - return ss-s; -} - -size_t strnlen32(const char32_t *s, size_t maxlen) -{ - const char32_t *ss = s; - while ((maxlen > 0) && *ss) { - ss++; - maxlen--; - } - return ss-s; -} - -size_t utf8_length(const char *src) -{ - const char *cur = src; - size_t ret = 0; - while (*cur != '\0') { - const char first_char = *cur++; - if ((first_char & 0x80) == 0) { // ASCII - ret += 1; - continue; - } - // (UTF-8's character must not be like 10xxxxxx, - // but 110xxxxx, 1110xxxx, ... or 1111110x) - if ((first_char & 0x40) == 0) { - return 0; - } - - int32_t mask, to_ignore_mask; - size_t num_to_read = 0; - char32_t utf32 = 0; - for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80; - num_to_read < 5 && (first_char & mask); - num_to_read++, to_ignore_mask |= mask, mask >>= 1) { - if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx - return 0; - } - // 0x3F == 00111111 - utf32 = (utf32 << 6) + (*cur++ & 0x3F); - } - // "first_char" must be (110xxxxx - 11110xxx) - if (num_to_read == 5) { - return 0; - } - to_ignore_mask |= mask; - utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1)); - if (utf32 > android::kUnicodeMaxCodepoint) { - return 0; - } - - ret += num_to_read; - } - return ret; -} - -size_t utf32_length(const char *src, size_t src_len) -{ - if (src == NULL || src_len == 0) { - return 0; - } - size_t ret = 0; - const char* cur; - const char* end; - size_t num_to_skip; - for (cur = src, end = src + src_len, num_to_skip = 1; - cur < end; - cur += num_to_skip, ret++) { - const char first_char = *cur; - num_to_skip = 1; - if ((first_char & 0x80) == 0) { // ASCII - continue; - } - int32_t mask; - - for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) { - } - } - return ret; -} - -size_t utf8_length_from_utf32(const char32_t *src, size_t src_len) -{ - if (src == NULL || src_len == 0) { - return 0; - } - size_t ret = 0; - const char32_t *end = src + src_len; - while (src < end) { - ret += android::utf32_to_utf8_bytes(*src++); - } - return ret; -} - -size_t utf8_length_from_utf16(const char16_t *src, size_t src_len) -{ - if (src == NULL || src_len == 0) { - return 0; - } - size_t ret = 0; - const char16_t* const end = src + src_len; - while (src < end) { - if ((*src & 0xFC00) == 0xD800 && (src + 1) < end - && (*++src & 0xFC00) == 0xDC00) { - // surrogate pairs are always 4 bytes. - ret += 4; - src++; - } else { - ret += android::utf32_to_utf8_bytes((char32_t) *src++); - } - } - return ret; -} - -static int32_t utf32_at_internal(const char* cur, size_t *num_read) -{ - const char first_char = *cur; - if ((first_char & 0x80) == 0) { // ASCII - *num_read = 1; - return *cur; - } - cur++; - char32_t mask, to_ignore_mask; - size_t num_to_read = 0; - char32_t utf32 = first_char; - for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80; - (first_char & mask); - num_to_read++, to_ignore_mask |= mask, mask >>= 1) { - // 0x3F == 00111111 - utf32 = (utf32 << 6) + (*cur++ & 0x3F); - } - to_ignore_mask |= mask; - utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1))); - - *num_read = num_to_read; - return static_cast(utf32); -} - -int32_t utf32_at(const char *src, size_t src_len, - size_t index, size_t *next_index) -{ - if (index >= src_len) { - return -1; - } - size_t dummy_index; - if (next_index == NULL) { - next_index = &dummy_index; - } - size_t num_read; - int32_t ret = utf32_at_internal(src + index, &num_read); - if (ret >= 0) { - *next_index = index + num_read; - } - - return ret; -} - -size_t utf8_to_utf32(const char* src, size_t src_len, - char32_t* dst, size_t dst_len) -{ - if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) { - return 0; - } - - const char* cur = src; - const char* end = src + src_len; - char32_t* cur_utf32 = dst; - const char32_t* end_utf32 = dst + dst_len; - while (cur_utf32 < end_utf32 && cur < end) { - size_t num_read; - *cur_utf32++ = - static_cast(utf32_at_internal(cur, &num_read)); - cur += num_read; - } - if (cur_utf32 < end_utf32) { - *cur_utf32 = 0; - } - return static_cast(cur_utf32 - dst); -} - -size_t utf32_to_utf8(const char32_t* src, size_t src_len, - char* dst, size_t dst_len) -{ - if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) { - return 0; - } - const char32_t *cur_utf32 = src; - const char32_t *end_utf32 = src + src_len; - char *cur = dst; - const char *end = dst + dst_len; - while (cur_utf32 < end_utf32 && cur < end) { - size_t len = android::utf32_to_utf8_bytes(*cur_utf32); - android::utf32_to_utf8((uint8_t *)cur, *cur_utf32++, len); - cur += len; - } - if (cur < end) { - *cur = '\0'; - } - return cur - dst; -} - -size_t utf16_to_utf8(const char16_t* src, size_t src_len, - char* dst, size_t dst_len) -{ - if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) { - return 0; - } - const char16_t* cur_utf16 = src; - const char16_t* const end_utf16 = src + src_len; - char *cur = dst; - const char* const end = dst + dst_len; - while (cur_utf16 < end_utf16 && cur < end) { - char32_t utf32; - // surrogate pairs - if ((*cur_utf16 & 0xFC00) == 0xD800 && (cur_utf16 + 1) < end_utf16) { - utf32 = (*cur_utf16++ - 0xD800) << 10; - utf32 |= *cur_utf16++ - 0xDC00; - utf32 += 0x10000; - } else { - utf32 = (char32_t) *cur_utf16++; - } - size_t len = android::utf32_to_utf8_bytes(utf32); - android::utf32_to_utf8((uint8_t*)cur, utf32, len); - cur += len; - } - if (cur < end) { - *cur = '\0'; - } - return cur - dst; -} diff --git a/external/android-libs/libandroid_runtime.so b/external/android-libs/libandroid_runtime.so deleted file mode 100644 index fa370f30..00000000 Binary files a/external/android-libs/libandroid_runtime.so and /dev/null differ diff --git a/external/android-libs/libbinder.so b/external/android-libs/libbinder.so deleted file mode 100644 index 5cbc35f2..00000000 Binary files a/external/android-libs/libbinder.so and /dev/null differ diff --git a/external/android-libs/libcrypto.so b/external/android-libs/libcrypto.so deleted file mode 100644 index 859bdac9..00000000 Binary files a/external/android-libs/libcrypto.so and /dev/null differ diff --git a/external/android-libs/libcutils.so b/external/android-libs/libcutils.so deleted file mode 100644 index d506bfaa..00000000 Binary files a/external/android-libs/libcutils.so and /dev/null differ diff --git a/external/android-libs/liblog.so b/external/android-libs/liblog.so deleted file mode 100644 index c7cc6d83..00000000 Binary files a/external/android-libs/liblog.so and /dev/null differ diff --git a/external/android-libs/libnativehelper.so b/external/android-libs/libnativehelper.so deleted file mode 100644 index 9cc76e49..00000000 Binary files a/external/android-libs/libnativehelper.so and /dev/null differ diff --git a/external/android-libs/libstlport_shared.so b/external/android-libs/libstlport_shared.so deleted file mode 100755 index 165ca68f..00000000 Binary files a/external/android-libs/libstlport_shared.so and /dev/null differ diff --git a/external/android-libs/libutils.so b/external/android-libs/libutils.so deleted file mode 100644 index 29837275..00000000 Binary files a/external/android-libs/libutils.so and /dev/null differ diff --git a/external/android-sqlite b/external/android-sqlite deleted file mode 160000 index 1c376985..00000000 --- a/external/android-sqlite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1c376985dd7602cd3542714b7eb46a5198270658 diff --git a/external/dalvik b/external/dalvik deleted file mode 160000 index ef4b0613..00000000 --- a/external/dalvik +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ef4b0613d6952770aefac07d503955eb7b962d2b diff --git a/external/icu4c b/external/icu4c deleted file mode 160000 index 0fa67b93..00000000 --- a/external/icu4c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0fa67b93b831c6636ca18b152a1b1b14cc99b034 diff --git a/external/includes/utils/String16.h b/external/includes/utils/String16.h deleted file mode 100644 index 07a0c118..00000000 --- a/external/includes/utils/String16.h +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_STRING16_H -#define ANDROID_STRING16_H - -#include -#include - -#include -#include - -// --------------------------------------------------------------------------- - -extern "C" { - -typedef uint16_t char16_t; - -// Standard string functions on char16 strings. -int strcmp16(const char16_t *, const char16_t *); -int strncmp16(const char16_t *s1, const char16_t *s2, size_t n); -size_t strlen16(const char16_t *); -size_t strnlen16(const char16_t *, size_t); -char16_t *strcpy16(char16_t *, const char16_t *); -char16_t *strncpy16(char16_t *, const char16_t *, size_t); - -// Version of comparison that supports embedded nulls. -// This is different than strncmp() because we don't stop -// at a nul character and consider the strings to be different -// if the lengths are different (thus we need to supply the -// lengths of both strings). This can also be used when -// your string is not nul-terminated as it will have the -// equivalent result as strcmp16 (unlike strncmp16). -int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2); - -// Version of strzcmp16 for comparing strings in different endianness. -int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2); - -// Convert UTF-8 to UTF-16 including surrogate pairs -void utf8_to_utf16(const uint8_t *src, size_t srcLen, char16_t* dst, const size_t dstLen); - -} - -// --------------------------------------------------------------------------- - -namespace android { - -// --------------------------------------------------------------------------- - -class String8; -class TextOutput; - -//! This is a string holding UTF-16 characters. -class String16 -{ -public: - String16(); - String16(const String16& o); - String16(const String16& o, - size_t len, - size_t begin=0); - explicit String16(const char16_t* o); - explicit String16(const char16_t* o, size_t len); - explicit String16(const String8& o); - explicit String16(const char* o); - explicit String16(const char* o, size_t len); - - ~String16(); - - inline const char16_t* string() const; - inline size_t size() const; - - inline const SharedBuffer* sharedBuffer() const; - - void setTo(const String16& other); - status_t setTo(const char16_t* other); - status_t setTo(const char16_t* other, size_t len); - status_t setTo(const String16& other, - size_t len, - size_t begin=0); - - status_t append(const String16& other); - status_t append(const char16_t* other, size_t len); - - inline String16& operator=(const String16& other); - - inline String16& operator+=(const String16& other); - inline String16 operator+(const String16& other) const; - - status_t insert(size_t pos, const char16_t* chrs); - status_t insert(size_t pos, - const char16_t* chrs, size_t len); - - ssize_t findFirst(char16_t c) const; - ssize_t findLast(char16_t c) const; - - bool startsWith(const String16& prefix) const; - bool startsWith(const char16_t* prefix) const; - - status_t makeLower(); - - status_t replaceAll(char16_t replaceThis, - char16_t withThis); - - status_t remove(size_t len, size_t begin=0); - - inline int compare(const String16& other) const; - - inline bool operator<(const String16& other) const; - inline bool operator<=(const String16& other) const; - inline bool operator==(const String16& other) const; - inline bool operator!=(const String16& other) const; - inline bool operator>=(const String16& other) const; - inline bool operator>(const String16& other) const; - - inline bool operator<(const char16_t* other) const; - inline bool operator<=(const char16_t* other) const; - inline bool operator==(const char16_t* other) const; - inline bool operator!=(const char16_t* other) const; - inline bool operator>=(const char16_t* other) const; - inline bool operator>(const char16_t* other) const; - - inline operator const char16_t*() const; - -private: - const char16_t* mString; -}; - -TextOutput& operator<<(TextOutput& to, const String16& val); - -// --------------------------------------------------------------------------- -// No user servicable parts below. - -inline int compare_type(const String16& lhs, const String16& rhs) -{ - return lhs.compare(rhs); -} - -inline int strictly_order_type(const String16& lhs, const String16& rhs) -{ - return compare_type(lhs, rhs) < 0; -} - -inline const char16_t* String16::string() const -{ - return mString; -} - -inline size_t String16::size() const -{ - return SharedBuffer::sizeFromData(mString)/sizeof(char16_t)-1; -} - -inline const SharedBuffer* String16::sharedBuffer() const -{ - return SharedBuffer::bufferFromData(mString); -} - -inline String16& String16::operator=(const String16& other) -{ - setTo(other); - return *this; -} - -inline String16& String16::operator+=(const String16& other) -{ - append(other); - return *this; -} - -inline String16 String16::operator+(const String16& other) const -{ - String16 tmp; - tmp += other; - return tmp; -} - -inline int String16::compare(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()); -} - -inline bool String16::operator<(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) < 0; -} - -inline bool String16::operator<=(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) <= 0; -} - -inline bool String16::operator==(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) == 0; -} - -inline bool String16::operator!=(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) != 0; -} - -inline bool String16::operator>=(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) >= 0; -} - -inline bool String16::operator>(const String16& other) const -{ - return strzcmp16(mString, size(), other.mString, other.size()) > 0; -} - -inline bool String16::operator<(const char16_t* other) const -{ - return strcmp16(mString, other) < 0; -} - -inline bool String16::operator<=(const char16_t* other) const -{ - return strcmp16(mString, other) <= 0; -} - -inline bool String16::operator==(const char16_t* other) const -{ - return strcmp16(mString, other) == 0; -} - -inline bool String16::operator!=(const char16_t* other) const -{ - return strcmp16(mString, other) != 0; -} - -inline bool String16::operator>=(const char16_t* other) const -{ - return strcmp16(mString, other) >= 0; -} - -inline bool String16::operator>(const char16_t* other) const -{ - return strcmp16(mString, other) > 0; -} - -inline String16::operator const char16_t*() const -{ - return mString; -} - -}; // namespace android - -// --------------------------------------------------------------------------- - -#endif // ANDROID_STRING16_H diff --git a/external/includes/utils/String8.h b/external/includes/utils/String8.h deleted file mode 100644 index ef0b51a4..00000000 --- a/external/includes/utils/String8.h +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_STRING8_H -#define ANDROID_STRING8_H - -#include - -// Need this for the char16_t type; String8.h should not -// be depedent on the String16 class. -#include - -#include -#include -#include - -// --------------------------------------------------------------------------- - -extern "C" { - -typedef uint32_t char32_t; - -size_t strlen32(const char32_t *); -size_t strnlen32(const char32_t *, size_t); - -/* - * Returns the length of "src" when "src" is valid UTF-8 string. - * Returns 0 if src is NULL, 0-length string or non UTF-8 string. - * This function should be used to determine whether "src" is valid UTF-8 - * characters with valid unicode codepoints. "src" must be null-terminated. - * - * If you are going to use other GetUtf... functions defined in this header - * with string which may not be valid UTF-8 with valid codepoint (form 0 to - * 0x10FFFF), you should use this function before calling others, since the - * other functions do not check whether the string is valid UTF-8 or not. - * - * If you do not care whether "src" is valid UTF-8 or not, you should use - * strlen() as usual, which should be much faster. - */ -size_t utf8_length(const char *src); - -/* - * Returns the UTF-32 length of "src". - */ -size_t utf32_length(const char *src, size_t src_len); - -/* - * Returns the UTF-8 length of "src". - */ -size_t utf8_length_from_utf16(const char16_t *src, size_t src_len); - -/* - * Returns the UTF-8 length of "src". - */ -size_t utf8_length_from_utf32(const char32_t *src, size_t src_len); - -/* - * Returns the unicode value at "index". - * Returns -1 when the index is invalid (equals to or more than "src_len"). - * If returned value is positive, it is able to be converted to char32_t, which - * is unsigned. Then, if "next_index" is not NULL, the next index to be used is - * stored in "next_index". "next_index" can be NULL. - */ -int32_t utf32_at(const char *src, size_t src_len, - size_t index, size_t *next_index); - -/* - * Stores a UTF-32 string converted from "src" in "dst", if "dst_length" is not - * large enough to store the string, the part of the "src" string is stored - * into "dst". - * Returns the size actually used for storing the string. - * "dst" is not null-terminated when dst_len is fully used (like strncpy). - */ -size_t utf8_to_utf32(const char* src, size_t src_len, - char32_t* dst, size_t dst_len); - -/* - * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not - * large enough to store the string, the part of the "src" string is stored - * into "dst" as much as possible. See the examples for more detail. - * Returns the size actually used for storing the string. - * dst" is not null-terminated when dst_len is fully used (like strncpy). - * - * Example 1 - * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) - * "src_len" == 2 - * "dst_len" >= 7 - * -> - * Returned value == 6 - * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0 - * (note that "dst" is null-terminated) - * - * Example 2 - * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) - * "src_len" == 2 - * "dst_len" == 5 - * -> - * Returned value == 3 - * "dst" becomes \xE3\x81\x82\0 - * (note that "dst" is null-terminated, but \u3044 is not stored in "dst" - * since "dst" does not have enough size to store the character) - * - * Example 3 - * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) - * "src_len" == 2 - * "dst_len" == 6 - * -> - * Returned value == 6 - * "dst" becomes \xE3\x81\x82\xE3\x81\x84 - * (note that "dst" is NOT null-terminated, like strncpy) - */ -size_t utf32_to_utf8(const char32_t* src, size_t src_len, - char* dst, size_t dst_len); - -size_t utf16_to_utf8(const char16_t* src, size_t src_len, - char* dst, size_t dst_len); - -} - -// --------------------------------------------------------------------------- - -namespace android { - -class TextOutput; - -//! This is a string holding UTF-8 characters. Does not allow the value more -// than 0x10FFFF, which is not valid unicode codepoint. -class String8 -{ -public: - String8(); - String8(const String8& o); - explicit String8(const char* o); - explicit String8(const char* o, size_t numChars); - - explicit String8(const String16& o); - explicit String8(const char16_t* o); - explicit String8(const char16_t* o, size_t numChars); - explicit String8(const char32_t* o); - explicit String8(const char32_t* o, size_t numChars); - ~String8(); - - inline const char* string() const; - inline size_t size() const; - inline size_t length() const; - inline size_t bytes() const; - - inline const SharedBuffer* sharedBuffer() const; - - void setTo(const String8& other); - status_t setTo(const char* other); - status_t setTo(const char* other, size_t numChars); - status_t setTo(const char16_t* other, size_t numChars); - status_t setTo(const char32_t* other, - size_t length); - - status_t append(const String8& other); - status_t append(const char* other); - status_t append(const char* other, size_t numChars); - - status_t appendFormat(const char* fmt, ...) - __attribute__((format (printf, 2, 3))); - - // Note that this function takes O(N) time to calculate the value. - // No cache value is stored. - size_t getUtf32Length() const; - int32_t getUtf32At(size_t index, - size_t *next_index) const; - size_t getUtf32(char32_t* dst, size_t dst_len) const; - - inline String8& operator=(const String8& other); - inline String8& operator=(const char* other); - - inline String8& operator+=(const String8& other); - inline String8 operator+(const String8& other) const; - - inline String8& operator+=(const char* other); - inline String8 operator+(const char* other) const; - - inline int compare(const String8& other) const; - - inline bool operator<(const String8& other) const; - inline bool operator<=(const String8& other) const; - inline bool operator==(const String8& other) const; - inline bool operator!=(const String8& other) const; - inline bool operator>=(const String8& other) const; - inline bool operator>(const String8& other) const; - - inline bool operator<(const char* other) const; - inline bool operator<=(const char* other) const; - inline bool operator==(const char* other) const; - inline bool operator!=(const char* other) const; - inline bool operator>=(const char* other) const; - inline bool operator>(const char* other) const; - - inline operator const char*() const; - - char* lockBuffer(size_t size); - void unlockBuffer(); - status_t unlockBuffer(size_t size); - - // return the index of the first byte of other in this at or after - // start, or -1 if not found - ssize_t find(const char* other, size_t start = 0) const; - - void toLower(); - void toLower(size_t start, size_t numChars); - void toUpper(); - void toUpper(size_t start, size_t numChars); - - /* - * These methods operate on the string as if it were a path name. - */ - - /* - * Set the filename field to a specific value. - * - * Normalizes the filename, removing a trailing '/' if present. - */ - void setPathName(const char* name); - void setPathName(const char* name, size_t numChars); - - /* - * Get just the filename component. - * - * "/tmp/foo/bar.c" --> "bar.c" - */ - String8 getPathLeaf(void) const; - - /* - * Remove the last (file name) component, leaving just the directory - * name. - * - * "/tmp/foo/bar.c" --> "/tmp/foo" - * "/tmp" --> "" // ????? shouldn't this be "/" ???? XXX - * "bar.c" --> "" - */ - String8 getPathDir(void) const; - - /* - * Retrieve the front (root dir) component. Optionally also return the - * remaining components. - * - * "/tmp/foo/bar.c" --> "tmp" (remain = "foo/bar.c") - * "/tmp" --> "tmp" (remain = "") - * "bar.c" --> "bar.c" (remain = "") - */ - String8 walkPath(String8* outRemains = NULL) const; - - /* - * Return the filename extension. This is the last '.' and up to - * four characters that follow it. The '.' is included in case we - * decide to expand our definition of what constitutes an extension. - * - * "/tmp/foo/bar.c" --> ".c" - * "/tmp" --> "" - * "/tmp/foo.bar/baz" --> "" - * "foo.jpeg" --> ".jpeg" - * "foo." --> "" - */ - String8 getPathExtension(void) const; - - /* - * Return the path without the extension. Rules for what constitutes - * an extension are described in the comment for getPathExtension(). - * - * "/tmp/foo/bar.c" --> "/tmp/foo/bar" - */ - String8 getBasePath(void) const; - - /* - * Add a component to the pathname. We guarantee that there is - * exactly one path separator between the old path and the new. - * If there is no existing name, we just copy the new name in. - * - * If leaf is a fully qualified path (i.e. starts with '/', it - * replaces whatever was there before. - */ - String8& appendPath(const char* leaf); - String8& appendPath(const String8& leaf) { return appendPath(leaf.string()); } - - /* - * Like appendPath(), but does not affect this string. Returns a new one instead. - */ - String8 appendPathCopy(const char* leaf) const - { String8 p(*this); p.appendPath(leaf); return p; } - String8 appendPathCopy(const String8& leaf) const { return appendPathCopy(leaf.string()); } - - /* - * Converts all separators in this string to /, the default path separator. - * - * If the default OS separator is backslash, this converts all - * backslashes to slashes, in-place. Otherwise it does nothing. - * Returns self. - */ - String8& convertToResPath(); - -private: - status_t real_append(const char* other, size_t numChars); - char* find_extension(void) const; - - const char* mString; -}; - -TextOutput& operator<<(TextOutput& to, const String16& val); - -// --------------------------------------------------------------------------- -// No user servicable parts below. - -inline int compare_type(const String8& lhs, const String8& rhs) -{ - return lhs.compare(rhs); -} - -inline int strictly_order_type(const String8& lhs, const String8& rhs) -{ - return compare_type(lhs, rhs) < 0; -} - -inline const char* String8::string() const -{ - return mString; -} - -inline size_t String8::length() const -{ - return SharedBuffer::sizeFromData(mString)-1; -} - -inline size_t String8::size() const -{ - return length(); -} - -inline size_t String8::bytes() const -{ - return SharedBuffer::sizeFromData(mString)-1; -} - -inline const SharedBuffer* String8::sharedBuffer() const -{ - return SharedBuffer::bufferFromData(mString); -} - -inline String8& String8::operator=(const String8& other) -{ - setTo(other); - return *this; -} - -inline String8& String8::operator=(const char* other) -{ - setTo(other); - return *this; -} - -inline String8& String8::operator+=(const String8& other) -{ - append(other); - return *this; -} - -inline String8 String8::operator+(const String8& other) const -{ - String8 tmp(*this); - tmp += other; - return tmp; -} - -inline String8& String8::operator+=(const char* other) -{ - append(other); - return *this; -} - -inline String8 String8::operator+(const char* other) const -{ - String8 tmp(*this); - tmp += other; - return tmp; -} - -inline int String8::compare(const String8& other) const -{ - return strcmp(mString, other.mString); -} - -inline bool String8::operator<(const String8& other) const -{ - return strcmp(mString, other.mString) < 0; -} - -inline bool String8::operator<=(const String8& other) const -{ - return strcmp(mString, other.mString) <= 0; -} - -inline bool String8::operator==(const String8& other) const -{ - return strcmp(mString, other.mString) == 0; -} - -inline bool String8::operator!=(const String8& other) const -{ - return strcmp(mString, other.mString) != 0; -} - -inline bool String8::operator>=(const String8& other) const -{ - return strcmp(mString, other.mString) >= 0; -} - -inline bool String8::operator>(const String8& other) const -{ - return strcmp(mString, other.mString) > 0; -} - -inline bool String8::operator<(const char* other) const -{ - return strcmp(mString, other) < 0; -} - -inline bool String8::operator<=(const char* other) const -{ - return strcmp(mString, other) <= 0; -} - -inline bool String8::operator==(const char* other) const -{ - return strcmp(mString, other) == 0; -} - -inline bool String8::operator!=(const char* other) const -{ - return strcmp(mString, other) != 0; -} - -inline bool String8::operator>=(const char* other) const -{ - return strcmp(mString, other) >= 0; -} - -inline bool String8::operator>(const char* other) const -{ - return strcmp(mString, other) > 0; -} - -inline String8::operator const char*() const -{ - return mString; -} - -} // namespace android - -// --------------------------------------------------------------------------- - -#endif // ANDROID_STRING8_H diff --git a/external/openssl b/external/openssl deleted file mode 160000 index 1a3c5799..00000000 --- a/external/openssl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1a3c5799337b90ddc56376ace7284a9e7f8cc988 diff --git a/external/platform-frameworks-base b/external/platform-frameworks-base deleted file mode 160000 index 94180377..00000000 --- a/external/platform-frameworks-base +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 94180377e709ed0faff6ea94c75af0f0a1183b36 diff --git a/external/platform-system-core b/external/platform-system-core deleted file mode 160000 index 31b2f441..00000000 --- a/external/platform-system-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 31b2f4414b291d87adddf13ad62d317d3ba382fa diff --git a/external/sqlcipher b/external/sqlcipher deleted file mode 160000 index ff9a3400..00000000 --- a/external/sqlcipher +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ff9a34003964900e2ee792169c544cd5d7696bdb diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..2d8d1e4d --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..41d9927a Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..070cb702 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jni/Android.mk b/jni/Android.mk deleted file mode 100644 index e8e008b5..00000000 --- a/jni/Android.mk +++ /dev/null @@ -1,71 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -EXTERNAL_PATH := ../external - -ifeq ($(TARGET_ARCH), arm) - LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))" -else - LOCAL_CFLAGS += -DPACKED="" -endif - -#TARGET_PLATFORM := android-8 - -ifeq ($(WITH_JIT),true) - LOCAL_CFLAGS += -DWITH_JIT -endif - -ifneq ($(USE_CUSTOM_RUNTIME_HEAP_MAX),) - LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX) -endif - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - net_sqlcipher_database_SQLiteCompiledSql.cpp \ - net_sqlcipher_database_SQLiteDatabase.cpp \ - net_sqlcipher_database_SQLiteProgram.cpp \ - net_sqlcipher_database_SQLiteQuery.cpp \ - net_sqlcipher_database_SQLiteStatement.cpp \ - net_sqlcipher_CursorWindow.cpp \ - CursorWindow.cpp -# net_sqlcipher_database_sqlcipher_SQLiteDebug.cpp - -LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) \ - $(EXTERNAL_PATH)/sqlcipher \ - $(EXTERNAL_PATH)/openssl/include \ - $(EXTERNAL_PATH)/platform-frameworks-base/core/jni \ - $(EXTERNAL_PATH)/android-sqlite/android \ - $(EXTERNAL_PATH)/dalvik/libnativehelper/include \ - $(EXTERNAL_PATH)/dalvik/libnativehelper/include/nativehelper \ - $(EXTERNAL_PATH)/platform-system-core/include \ - $(LOCAL_PATH)/include \ - $(EXTERNAL_PATH)/platform-frameworks-base/include \ - -LOCAL_SHARED_LIBRARIES := \ - libcrypto \ - libssl \ - libsqlcipher \ - libsqlite3_android - -LOCAL_CFLAGS += -U__APPLE__ -LOCAL_LDFLAGS += -L../external/android-libs/ -L../external/libs/armeabi/ - -# libs from the NDK -LOCAL_LDLIBS += -ldl -llog -# libnativehelper and libandroid_runtime are included with Android but not the NDK -LOCAL_LDLIBS += -lnativehelper -landroid_runtime -lutils -lbinder -# these are build in the ../external section - -#LOCAL_REQUIRED_MODULES += libsqlcipher libicuuc libicui18n -LOCAL_LDLIBS += -lsqlcipher_android - -ifeq ($(WITH_MALLOC_LEAK_CHECK),true) - LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK -endif - -LOCAL_MODULE:= libdatabase_sqlcipher - -include $(BUILD_SHARED_LIBRARY) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/jni/Application.mk b/jni/Application.mk deleted file mode 100644 index ccc3b137..00000000 --- a/jni/Application.mk +++ /dev/null @@ -1,5 +0,0 @@ -APP_PROJECT_PATH := $(shell pwd) -APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk -# fixes this error when building external/android-sqlite/android/sqlite3_android.cpp -# icu4c/common/unicode/std_string.h:39:18: error: string: No such file or directory -APP_STL := stlport_shared diff --git a/jni/android_util_Binder.cpp b/jni/android_util_Binder.cpp deleted file mode 100644 index 7a53874c..00000000 --- a/jni/android_util_Binder.cpp +++ /dev/null @@ -1,1707 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "JavaBinder" -//#define LOG_NDEBUG 0 - -#include "android_util_Binder.h" -#include "JNIHelp.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -//#undef LOGV -//#define LOGV(...) fprintf(stderr, __VA_ARGS__) - -using namespace android; - -// ---------------------------------------------------------------------------- - -static struct bindernative_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mExecTransact; - - // Object state. - jfieldID mObject; - -} gBinderOffsets; - -// ---------------------------------------------------------------------------- - -static struct binderinternal_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mForceGc; - -} gBinderInternalOffsets; - -// ---------------------------------------------------------------------------- - -static struct debug_offsets_t -{ - // Class state. - jclass mClass; - -} gDebugOffsets; - -// ---------------------------------------------------------------------------- - -static struct weakreference_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mGet; - -} gWeakReferenceOffsets; - -static struct error_offsets_t -{ - jclass mClass; -} gErrorOffsets; - -// ---------------------------------------------------------------------------- - -static struct binderproxy_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mConstructor; - jmethodID mSendDeathNotice; - - // Object state. - jfieldID mObject; - jfieldID mSelf; - -} gBinderProxyOffsets; - -// ---------------------------------------------------------------------------- - -static struct parcel_offsets_t -{ - jfieldID mObject; - jfieldID mOwnObject; -} gParcelOffsets; - -static struct log_offsets_t -{ - // Class state. - jclass mClass; - jmethodID mLogE; -} gLogOffsets; - -static struct file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; - jfieldID mDescriptor; -} gFileDescriptorOffsets; - -static struct parcel_file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; -} gParcelFileDescriptorOffsets; - -static struct strict_mode_callback_offsets_t -{ - jclass mClass; - jmethodID mCallback; -} gStrictModeCallbackOffsets; - -// **************************************************************************** -// **************************************************************************** -// **************************************************************************** - -static volatile int32_t gNumRefsCreated = 0; -static volatile int32_t gNumProxyRefs = 0; -static volatile int32_t gNumLocalRefs = 0; -static volatile int32_t gNumDeathRefs = 0; - -static void incRefsCreated(JNIEnv* env) -{ - int old = android_atomic_inc(&gNumRefsCreated); - if (old == 200) { - android_atomic_and(0, &gNumRefsCreated); - env->CallStaticVoidMethod(gBinderInternalOffsets.mClass, - gBinderInternalOffsets.mForceGc); - } else { - LOGV("Now have %d binder ops", old); - } -} - -static JavaVM* jnienv_to_javavm(JNIEnv* env) -{ - JavaVM* vm; - return env->GetJavaVM(&vm) >= 0 ? vm : NULL; -} - -static JNIEnv* javavm_to_jnienv(JavaVM* vm) -{ - JNIEnv* env; - return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL; -} - -static void report_exception(JNIEnv* env, jthrowable excep, const char* msg) -{ - env->ExceptionClear(); - - jstring tagstr = env->NewStringUTF(LOG_TAG); - jstring msgstr = env->NewStringUTF(msg); - - if ((tagstr == NULL) || (msgstr == NULL)) { - env->ExceptionClear(); /* assume exception (OOM?) was thrown */ - LOGE("Unable to call Log.e()\n"); - LOGE("%s", msg); - goto bail; - } - - env->CallStaticIntMethod( - gLogOffsets.mClass, gLogOffsets.mLogE, tagstr, msgstr, excep); - if (env->ExceptionCheck()) { - /* attempting to log the failure has failed */ - LOGW("Failed trying to log exception, msg='%s'\n", msg); - env->ExceptionClear(); - } - - if (env->IsInstanceOf(excep, gErrorOffsets.mClass)) { - /* - * It's an Error: Reraise the exception, detach this thread, and - * wait for the fireworks. Die even more blatantly after a minute - * if the gentler attempt doesn't do the trick. - * - * The GetJavaVM function isn't on the "approved" list of JNI calls - * that can be made while an exception is pending, so we want to - * get the VM ptr, throw the exception, and then detach the thread. - */ - JavaVM* vm = jnienv_to_javavm(env); - env->Throw(excep); - vm->DetachCurrentThread(); - sleep(60); - LOGE("Forcefully exiting"); - exit(1); - *((int *) 1) = 1; - } - -bail: - /* discard local refs created for us by VM */ - env->DeleteLocalRef(tagstr); - env->DeleteLocalRef(msgstr); -} - -static void set_dalvik_blockguard_policy(JNIEnv* env, jint strict_policy) -{ - // Call back into android.os.StrictMode#onBinderStrictModePolicyChange - // to sync our state back to it. See the comments in StrictMode.java. - env->CallStaticVoidMethod(gStrictModeCallbackOffsets.mClass, - gStrictModeCallbackOffsets.mCallback, - strict_policy); -} - -class JavaBBinderHolder; - -class JavaBBinder : public BBinder -{ -public: - JavaBBinder(JNIEnv* env, jobject object) - : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)) - { - LOGV("Creating JavaBBinder %p\n", this); - android_atomic_inc(&gNumLocalRefs); - incRefsCreated(env); - } - - bool checkSubclass(const void* subclassID) const - { - return subclassID == &gBinderOffsets; - } - - jobject object() const - { - return mObject; - } - -protected: - virtual ~JavaBBinder() - { - LOGV("Destroying JavaBBinder %p\n", this); - android_atomic_dec(&gNumLocalRefs); - JNIEnv* env = javavm_to_jnienv(mVM); - env->DeleteGlobalRef(mObject); - } - - virtual status_t onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) - { - JNIEnv* env = javavm_to_jnienv(mVM); - - LOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM); - - IPCThreadState* thread_state = IPCThreadState::self(); - const int strict_policy_before = thread_state->getStrictModePolicy(); - thread_state->setLastTransactionBinderFlags(flags); - - //printf("Transact from %p to Java code sending: ", this); - //data.print(); - //printf("\n"); - jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, - code, (int32_t)&data, (int32_t)reply, flags); - jthrowable excep = env->ExceptionOccurred(); - - // Restore the Java binder thread's state if it changed while - // processing a call (as it would if the Parcel's header had a - // new policy mask and Parcel.enforceInterface() changed - // it...) - const int strict_policy_after = thread_state->getStrictModePolicy(); - if (strict_policy_after != strict_policy_before) { - // Our thread-local... - thread_state->setStrictModePolicy(strict_policy_before); - // And the Java-level thread-local... - set_dalvik_blockguard_policy(env, strict_policy_before); - } - - if (excep) { - report_exception(env, excep, - "*** Uncaught remote exception! " - "(Exceptions are not yet supported across processes.)"); - res = JNI_FALSE; - - /* clean up JNI local ref -- we don't return to Java code */ - env->DeleteLocalRef(excep); - } - - //aout << "onTransact to Java code; result=" << res << endl - // << "Transact from " << this << " to Java code returning " - // << reply << ": " << *reply << endl; - return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION; - } - - virtual status_t dump(int fd, const Vector& args) - { - return 0; - } - -private: - JavaVM* const mVM; - jobject const mObject; -}; - -// ---------------------------------------------------------------------------- - -class JavaBBinderHolder : public RefBase -{ -public: - JavaBBinderHolder(JNIEnv* env, jobject object) - : mObject(object) - { - LOGV("Creating JavaBBinderHolder for Object %p\n", object); - } - ~JavaBBinderHolder() - { - LOGV("Destroying JavaBBinderHolder for Object %p\n", mObject); - } - - sp get(JNIEnv* env) - { - AutoMutex _l(mLock); - sp b = mBinder.promote(); - if (b == NULL) { - b = new JavaBBinder(env, mObject); - mBinder = b; - LOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%d\n", - b.get(), b->getWeakRefs(), mObject, b->getWeakRefs()->getWeakCount()); - } - - return b; - } - - sp getExisting() - { - AutoMutex _l(mLock); - return mBinder.promote(); - } - -private: - Mutex mLock; - jobject mObject; - wp mBinder; -}; - -// ---------------------------------------------------------------------------- - -class JavaDeathRecipient : public IBinder::DeathRecipient -{ -public: - JavaDeathRecipient(JNIEnv* env, jobject object) - : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)), - mHoldsRef(true) - { - incStrong(this); - android_atomic_inc(&gNumDeathRefs); - incRefsCreated(env); - } - - void binderDied(const wp& who) - { - JNIEnv* env = javavm_to_jnienv(mVM); - - LOGV("Receiving binderDied() on JavaDeathRecipient %p\n", this); - - env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject); - jthrowable excep = env->ExceptionOccurred(); - if (excep) { - report_exception(env, excep, - "*** Uncaught exception returned from death notification!"); - } - - clearReference(); - } - - void clearReference() - { - bool release = false; - mLock.lock(); - if (mHoldsRef) { - mHoldsRef = false; - release = true; - } - mLock.unlock(); - if (release) { - decStrong(this); - } - } - -protected: - virtual ~JavaDeathRecipient() - { - //LOGI("Removing death ref: recipient=%p\n", mObject); - android_atomic_dec(&gNumDeathRefs); - JNIEnv* env = javavm_to_jnienv(mVM); - env->DeleteGlobalRef(mObject); - } - -private: - JavaVM* const mVM; - jobject const mObject; - Mutex mLock; - bool mHoldsRef; -}; - -// ---------------------------------------------------------------------------- - -namespace android { - -static void proxy_cleanup(const void* id, void* obj, void* cleanupCookie) -{ - android_atomic_dec(&gNumProxyRefs); - JNIEnv* env = javavm_to_jnienv((JavaVM*)cleanupCookie); - env->DeleteGlobalRef((jobject)obj); -} - -static Mutex mProxyLock; - -jobject javaObjectForIBinder(JNIEnv* env, const sp& val) -{ - if (val == NULL) return NULL; - - if (val->checkSubclass(&gBinderOffsets)) { - // One of our own! - jobject object = static_cast(val.get())->object(); - //printf("objectForBinder %p: it's our own %p!\n", val.get(), object); - return object; - } - - // For the rest of the function we will hold this lock, to serialize - // looking/creation of Java proxies for native Binder proxies. - AutoMutex _l(mProxyLock); - - // Someone else's... do we know about it? - jobject object = (jobject)val->findObject(&gBinderProxyOffsets); - if (object != NULL) { - jobject res = env->CallObjectMethod(object, gWeakReferenceOffsets.mGet); - if (res != NULL) { - LOGV("objectForBinder %p: found existing %p!\n", val.get(), res); - return res; - } - LOGV("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get()); - android_atomic_dec(&gNumProxyRefs); - val->detachObject(&gBinderProxyOffsets); - env->DeleteGlobalRef(object); - } - - object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor); - if (object != NULL) { - LOGV("objectForBinder %p: created new %p!\n", val.get(), object); - // The proxy holds a reference to the native object. - env->SetIntField(object, gBinderProxyOffsets.mObject, (int)val.get()); - val->incStrong(object); - - // The native object needs to hold a weak reference back to the - // proxy, so we can retrieve the same proxy if it is still active. - jobject refObject = env->NewGlobalRef( - env->GetObjectField(object, gBinderProxyOffsets.mSelf)); - val->attachObject(&gBinderProxyOffsets, refObject, - jnienv_to_javavm(env), proxy_cleanup); - - // Note that a new object reference has been created. - android_atomic_inc(&gNumProxyRefs); - incRefsCreated(env); - } - - return object; -} - -sp ibinderForJavaObject(JNIEnv* env, jobject obj) -{ - if (obj == NULL) return NULL; - - if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) { - JavaBBinderHolder* jbh = (JavaBBinderHolder*) - env->GetIntField(obj, gBinderOffsets.mObject); - return jbh != NULL ? jbh->get(env) : NULL; - } - - if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) { - return (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - } - - LOGW("ibinderForJavaObject: %p is not a Binder object", obj); - return NULL; -} - -Parcel* parcelForJavaObject(JNIEnv* env, jobject obj) -{ - if (obj) { - Parcel* p = (Parcel*)env->GetIntField(obj, gParcelOffsets.mObject); - if (p != NULL) { - return p; - } - jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!"); - } - return NULL; -} - -jobject newFileDescriptor(JNIEnv* env, int fd) -{ - jobject object = env->NewObject( - gFileDescriptorOffsets.mClass, gFileDescriptorOffsets.mConstructor); - if (object != NULL) { - //LOGI("Created new FileDescriptor %p with fd %d\n", object, fd); - env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, fd); - } - return object; -} - -jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc) -{ - return env->NewObject( - gParcelFileDescriptorOffsets.mClass, gParcelFileDescriptorOffsets.mConstructor, fileDesc); -} - -void signalExceptionForError(JNIEnv* env, jobject obj, status_t err) -{ - switch (err) { - case UNKNOWN_ERROR: - jniThrowException(env, "java/lang/RuntimeException", "Unknown error"); - break; - case NO_MEMORY: - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - break; - case INVALID_OPERATION: - jniThrowException(env, "java/lang/UnsupportedOperationException", NULL); - break; - case BAD_VALUE: - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - break; - case BAD_INDEX: - jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL); - break; - case BAD_TYPE: - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - break; - case NAME_NOT_FOUND: - jniThrowException(env, "java/util/NoSuchElementException", NULL); - break; - case PERMISSION_DENIED: - jniThrowException(env, "java/lang/SecurityException", NULL); - break; - case NOT_ENOUGH_DATA: - jniThrowException(env, "android/os/ParcelFormatException", "Not enough data"); - break; - case NO_INIT: - jniThrowException(env, "java/lang/RuntimeException", "Not initialized"); - break; - case ALREADY_EXISTS: - jniThrowException(env, "java/lang/RuntimeException", "Item already exists"); - break; - case DEAD_OBJECT: - jniThrowException(env, "android/os/DeadObjectException", NULL); - break; - case UNKNOWN_TRANSACTION: - jniThrowException(env, "java/lang/RuntimeException", "Unknown transaction code"); - break; - case FAILED_TRANSACTION: - LOGE("!!! FAILED BINDER TRANSACTION !!!"); - //jniThrowException(env, "java/lang/OutOfMemoryError", "Binder transaction too large"); - break; - default: - LOGE("Unknown binder error code. 0x%x", err); - } -} - -} - -// ---------------------------------------------------------------------------- - -static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz) -{ - return IPCThreadState::self()->getCallingPid(); -} - -static jint android_os_Binder_getCallingUid(JNIEnv* env, jobject clazz) -{ - return IPCThreadState::self()->getCallingUid(); -} - -static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz) -{ - return IPCThreadState::self()->clearCallingIdentity(); -} - -static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, jlong token) -{ - IPCThreadState::self()->restoreCallingIdentity(token); -} - -static void android_os_Binder_setThreadStrictModePolicy(JNIEnv* env, jobject clazz, jint policyMask) -{ - IPCThreadState::self()->setStrictModePolicy(policyMask); -} - -static jint android_os_Binder_getThreadStrictModePolicy(JNIEnv* env, jobject clazz) -{ - return IPCThreadState::self()->getStrictModePolicy(); -} - -static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz) -{ - IPCThreadState::self()->flushCommands(); -} - -static void android_os_Binder_init(JNIEnv* env, jobject clazz) -{ - JavaBBinderHolder* jbh = new JavaBBinderHolder(env, clazz); - if (jbh == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - return; - } - LOGV("Java Binder %p: acquiring first ref on holder %p", clazz, jbh); - jbh->incStrong(clazz); - env->SetIntField(clazz, gBinderOffsets.mObject, (int)jbh); -} - -static void android_os_Binder_destroy(JNIEnv* env, jobject clazz) -{ - JavaBBinderHolder* jbh = (JavaBBinderHolder*) - env->GetIntField(clazz, gBinderOffsets.mObject); - if (jbh != NULL) { - env->SetIntField(clazz, gBinderOffsets.mObject, 0); - LOGV("Java Binder %p: removing ref on holder %p", clazz, jbh); - jbh->decStrong(clazz); - } else { - // Encountering an uninitialized binder is harmless. All it means is that - // the Binder was only partially initialized when its finalizer ran and called - // destroy(). The Binder could be partially initialized for several reasons. - // For example, a Binder subclass constructor might have thrown an exception before - // it could delegate to its superclass's constructor. Consequently init() would - // not have been called and the holder pointer would remain NULL. - LOGV("Java Binder %p: ignoring uninitialized binder", clazz); - } -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gBinderMethods[] = { - /* name, signature, funcPtr */ - { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid }, - { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid }, - { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity }, - { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity }, - { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy }, - { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy }, - { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands }, - { "init", "()V", (void*)android_os_Binder_init }, - { "destroy", "()V", (void*)android_os_Binder_destroy } -}; - -const char* const kBinderPathName = "android/os/Binder"; - -static int int_register_android_os_Binder(JNIEnv* env) -{ - jclass clazz; - - clazz = env->FindClass(kBinderPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Binder"); - - gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gBinderOffsets.mExecTransact - = env->GetMethodID(clazz, "execTransact", "(IIII)Z"); - assert(gBinderOffsets.mExecTransact); - - gBinderOffsets.mObject - = env->GetFieldID(clazz, "mObject", "I"); - assert(gBinderOffsets.mObject); - - return AndroidRuntime::registerNativeMethods( - env, kBinderPathName, - gBinderMethods, NELEM(gBinderMethods)); -} - -// **************************************************************************** -// **************************************************************************** -// **************************************************************************** - -namespace android { - -jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz) -{ - return gNumLocalRefs; -} - -jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz) -{ - return gNumProxyRefs; -} - -jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz) -{ - return gNumDeathRefs; -} - -} - -// **************************************************************************** -// **************************************************************************** -// **************************************************************************** - -static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz) -{ - sp b = ProcessState::self()->getContextObject(NULL); - return javaObjectForIBinder(env, b); -} - -static void android_os_BinderInternal_joinThreadPool(JNIEnv* env, jobject clazz) -{ - sp b = ProcessState::self()->getContextObject(NULL); - android::IPCThreadState::self()->joinThreadPool(); -} - -static void android_os_BinderInternal_disableBackgroundScheduling(JNIEnv* env, - jobject clazz, jboolean disable) -{ - IPCThreadState::disableBackgroundScheduling(disable ? true : false); -} - -static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz) -{ - LOGV("Gc has executed, clearing binder ops"); - android_atomic_and(0, &gNumRefsCreated); -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gBinderInternalMethods[] = { - /* name, signature, funcPtr */ - { "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject }, - { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool }, - { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling }, - { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc } -}; - -const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal"; - -static int int_register_android_os_BinderInternal(JNIEnv* env) -{ - jclass clazz; - - clazz = env->FindClass(kBinderInternalPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class com.android.internal.os.BinderInternal"); - - gBinderInternalOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gBinderInternalOffsets.mForceGc - = env->GetStaticMethodID(clazz, "forceBinderGc", "()V"); - assert(gBinderInternalOffsets.mForceGc); - - return AndroidRuntime::registerNativeMethods( - env, kBinderInternalPathName, - gBinderInternalMethods, NELEM(gBinderInternalMethods)); -} - -// **************************************************************************** -// **************************************************************************** -// **************************************************************************** - -static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj) -{ - IBinder* target = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - return JNI_FALSE; - } - status_t err = target->pingBinder(); - return err == NO_ERROR ? JNI_TRUE : JNI_FALSE; -} - -static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobject obj) -{ - IBinder* target = (IBinder*) env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target != NULL) { - const String16& desc = target->getInterfaceDescriptor(); - return env->NewString(desc.string(), desc.size()); - } - jniThrowException(env, "java/lang/RuntimeException", - "No binder found for object"); - return NULL; -} - -static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj) -{ - IBinder* target = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - return JNI_FALSE; - } - bool alive = target->isBinderAlive(); - return alive ? JNI_TRUE : JNI_FALSE; -} - -static int getprocname(pid_t pid, char *buf, size_t len) { - char filename[20]; - FILE *f; - - sprintf(filename, "/proc/%d/cmdline", pid); - f = fopen(filename, "r"); - if (!f) { *buf = '\0'; return 1; } - if (!fgets(buf, len, f)) { *buf = '\0'; return 2; } - fclose(f); - return 0; -} - -static bool push_eventlog_string(char** pos, const char* end, const char* str) { - jint len = strlen(str); - int space_needed = 1 + sizeof(len) + len; - if (end - *pos < space_needed) { - LOGW("not enough space for string. remain=%d; needed=%d", - (end - *pos), space_needed); - return false; - } - **pos = EVENT_TYPE_STRING; - (*pos)++; - memcpy(*pos, &len, sizeof(len)); - *pos += sizeof(len); - memcpy(*pos, str, len); - *pos += len; - return true; -} - -static bool push_eventlog_int(char** pos, const char* end, jint val) { - int space_needed = 1 + sizeof(val); - if (end - *pos < space_needed) { - LOGW("not enough space for int. remain=%d; needed=%d", - (end - *pos), space_needed); - return false; - } - **pos = EVENT_TYPE_INT; - (*pos)++; - memcpy(*pos, &val, sizeof(val)); - *pos += sizeof(val); - return true; -} - -// From frameworks/base/core/java/android/content/EventLogTags.logtags: -#define LOGTAG_BINDER_OPERATION 52004 - -static void conditionally_log_binder_call(int64_t start_millis, - IBinder* target, jint code) { - int duration_ms = static_cast(uptimeMillis() - start_millis); - - int sample_percent; - if (duration_ms >= 500) { - sample_percent = 100; - } else { - sample_percent = 100 * duration_ms / 500; - if (sample_percent == 0) { - return; - } - if (sample_percent < (random() % 100 + 1)) { - return; - } - } - - char process_name[40]; - getprocname(getpid(), process_name, sizeof(process_name)); - String8 desc(target->getInterfaceDescriptor()); - - char buf[LOGGER_ENTRY_MAX_PAYLOAD]; - buf[0] = EVENT_TYPE_LIST; - buf[1] = 5; - char* pos = &buf[2]; - char* end = &buf[LOGGER_ENTRY_MAX_PAYLOAD - 1]; // leave room for final \n - if (!push_eventlog_string(&pos, end, desc.string())) return; - if (!push_eventlog_int(&pos, end, code)) return; - if (!push_eventlog_int(&pos, end, duration_ms)) return; - if (!push_eventlog_string(&pos, end, process_name)) return; - if (!push_eventlog_int(&pos, end, sample_percent)) return; - *(pos++) = '\n'; // conventional with EVENT_TYPE_LIST apparently. - android_bWriteLog(LOGTAG_BINDER_OPERATION, buf, pos - buf); -} - -// We only measure binder call durations to potentially log them if -// we're on the main thread. Unfortunately sim-eng doesn't seem to -// have gettid, so we just ignore this and don't log if we can't -// get the thread id. -static bool should_time_binder_calls() { -#ifdef HAVE_GETTID - return (getpid() == androidGetTid()); -#else -#warning no gettid(), so not logging Binder calls... - return false; -#endif -} - -static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, - jint code, jobject dataObj, - jobject replyObj, jint flags) -{ - if (dataObj == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return JNI_FALSE; - } - - Parcel* data = parcelForJavaObject(env, dataObj); - if (data == NULL) { - return JNI_FALSE; - } - Parcel* reply = parcelForJavaObject(env, replyObj); - if (reply == NULL && replyObj != NULL) { - return JNI_FALSE; - } - - IBinder* target = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!"); - return JNI_FALSE; - } - - LOGV("Java code calling transact on %p in Java object %p with code %d\n", - target, obj, code); - - // Only log the binder call duration for things on the Java-level main thread. - // But if we don't - const bool time_binder_calls = should_time_binder_calls(); - - int64_t start_millis; - if (time_binder_calls) { - start_millis = uptimeMillis(); - } - //printf("Transact from Java code to %p sending: ", target); data->print(); - status_t err = target->transact(code, *data, reply, flags); - //if (reply) printf("Transact from Java code to %p received: ", target); reply->print(); - if (time_binder_calls) { - conditionally_log_binder_call(start_millis, target, code); - } - - if (err == NO_ERROR) { - return JNI_TRUE; - } else if (err == UNKNOWN_TRANSACTION) { - return JNI_FALSE; - } - - signalExceptionForError(env, obj, err); - return JNI_FALSE; -} - -static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj, - jobject recipient, jint flags) -{ - if (recipient == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return; - } - - IBinder* target = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - LOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient); - assert(false); - } - - LOGV("linkToDeath: binder=%p recipient=%p\n", target, recipient); - - if (!target->localBinder()) { - sp jdr = new JavaDeathRecipient(env, recipient); - status_t err = target->linkToDeath(jdr, recipient, flags); - if (err != NO_ERROR) { - // Failure adding the death recipient, so clear its reference - // now. - jdr->clearReference(); - signalExceptionForError(env, obj, err); - } - } -} - -static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, - jobject recipient, jint flags) -{ - jboolean res = JNI_FALSE; - if (recipient == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return res; - } - - IBinder* target = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - LOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient); - return JNI_FALSE; - } - - LOGV("unlinkToDeath: binder=%p recipient=%p\n", target, recipient); - - if (!target->localBinder()) { - wp dr; - status_t err = target->unlinkToDeath(NULL, recipient, flags, &dr); - if (err == NO_ERROR && dr != NULL) { - sp sdr = dr.promote(); - JavaDeathRecipient* jdr = static_cast(sdr.get()); - if (jdr != NULL) { - jdr->clearReference(); - } - } - if (err == NO_ERROR || err == DEAD_OBJECT) { - res = JNI_TRUE; - } else { - jniThrowException(env, "java/util/NoSuchElementException", - "Death link does not exist"); - } - } - - return res; -} - -static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj) -{ - IBinder* b = (IBinder*) - env->GetIntField(obj, gBinderProxyOffsets.mObject); - LOGV("Destroying BinderProxy %p: binder=%p\n", obj, b); - env->SetIntField(obj, gBinderProxyOffsets.mObject, 0); - b->decStrong(obj); - IPCThreadState::self()->flushCommands(); -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gBinderProxyMethods[] = { - /* name, signature, funcPtr */ - {"pingBinder", "()Z", (void*)android_os_BinderProxy_pingBinder}, - {"isBinderAlive", "()Z", (void*)android_os_BinderProxy_isBinderAlive}, - {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor}, - {"transact", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}, - {"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, - {"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, - {"destroy", "()V", (void*)android_os_BinderProxy_destroy}, -}; - -const char* const kBinderProxyPathName = "android/os/BinderProxy"; - -static int int_register_android_os_BinderProxy(JNIEnv* env) -{ - jclass clazz; - - clazz = env->FindClass("java/lang/ref/WeakReference"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.ref.WeakReference"); - gWeakReferenceOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gWeakReferenceOffsets.mGet - = env->GetMethodID(clazz, "get", "()Ljava/lang/Object;"); - assert(gWeakReferenceOffsets.mGet); - - clazz = env->FindClass("java/lang/Error"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.Error"); - gErrorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - - clazz = env->FindClass(kBinderProxyPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.BinderProxy"); - - gBinderProxyOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gBinderProxyOffsets.mConstructor - = env->GetMethodID(clazz, "", "()V"); - assert(gBinderProxyOffsets.mConstructor); - gBinderProxyOffsets.mSendDeathNotice - = env->GetStaticMethodID(clazz, "sendDeathNotice", "(Landroid/os/IBinder$DeathRecipient;)V"); - assert(gBinderProxyOffsets.mSendDeathNotice); - - gBinderProxyOffsets.mObject - = env->GetFieldID(clazz, "mObject", "I"); - assert(gBinderProxyOffsets.mObject); - gBinderProxyOffsets.mSelf - = env->GetFieldID(clazz, "mSelf", "Ljava/lang/ref/WeakReference;"); - assert(gBinderProxyOffsets.mSelf); - - return AndroidRuntime::registerNativeMethods( - env, kBinderProxyPathName, - gBinderProxyMethods, NELEM(gBinderProxyMethods)); -} - -// **************************************************************************** -// **************************************************************************** -// **************************************************************************** - -static jint android_os_Parcel_dataSize(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - return parcel ? parcel->dataSize() : 0; -} - -static jint android_os_Parcel_dataAvail(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - return parcel ? parcel->dataAvail() : 0; -} - -static jint android_os_Parcel_dataPosition(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - return parcel ? parcel->dataPosition() : 0; -} - -static jint android_os_Parcel_dataCapacity(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - return parcel ? parcel->dataCapacity() : 0; -} - -static void android_os_Parcel_setDataSize(JNIEnv* env, jobject clazz, jint size) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->setDataSize(size); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_setDataPosition(JNIEnv* env, jobject clazz, jint pos) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - parcel->setDataPosition(pos); - } -} - -static void android_os_Parcel_setDataCapacity(JNIEnv* env, jobject clazz, jint size) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->setDataCapacity(size); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeNative(JNIEnv* env, jobject clazz, - jobject data, jint offset, - jint length) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel == NULL) { - return; - } - void *dest; - - const status_t err = parcel->writeInt32(length); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - - dest = parcel->writeInplace(length); - - if (dest == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - return; - } - - jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0); - if (ar) { - memcpy(dest, ar, length); - env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0); - } -} - - -static void android_os_Parcel_writeInt(JNIEnv* env, jobject clazz, jint val) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeInt32(val); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeLong(JNIEnv* env, jobject clazz, jlong val) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeInt64(val); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeFloat(JNIEnv* env, jobject clazz, jfloat val) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeFloat(val); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeDouble(JNIEnv* env, jobject clazz, jdouble val) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeDouble(val); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeString(JNIEnv* env, jobject clazz, jstring val) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - status_t err = NO_MEMORY; - if (val) { - const jchar* str = env->GetStringCritical(val, 0); - if (str) { - err = parcel->writeString16(str, env->GetStringLength(val)); - env->ReleaseStringCritical(val, str); - } - } else { - err = parcel->writeString16(NULL, 0); - } - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jobject clazz, jobject object) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object)); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jobject clazz, jobject object) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const status_t err = parcel->writeDupFileDescriptor( - env->GetIntField(object, gFileDescriptorOffsets.mDescriptor)); - if (err != NO_ERROR) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - } - } -} - -static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jobject clazz) -{ - jbyteArray ret = NULL; - - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - int32_t len = parcel->readInt32(); - - // sanity check the stored length against the true data size - if (len >= 0 && len <= (int32_t)parcel->dataAvail()) { - ret = env->NewByteArray(len); - - if (ret != NULL) { - jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0); - if (a2) { - const void* data = parcel->readInplace(len); - memcpy(a2, data, len); - env->ReleasePrimitiveArrayCritical(ret, a2, 0); - } - } - } - } - - return ret; -} - -static jint android_os_Parcel_readInt(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - return parcel->readInt32(); - } - return 0; -} - -static jlong android_os_Parcel_readLong(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - return parcel->readInt64(); - } - return 0; -} - -static jfloat android_os_Parcel_readFloat(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - return parcel->readFloat(); - } - return 0; -} - -static jdouble android_os_Parcel_readDouble(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - return parcel->readDouble(); - } - return 0; -} - -static jstring android_os_Parcel_readString(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - size_t len; - const char16_t* str = parcel->readString16Inplace(&len); - if (str) { - return env->NewString(str, len); - } - return NULL; - } - return NULL; -} - -static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - return javaObjectForIBinder(env, parcel->readStrongBinder()); - } - return NULL; -} - -static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - int fd = parcel->readFileDescriptor(); - if (fd < 0) return NULL; - fd = dup(fd); - if (fd < 0) return NULL; - jobject object = env->NewObject( - gFileDescriptorOffsets.mClass, gFileDescriptorOffsets.mConstructor); - if (object != NULL) { - //LOGI("Created new FileDescriptor %p with fd %d\n", object, fd); - env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, fd); - } - return object; - } - return NULL; -} - -static jobject android_os_Parcel_openFileDescriptor(JNIEnv* env, jobject clazz, - jstring name, jint mode) -{ - if (name == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return NULL; - } - const jchar* str = env->GetStringCritical(name, 0); - if (str == NULL) { - // Whatever, whatever. - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - String8 name8(str, env->GetStringLength(name)); - env->ReleaseStringCritical(name, str); - int flags=0; - switch (mode&0x30000000) { - case 0: - case 0x10000000: - flags = O_RDONLY; - break; - case 0x20000000: - flags = O_WRONLY; - break; - case 0x30000000: - flags = O_RDWR; - break; - } - - if (mode&0x08000000) flags |= O_CREAT; - if (mode&0x04000000) flags |= O_TRUNC; - if (mode&0x02000000) flags |= O_APPEND; - - int realMode = S_IRWXU|S_IRWXG; - if (mode&0x00000001) realMode |= S_IROTH; - if (mode&0x00000002) realMode |= S_IWOTH; - - int fd = open(name8.string(), flags, realMode); - if (fd < 0) { - jniThrowException(env, "java/io/FileNotFoundException", NULL); - return NULL; - } - jobject object = newFileDescriptor(env, fd); - if (object == NULL) { - close(fd); - } - return object; -} - -static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, jobject clazz, jobject object) -{ - int fd = env->GetIntField(object, gFileDescriptorOffsets.mDescriptor); - if (fd >= 0) { - env->SetIntField(object, gFileDescriptorOffsets.mDescriptor, -1); - //LOGI("Closing ParcelFileDescriptor %d\n", fd); - close(fd); - } -} - -static void android_os_Parcel_freeBuffer(JNIEnv* env, jobject clazz) -{ - int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject); - if (own) { - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - //LOGI("Parcel.freeBuffer() called for C++ Parcel %p\n", parcel); - parcel->freeData(); - } - } -} - -static void android_os_Parcel_init(JNIEnv* env, jobject clazz, jint parcelInt) -{ - Parcel* parcel = (Parcel*)parcelInt; - int own = 0; - if (!parcel) { - //LOGI("Initializing obj %p: creating new Parcel\n", clazz); - own = 1; - parcel = new Parcel; - } else { - //LOGI("Initializing obj %p: given existing Parcel %p\n", clazz, parcel); - } - if (parcel == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - return; - } - //LOGI("Initializing obj %p from C++ Parcel %p, own=%d\n", clazz, parcel, own); - env->SetIntField(clazz, gParcelOffsets.mOwnObject, own); - env->SetIntField(clazz, gParcelOffsets.mObject, (int)parcel); -} - -static void android_os_Parcel_destroy(JNIEnv* env, jobject clazz) -{ - int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject); - if (own) { - Parcel* parcel = parcelForJavaObject(env, clazz); - env->SetIntField(clazz, gParcelOffsets.mObject, 0); - //LOGI("Destroying obj %p: deleting C++ Parcel %p\n", clazz, parcel); - delete parcel; - } else { - env->SetIntField(clazz, gParcelOffsets.mObject, 0); - //LOGI("Destroying obj %p: leaving C++ Parcel %p\n", clazz); - } -} - -static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jobject clazz) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel == NULL) { - return NULL; - } - - // do not marshall if there are binder objects in the parcel - if (parcel->objectsCount()) - { - jniThrowException(env, "java/lang/RuntimeException", "Tried to marshall a Parcel that contained Binder objects."); - return NULL; - } - - jbyteArray ret = env->NewByteArray(parcel->dataSize()); - - if (ret != NULL) - { - jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0); - if (array != NULL) - { - memcpy(array, parcel->data(), parcel->dataSize()); - env->ReleasePrimitiveArrayCritical(ret, array, 0); - } - } - - return ret; -} - -static void android_os_Parcel_unmarshall(JNIEnv* env, jobject clazz, jbyteArray data, jint offset, jint length) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel == NULL || length < 0) { - return; - } - - jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0); - if (array) - { - parcel->setDataSize(length); - parcel->setDataPosition(0); - - void* raw = parcel->writeInplace(length); - memcpy(raw, (array + offset), length); - - env->ReleasePrimitiveArrayCritical(data, array, 0); - } -} - -static void android_os_Parcel_appendFrom(JNIEnv* env, jobject clazz, jobject parcel, jint offset, jint length) -{ - Parcel* thisParcel = parcelForJavaObject(env, clazz); - if (thisParcel == NULL) { - return; - } - Parcel* otherParcel = parcelForJavaObject(env, parcel); - if (otherParcel == NULL) { - return; - } - - (void) thisParcel->appendFrom(otherParcel, offset, length); -} - -static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jobject clazz) -{ - jboolean ret = JNI_FALSE; - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - if (parcel->hasFileDescriptors()) { - ret = JNI_TRUE; - } - } - return ret; -} - -static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jobject clazz, jstring name) -{ - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - // In the current implementation, the token is just the serialized interface name that - // the caller expects to be invoking - const jchar* str = env->GetStringCritical(name, 0); - if (str != NULL) { - parcel->writeInterfaceToken(String16(str, env->GetStringLength(name))); - env->ReleaseStringCritical(name, str); - } - } -} - -static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstring name) -{ - jboolean ret = JNI_FALSE; - - Parcel* parcel = parcelForJavaObject(env, clazz); - if (parcel != NULL) { - const jchar* str = env->GetStringCritical(name, 0); - if (str) { - IPCThreadState* threadState = IPCThreadState::self(); - const int32_t oldPolicy = threadState->getStrictModePolicy(); - const bool isValid = parcel->enforceInterface( - String16(str, env->GetStringLength(name)), - threadState); - env->ReleaseStringCritical(name, str); - if (isValid) { - const int32_t newPolicy = threadState->getStrictModePolicy(); - if (oldPolicy != newPolicy) { - // Need to keep the Java-level thread-local strict - // mode policy in sync for the libcore - // enforcements, which involves an upcall back - // into Java. (We can't modify the - // Parcel.enforceInterface signature, as it's - // pseudo-public, and used via AIDL - // auto-generation...) - set_dalvik_blockguard_policy(env, newPolicy); - } - return; // everything was correct -> return silently - } - } - } - - // all error conditions wind up here - jniThrowException(env, "java/lang/SecurityException", - "Binder invocation to an incorrect interface"); -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gParcelMethods[] = { - {"dataSize", "()I", (void*)android_os_Parcel_dataSize}, - {"dataAvail", "()I", (void*)android_os_Parcel_dataAvail}, - {"dataPosition", "()I", (void*)android_os_Parcel_dataPosition}, - {"dataCapacity", "()I", (void*)android_os_Parcel_dataCapacity}, - {"setDataSize", "(I)V", (void*)android_os_Parcel_setDataSize}, - {"setDataPosition", "(I)V", (void*)android_os_Parcel_setDataPosition}, - {"setDataCapacity", "(I)V", (void*)android_os_Parcel_setDataCapacity}, - {"writeNative", "([BII)V", (void*)android_os_Parcel_writeNative}, - {"writeInt", "(I)V", (void*)android_os_Parcel_writeInt}, - {"writeLong", "(J)V", (void*)android_os_Parcel_writeLong}, - {"writeFloat", "(F)V", (void*)android_os_Parcel_writeFloat}, - {"writeDouble", "(D)V", (void*)android_os_Parcel_writeDouble}, - {"writeString", "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeString}, - {"writeStrongBinder", "(Landroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder}, - {"writeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor}, - {"createByteArray", "()[B", (void*)android_os_Parcel_createByteArray}, - {"readInt", "()I", (void*)android_os_Parcel_readInt}, - {"readLong", "()J", (void*)android_os_Parcel_readLong}, - {"readFloat", "()F", (void*)android_os_Parcel_readFloat}, - {"readDouble", "()D", (void*)android_os_Parcel_readDouble}, - {"readString", "()Ljava/lang/String;", (void*)android_os_Parcel_readString}, - {"readStrongBinder", "()Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder}, - {"internalReadFileDescriptor", "()Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor}, - {"openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor}, - {"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor}, - {"freeBuffer", "()V", (void*)android_os_Parcel_freeBuffer}, - {"init", "(I)V", (void*)android_os_Parcel_init}, - {"destroy", "()V", (void*)android_os_Parcel_destroy}, - {"marshall", "()[B", (void*)android_os_Parcel_marshall}, - {"unmarshall", "([BII)V", (void*)android_os_Parcel_unmarshall}, - {"appendFrom", "(Landroid/os/Parcel;II)V", (void*)android_os_Parcel_appendFrom}, - {"hasFileDescriptors", "()Z", (void*)android_os_Parcel_hasFileDescriptors}, - {"writeInterfaceToken", "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken}, - {"enforceInterface", "(Ljava/lang/String;)V", (void*)android_os_Parcel_enforceInterface}, -}; - -const char* const kParcelPathName = "android/os/Parcel"; - -static int int_register_android_os_Parcel(JNIEnv* env) -{ - jclass clazz; - - clazz = env->FindClass("android/util/Log"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.util.Log"); - gLogOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gLogOffsets.mLogE = env->GetStaticMethodID( - clazz, "e", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I"); - assert(gLogOffsets.mLogE); - - clazz = env->FindClass("java/io/FileDescriptor"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); - gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gFileDescriptorOffsets.mConstructor - = env->GetMethodID(clazz, "", "()V"); - gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I"); - LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL, - "Unable to find descriptor field in java.io.FileDescriptor"); - - clazz = env->FindClass("android/os/ParcelFileDescriptor"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); - gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gParcelFileDescriptorOffsets.mConstructor - = env->GetMethodID(clazz, "", "(Ljava/io/FileDescriptor;)V"); - - clazz = env->FindClass(kParcelPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Parcel"); - - gParcelOffsets.mObject - = env->GetFieldID(clazz, "mObject", "I"); - gParcelOffsets.mOwnObject - = env->GetFieldID(clazz, "mOwnObject", "I"); - - clazz = env->FindClass("android/os/StrictMode"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.StrictMode"); - gStrictModeCallbackOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gStrictModeCallbackOffsets.mCallback = env->GetStaticMethodID( - clazz, "onBinderStrictModePolicyChange", "(I)V"); - LOG_FATAL_IF(gStrictModeCallbackOffsets.mCallback == NULL, - "Unable to find strict mode callback."); - - return AndroidRuntime::registerNativeMethods( - env, kParcelPathName, - gParcelMethods, NELEM(gParcelMethods)); -} - -int register_android_os_Binder(JNIEnv* env) -{ - if (int_register_android_os_Binder(env) < 0) - return -1; - if (int_register_android_os_BinderInternal(env) < 0) - return -1; - if (int_register_android_os_BinderProxy(env) < 0) - return -1; - if (int_register_android_os_Parcel(env) < 0) - return -1; - return 0; -} - -namespace android { - -// Returns the Unix file descriptor for a ParcelFileDescriptor object -int getParcelFileDescriptorFD(JNIEnv* env, jobject object) -{ - return env->GetIntField(object, gFileDescriptorOffsets.mDescriptor); -} - -} diff --git a/jni/android_util_Binder.h b/jni/android_util_Binder.h deleted file mode 100644 index 495e76a9..00000000 --- a/jni/android_util_Binder.h +++ /dev/null @@ -1,35 +0,0 @@ -/* //device/libs/android_runtime/android_util_Binder.h -** -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include - -#include "jni.h" - -namespace android { - -// Converstion to/from Java IBinder Object and C++ IBinder instance. -extern jobject javaObjectForIBinder(JNIEnv* env, const sp& val); -extern sp ibinderForJavaObject(JNIEnv* env, jobject obj); - -// Conversion from Java Parcel Object to C++ Parcel instance. -// Note: does not type checking; must guarantee jobject is a Java Parcel -extern Parcel* parcelForJavaObject(JNIEnv* env, jobject obj); - -extern jobject newFileDescriptor(JNIEnv* env, int fd); -extern jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc); - -} diff --git a/jni/include/utils/Log.h b/jni/include/utils/Log.h deleted file mode 100644 index 3c6cc8bd..00000000 --- a/jni/include/utils/Log.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// C/C++ logging functions. See the logging documentation for API details. -// -// We'd like these to be available from C code (in case we import some from -// somewhere), so this has a C interface. -// -// The output will be correct when the log file is shared between multiple -// threads and/or multiple processes so long as the operating system -// supports O_APPEND. These calls have mutex-protected data structures -// and so are NOT reentrant. Do not use LOG in a signal handler. -// -#ifndef _LIBS_UTILS_LOG_H -#define _LIBS_UTILS_LOG_H - -#include - -#endif // _LIBS_UTILS_LOG_H diff --git a/jni/net_sqlcipher_CursorWindow.cpp b/jni/net_sqlcipher_CursorWindow.cpp deleted file mode 100644 index c1e53af0..00000000 --- a/jni/net_sqlcipher_CursorWindow.cpp +++ /dev/null @@ -1,723 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "CursorWindow" - -#include -#include -#include - -#include - -#include -#include -#include - -#include "CursorWindow.h" -#include "sqlite3_exception.h" -#include "android_util_Binder.h" - -namespace sqlcipher { - -static jfieldID gWindowField; -static jfieldID gBufferField; -static jfieldID gSizeCopiedField; - -#define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField)) -#define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window)) -#define SET_BUFFER(env, object, buf) (env->SetObjectField(object, gBufferField, buf)) -#define SET_SIZE_COPIED(env, object, size) (env->SetIntField(object, gSizeCopiedField, size)) - -CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow) -{ - return GET_WINDOW(env, javaWindow); -} - -static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly) -{ - uint8_t * data; - size_t size; - CursorWindow * window; - - window = new CursorWindow(MAX_WINDOW_SIZE); - if (!window) { - jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object"); - return; - } - - if (!window->initBuffer(localOnly)) { - jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window"); - delete window; - return; - } - -LOG_WINDOW("native_init_empty: window = %p", window); - SET_WINDOW(env, object, window); -} - -static void native_init_memory(JNIEnv * env, jobject object, jobject memObj) -{ - android::sp memory = android::interface_cast(android::ibinderForJavaObject(env, memObj)); - - if (memory == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder"); - return; - } - - CursorWindow * window = new CursorWindow(); - if (!window) { - jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object"); - return; - } - if (!window->setMemory(memory)) { - jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj"); - delete window; - return; - } - -LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window); - SET_WINDOW(env, object, window); -} - -static jobject native_getBinder(JNIEnv * env, jobject object) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (window) { - android::sp memory = window->getMemory(); - if (memory != NULL) { - android::sp binder = memory->asBinder(); - return javaObjectForIBinder(env, binder); - } - } - return NULL; -} - -static void native_clear(JNIEnv * env, jobject object) -{ - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Clearing window %p", window); - if (window == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", "clear() called after close()"); - return; - } - window->clear(); -} - -static void native_close(JNIEnv * env, jobject object) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (window) { -LOG_WINDOW("Closing window %p", window); - delete window; - SET_WINDOW(env, object, 0); - } -} - -static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column) -{ - char buf[100]; - snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column); - jniThrowException(env, "java/lang/IllegalStateException", buf); -} - -static void throwUnknowTypeException(JNIEnv * env, jint type) -{ - char buf[80]; - snprintf(buf, sizeof(buf), "UNKNOWN type %d", type); - jniThrowException(env, "java/lang/IllegalStateException", buf); -} - -static jlong getLong_native(JNIEnv * env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Getting long for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return 0; - } - - uint8_t type = field.type; - if (type == FIELD_TYPE_INTEGER) { - int64_t value; - if (window->getLong(row, column, &value)) { - return value; - } - return 0; - } else if (type == FIELD_TYPE_STRING) { - uint32_t size = field.data.buffer.size; - if (size > 0) { -#if WINDOW_STORAGE_UTF8 - return strtoll((char const *)window->offsetToPtr(field.data.buffer.offset), NULL, 0); -#else - String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2); - char const * str = ascii.string(); - return strtoll(str, NULL, 0); -#endif - } else { - return 0; - } - } else if (type == FIELD_TYPE_FLOAT) { - double value; - if (window->getDouble(row, column, &value)) { - return value; - } - return 0; - } else if (type == FIELD_TYPE_NULL) { - return 0; - } else if (type == FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to long"); - return 0; - } else { - throwUnknowTypeException(env, type); - return 0; - } -} - -static jbyteArray getBlob_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - uint8_t type = field.type; - if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) { - jbyteArray byteArray = env->NewByteArray(field.data.buffer.size); - LOG_ASSERT(byteArray, "Native could not create new byte[]"); - env->SetByteArrayRegion(byteArray, 0, field.data.buffer.size, - (const jbyte*)window->offsetToPtr(field.data.buffer.offset)); - return byteArray; - } else if (type == FIELD_TYPE_INTEGER) { - throw_sqlite3_exception(env, "INTEGER data in getBlob_native "); - } else if (type == FIELD_TYPE_FLOAT) { - throw_sqlite3_exception(env, "FLOAT data in getBlob_native "); - } else if (type == FIELD_TYPE_NULL) { - // do nothing - } else { - throwUnknowTypeException(env, type); - } - return NULL; -} - -static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Checking if column is a blob or null for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - return field.type == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL; -} - -static jboolean isString_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Checking if column is a string or null for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - return field.type == FIELD_TYPE_STRING || field.type == FIELD_TYPE_NULL; -} - -static jboolean isInteger_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Checking if column is an integer for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - return field.type == FIELD_TYPE_INTEGER; -} - -static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Checking if column is a float for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - return field.type == FIELD_TYPE_FLOAT; -} - -static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Getting string for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - uint8_t type = field.type; - if (type == FIELD_TYPE_STRING) { - uint32_t size = field.data.buffer.size; - if (size > 0) { -#if WINDOW_STORAGE_UTF8 - // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string - android::String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1); - return env->NewString((jchar const *)utf16.string(), utf16.size()); -#else - return env->NewString((jchar const *)window->offsetToPtr(field.data.buffer.offset), size / 2); -#endif - } else { - return env->NewStringUTF(""); - } - } else if (type == FIELD_TYPE_INTEGER) { - int64_t value; - if (window->getLong(row, column, &value)) { - char buf[32]; - snprintf(buf, sizeof(buf), "%lld", value); - return env->NewStringUTF(buf); - } - return NULL; - } else if (type == FIELD_TYPE_FLOAT) { - double value; - if (window->getDouble(row, column, &value)) { - char buf[32]; - snprintf(buf, sizeof(buf), "%g", value); - return env->NewStringUTF(buf); - } - return NULL; - } else if (type == FIELD_TYPE_NULL) { - return NULL; - } else if (type == FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to string"); - return NULL; - } else { - throwUnknowTypeException(env, type); - return NULL; - } -} - -/** - * Use this only to convert characters that are known to be within the - * 0-127 range for direct conversion to UTF-16 - */ -static jint charToJchar(const char* src, jchar* dst, jint bufferSize) -{ - int32_t len = strlen(src); - - if (bufferSize < len) { - len = bufferSize; - } - - for (int i = 0; i < len; i++) { - *dst++ = (*src++ & 0x7F); - } - return len; -} - -static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row, - jint column, jint bufferSize, jobject buf) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Copying string for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot"); - return NULL; - } - - jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField); - if (buffer == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null"); - return NULL; - } - jchar* dst = env->GetCharArrayElements(buffer, NULL); - uint8_t type = field.type; - uint32_t sizeCopied = 0; - jcharArray newArray = NULL; - if (type == FIELD_TYPE_STRING) { - uint32_t size = field.data.buffer.size; - if (size > 0) { -#if WINDOW_STORAGE_UTF8 - // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string - android::String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1); - int32_t strSize = utf16.size(); - if (strSize > bufferSize || dst == NULL) { - newArray = env->NewCharArray(strSize); - env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string()); - } else { - memcpy(dst, (jchar const *)utf16.string(), strSize * 2); - } - sizeCopied = strSize; -#else - sizeCopied = size/2 + size % 2; - if (size > bufferSize * 2 || dst == NULL) { - newArray = env->NewCharArray(sizeCopied); - memcpy(newArray, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size); - } else { - memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size); - } -#endif - } - } else if (type == FIELD_TYPE_INTEGER) { - int64_t value; - if (window->getLong(row, column, &value)) { - char buf[32]; - int len; - snprintf(buf, sizeof(buf), "%lld", value); - jchar* dst = env->GetCharArrayElements(buffer, NULL); - sizeCopied = charToJchar(buf, dst, bufferSize); - } - } else if (type == FIELD_TYPE_FLOAT) { - double value; - if (window->getDouble(row, column, &value)) { - char tempbuf[32]; - snprintf(tempbuf, sizeof(tempbuf), "%g", value); - jchar* dst = env->GetCharArrayElements(buffer, NULL); - sizeCopied = charToJchar(tempbuf, dst, bufferSize); - } - } else if (type == FIELD_TYPE_NULL) { - } else if (type == FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to string"); - } else { - LOGE("Unknown field type %d", type); - throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()"); - } - SET_SIZE_COPIED(env, buf, sizeCopied); - env->ReleaseCharArrayElements(buffer, dst, JNI_OK); - return newArray; -} - -static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column) -{ - int32_t err; - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); - - field_slot_t field; - err = window->read_field_slot(row, column, &field); - if (err != 0) { - throwExceptionWithRowCol(env, row, column); - return 0.0; - } - - uint8_t type = field.type; - if (type == FIELD_TYPE_FLOAT) { - double value; - if (window->getDouble(row, column, &value)) { - return value; - } - return 0.0; - } else if (type == FIELD_TYPE_STRING) { - uint32_t size = field.data.buffer.size; - if (size > 0) { -#if WINDOW_STORAGE_UTF8 - return strtod((char const *)window->offsetToPtr(field.data.buffer.offset), NULL); -#else - String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2); - char const * str = ascii.string(); - return strtod(str, NULL); -#endif - } else { - return 0.0; - } - } else if (type == FIELD_TYPE_INTEGER) { - int64_t value; - if (window->getLong(row, column, &value)) { - return (double) value; - } - return 0.0; - } else if (type == FIELD_TYPE_NULL) { - return 0.0; - } else if (type == FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to double"); - return 0.0; - } else { - throwUnknowTypeException(env, type); - return 0.0; - } -} - -static jboolean isNull_native(JNIEnv* env, jobject object, jint row, jint column) -{ - CursorWindow * window = GET_WINDOW(env, object); -LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window); - - bool isNull; - if (window->getNull(row, column, &isNull)) { - return isNull; - } - - //TODO throw execption? - return true; -} - -static jint getNumRows(JNIEnv * env, jobject object) -{ - CursorWindow * window = GET_WINDOW(env, object); - return window->getNumRows(); -} - -static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum) -{ - CursorWindow * window = GET_WINDOW(env, object); - return window->setNumColumns(columnNum); -} - -static jboolean allocRow(JNIEnv * env, jobject object) -{ - CursorWindow * window = GET_WINDOW(env, object); - return window->allocRow() != NULL; -} - -static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (!value) { - LOG_WINDOW("How did a null value send to here"); - return false; - } - field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); - if (fieldSlot == NULL) { - LOG_WINDOW(" getFieldSlotWithCheck error "); - return false; - } - - jint len = env->GetArrayLength(value); - int offset = window->alloc(len); - if (!offset) { - LOG_WINDOW("Failed allocating %u bytes", len); - return false; - } - jbyte * bytes = env->GetByteArrayElements(value, NULL); - window->copyIn(offset, (uint8_t const *)bytes, len); - - // This must be updated after the call to alloc(), since that - // may move the field around in the window - fieldSlot->type = FIELD_TYPE_BLOB; - fieldSlot->data.buffer.offset = offset; - fieldSlot->data.buffer.size = len; - env->ReleaseByteArrayElements(value, bytes, JNI_ABORT); - LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset); - return true; -} - -static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (!value) { - LOG_WINDOW("How did a null value send to here"); - return false; - } - field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); - if (fieldSlot == NULL) { - LOG_WINDOW(" getFieldSlotWithCheck error "); - return false; - } - -#if WINDOW_STORAGE_UTF8 - int len = env->GetStringUTFLength(value) + 1; - char const * valStr = env->GetStringUTFChars(value, NULL); -#else - int len = env->GetStringLength(value); - // GetStringLength return number of chars and one char takes 2 bytes - len *= 2; - const jchar* valStr = env->GetStringChars(value, NULL); -#endif - if (!valStr) { - LOG_WINDOW("value can't be transfer to UTFChars"); - return false; - } - - int offset = window->alloc(len); - if (!offset) { - LOG_WINDOW("Failed allocating %u bytes", len); -#if WINDOW_STORAGE_UTF8 - env->ReleaseStringUTFChars(value, valStr); -#else - env->ReleaseStringChars(value, valStr); -#endif - return false; - } - - window->copyIn(offset, (uint8_t const *)valStr, len); - - // This must be updated after the call to alloc(), since that - // may move the field around in the window - fieldSlot->type = FIELD_TYPE_STRING; - fieldSlot->data.buffer.offset = offset; - fieldSlot->data.buffer.size = len; - - LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset); -#if WINDOW_STORAGE_UTF8 - env->ReleaseStringUTFChars(value, valStr); -#else - env->ReleaseStringChars(value, valStr); -#endif - - return true; -} - -static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (!window->putLong(row, col, value)) { - LOG_WINDOW(" getFieldSlotWithCheck error "); - return false; - } - - LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value); - - return true; -} - -static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (!window->putDouble(row, col, value)) { - LOG_WINDOW(" getFieldSlotWithCheck error "); - return false; - } - - LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value); - - return true; -} - -static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col) -{ - CursorWindow * window = GET_WINDOW(env, object); - if (!window->putNull(row, col)) { - LOG_WINDOW(" getFieldSlotWithCheck error "); - return false; - } - - LOG_WINDOW("%d,%d is NULL", row, col); - - return true; -} - -// free the last row -static void freeLastRow(JNIEnv * env, jobject object) { - CursorWindow * window = GET_WINDOW(env, object); - window->freeLastRow(); -} - -static JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - {"native_init", "(Z)V", (void *)native_init_empty}, - {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory}, - {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder}, - {"native_clear", "()V", (void *)native_clear}, - {"close_native", "()V", (void *)native_close}, - {"getLong_native", "(II)J", (void *)getLong_native}, - {"getBlob_native", "(II)[B", (void *)getBlob_native}, - {"isBlob_native", "(II)Z", (void *)isBlob_native}, - {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native}, - {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native}, - {"getDouble_native", "(II)D", (void *)getDouble_native}, - {"isNull_native", "(II)Z", (void *)isNull_native}, - {"getNumRows_native", "()I", (void *)getNumRows}, - {"setNumColumns_native", "(I)Z", (void *)setNumColumns}, - {"allocRow_native", "()Z", (void *)allocRow}, - {"putBlob_native", "([BII)Z", (void *)putBlob_native}, - {"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native}, - {"putLong_native", "(JII)Z", (void *)putLong_native}, - {"putDouble_native", "(DII)Z", (void *)putDouble_native}, - {"freeLastRow_native", "()V", (void *)freeLastRow}, - {"putNull_native", "(II)Z", (void *)putNull_native}, - {"isString_native", "(II)Z", (void *)isString_native}, - {"isFloat_native", "(II)Z", (void *)isFloat_native}, - {"isInteger_native", "(II)Z", (void *)isInteger_native}, -}; - -int register_android_database_CursorWindow(JNIEnv * env) -{ - jclass clazz; - - clazz = env->FindClass("net/sqlcipher/CursorWindow"); - if (clazz == NULL) { - LOGE("Can't find net/sqlcipher/CursorWindow"); - return -1; - } - - gWindowField = env->GetFieldID(clazz, "nWindow", "I"); - - if (gWindowField == NULL) { - LOGE("Error locating fields"); - return -1; - } - - clazz = env->FindClass("android/database/CharArrayBuffer"); - if (clazz == NULL) { - LOGE("Can't find android/database/CharArrayBuffer"); - return -1; - } - - gBufferField = env->GetFieldID(clazz, "data", "[C"); - - if (gBufferField == NULL) { - LOGE("Error locating fields data in CharArrayBuffer"); - return -1; - } - - gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I"); - - if (gSizeCopiedField == NULL) { - LOGE("Error locating fields sizeCopied in CharArrayBuffer"); - return -1; - } - - return android::AndroidRuntime::registerNativeMethods(env, "net/sqlcipher/CursorWindow", - sMethods, NELEM(sMethods)); -} - -} // namespace sqlcipher diff --git a/jni/net_sqlcipher_database_SQLiteDatabase.cpp b/jni/net_sqlcipher_database_SQLiteDatabase.cpp deleted file mode 100644 index 812388d7..00000000 --- a/jni/net_sqlcipher_database_SQLiteDatabase.cpp +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2006-2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "Database" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "sqlite3_exception.h" -#include "sqlcipher_loading.h" - -#define UTF16_STORAGE 0 -#define INVALID_VERSION -1 -#define SQLITE_SOFT_HEAP_LIMIT (4 * 1024 * 1024) -#define ANDROID_TABLE "android_metadata" -/* uncomment the next line to force-enable logging of all statements */ -// #define DB_LOG_STATEMENTS - - - -namespace sqlcipher { - - -enum { - OPEN_READWRITE = 0x00000000, - OPEN_READONLY = 0x00000001, - OPEN_READ_MASK = 0x00000001, - NO_LOCALIZED_COLLATORS = 0x00000010, - CREATE_IF_NECESSARY = 0x10000000 -}; - -static jfieldID offset_db_handle; - -static char *createStr(const char *path) { - int len = strlen(path); - char *str = (char *)malloc(len + 1); - strncpy(str, path, len); - str[len] = NULL; - return str; -} - -static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) { - // skip printing this message if it is due to certain types of errors - if (iErrCode == SQLITE_CONSTRAINT) return; - LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg); -} - -// register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called. -static void registerLoggingFunc(const char *path) { - static bool loggingFuncSet = false; - if (loggingFuncSet) { - return; - } - - LOGV("Registering sqlite logging func \n"); - int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path)); - if (err != SQLITE_OK) { - LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err); - return; - } - loggingFuncSet = true; -} - -/* public native void setICURoot(String path); */ -void setICURoot(JNIEnv* env, jobject object, jstring ICURoot) -{ - char const * ICURootPath = env->GetStringUTFChars(ICURoot, NULL); - setenv("SQLCIPHER_ICU_PREFIX", ICURootPath, 1); - env->ReleaseStringUTFChars(ICURoot, ICURootPath); -} - - -/* public native void dbopen(String path, int flags, String locale); */ -void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags) -{ - int err; - sqlite3 * handle = NULL; - sqlite3_stmt * statement = NULL; - char const * path8 = env->GetStringUTFChars(pathString, NULL); - int sqliteFlags; - - // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called. - registerLoggingFunc(path8); - - // convert our flags into the sqlite flags - if (flags & CREATE_IF_NECESSARY) { - sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; - } else if (flags & OPEN_READONLY) { - sqliteFlags = SQLITE_OPEN_READONLY; - } else { - sqliteFlags = SQLITE_OPEN_READWRITE; - } - - err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL); - if (err != SQLITE_OK) { - LOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags); - throw_sqlite3_exception(env, handle); - goto done; - } - - // The soft heap limit prevents the page cache allocations from growing - // beyond the given limit, no matter what the max page cache sizes are - // set to. The limit does not, as of 3.5.0, affect any other allocations. - sqlite3_soft_heap_limit(SQLITE_SOFT_HEAP_LIMIT); - - // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY - err = sqlite3_busy_timeout(handle, 1000 /* ms */); - if (err != SQLITE_OK) { - LOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8); - throw_sqlite3_exception(env, handle); - goto done; - } - -#ifdef DB_INTEGRITY_CHECK - static const char* integritySql = "pragma integrity_check(1);"; - err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL); - if (err != SQLITE_OK) { - LOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8); - throw_sqlite3_exception(env, handle); - goto done; - } - - // first is OK or error message - err = sqlite3_step(statement); - if (err != SQLITE_ROW) { - LOGE("integrity check failed for \"%s\"\n", integritySql, path8); - throw_sqlite3_exception(env, handle); - goto done; - } else { - const char *text = (const char*)sqlite3_column_text(statement, 0); - if (strcmp(text, "ok") != 0) { - LOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text); - jniThrowException(env, "net/sqlcipher/database/SQLiteDatabaseCorruptException", text); - goto done; - } - } -#endif - - err = register_android_functions(handle, UTF16_STORAGE); - if (err) { - throw_sqlite3_exception(env, handle); - goto done; - } - - sqlite3_enable_load_extension(handle, 1); - - LOGV("Opened '%s' - %p\n", path8, handle); - env->SetIntField(object, offset_db_handle, (int) handle); - handle = NULL; // The caller owns the handle now. - -done: - // Release allocated resources - if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8); - if (statement != NULL) sqlite3_finalize(statement); - if (handle != NULL) sqlite3_close(handle); -} - -static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName) { - char const *path = env->GetStringUTFChars(databaseName, NULL); - if (path == NULL) { - LOGE("Failure in getDatabaseName(). VM ran out of memory?\n"); - return NULL; // VM would have thrown OutOfMemoryError - } - char *dbNameStr = createStr(path); - env->ReleaseStringUTFChars(databaseName, path); - return dbNameStr; -} - -static void sqlTrace(void *databaseName, const char *sql) { - LOGI("sql_statement|%s|%s\n", (char *)databaseName, sql); -} - -/* public native void enableSqlTracing(); */ -static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName)); -} - -static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) { - double d = tm/1000000.0; - LOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql); -} - -/* public native void enableSqlProfiling(); */ -static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName)); -} - - -/* public native void close(); */ -static void dbclose(JNIEnv* env, jobject object) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - - if (handle != NULL) { - // release the memory associated with the traceFuncArg in enableSqlTracing function - void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL); - if (traceFuncArg != NULL) { - free(traceFuncArg); - } - // release the memory associated with the traceFuncArg in enableSqlProfiling function - traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL); - if (traceFuncArg != NULL) { - free(traceFuncArg); - } - LOGV("Closing database: handle=%p\n", handle); - int result = sqlite3_close(handle); - if (result == SQLITE_OK) { - LOGV("Closed %p\n", handle); - env->SetIntField(object, offset_db_handle, 0); - } else { - // This can happen if sub-objects aren't closed first. Make sure the caller knows. - throw_sqlite3_exception(env, handle); - LOGE("sqlite3_close(%p) failed: %d\n", handle, result); - } - } -} - -/* public native void native_execSQL(String sql); */ -static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString) -{ - int err; - int stepErr; - sqlite3_stmt * statement = NULL; - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - jchar const * sql = env->GetStringChars(sqlString, NULL); - jsize sqlLen = env->GetStringLength(sqlString); - - if (sql == NULL || sqlLen == 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string"); - return; - } - - err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); - - env->ReleaseStringChars(sqlString, sql); - - if (err != SQLITE_OK) { - char const * sql8 = env->GetStringUTFChars(sqlString, NULL); - LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8); - throw_sqlite3_exception(env, handle, sql8); - env->ReleaseStringUTFChars(sqlString, sql8); - return; - } - - stepErr = sqlite3_step(statement); - err = sqlite3_finalize(statement); - - if (stepErr != SQLITE_DONE) { - if (stepErr == SQLITE_ROW) { - throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead."); - } else { - char const * sql8 = env->GetStringUTFChars(sqlString, NULL); - LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8); - throw_sqlite3_exception(env, handle, sql8); - env->ReleaseStringUTFChars(sqlString, sql8); - - } - } else -#ifndef DB_LOG_STATEMENTS -// IF_LOGV() -#endif - { - char const * sql8 = env->GetStringUTFChars(sqlString, NULL); - LOGV("Success on %p when executing '%s'\n", handle, sql8); - env->ReleaseStringUTFChars(sqlString, sql8); - } -} - -/* native long lastInsertRow(); */ -static jlong lastInsertRow(JNIEnv* env, jobject object) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - - return sqlite3_last_insert_rowid(handle); -} - -/* native int lastChangeCount(); */ -static jint lastChangeCount(JNIEnv* env, jobject object) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - - return sqlite3_changes(handle); -} - -/* native int native_getDbLookaside(); */ -static jint native_getDbLookaside(JNIEnv* env, jobject object) -{ - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - int pCur = -1; - int unused; - sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0); - return pCur; -} - -/* set locale in the android_metadata table, install localized collators, and rebuild indexes */ -static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags) -{ - if ((flags & NO_LOCALIZED_COLLATORS)) return; - - int err; - char const* locale8 = env->GetStringUTFChars(localeString, NULL); - sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); - sqlite3_stmt* stmt = NULL; - char** meta = NULL; - int rowCount, colCount; - char* dbLocale = NULL; - - // create the table, if necessary and possible - if (!(flags & OPEN_READONLY)) { - static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)"; - err = sqlite3_exec(handle, createSql, NULL, NULL, NULL); - if (err != SQLITE_OK) { - LOGE("CREATE TABLE " ANDROID_TABLE " failed\n"); - throw_sqlite3_exception(env, handle); - goto done; - } - } - - // try to read from the table - static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1"; - err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL); - if (err != SQLITE_OK) { - LOGE("SELECT locale FROM " ANDROID_TABLE " failed\n"); - throw_sqlite3_exception(env, handle); - goto done; - } - - dbLocale = (rowCount >= 1) ? meta[colCount] : NULL; - - if (dbLocale != NULL && !strcmp(dbLocale, locale8)) { - // database locale is the same as the desired locale; set up the collators and go - err = register_localized_collators(handle, locale8, UTF16_STORAGE); - if (err != SQLITE_OK) throw_sqlite3_exception(env, handle); - goto done; // no database changes needed - } - - if ((flags & OPEN_READONLY)) { - // read-only database, so we're going to have to put up with whatever we got - // For registering new index. Not for modifing the read-only database. - err = register_localized_collators(handle, locale8, UTF16_STORAGE); - if (err != SQLITE_OK) throw_sqlite3_exception(env, handle); - goto done; - } - - // need to update android_metadata and indexes atomically, so use a transaction... - err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL); - if (err != SQLITE_OK) { - LOGE("BEGIN TRANSACTION failed setting locale\n"); - throw_sqlite3_exception(env, handle); - goto done; - } - - err = register_localized_collators(handle, locale8, UTF16_STORAGE); - if (err != SQLITE_OK) { - LOGE("register_localized_collators() failed setting locale\n"); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL); - if (err != SQLITE_OK) { - LOGE("DELETE failed setting locale\n"); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);"; - err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL); - if (err != SQLITE_OK) { - LOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT); - if (err != SQLITE_OK) { - LOGE("sqlite3_bind_text() failed setting locale\n"); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - err = sqlite3_step(stmt); - if (err != SQLITE_OK && err != SQLITE_DONE) { - LOGE("sqlite3_step(\"%s\") failed setting locale\n", sql); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL); - if (err != SQLITE_OK) { - LOGE("REINDEX LOCALIZED failed\n"); - throw_sqlite3_exception(env, handle); - goto rollback; - } - - // all done, yay! - err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL); - if (err != SQLITE_OK) { - LOGE("COMMIT TRANSACTION failed setting locale\n"); - throw_sqlite3_exception(env, handle); - goto done; - } - -rollback: - if (err != SQLITE_OK) { - sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); - } - -done: - if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8); - if (stmt != NULL) sqlite3_finalize(stmt); - if (meta != NULL) sqlite3_free_table(meta); -} - -static jint native_releaseMemory(JNIEnv *env, jobject clazz) -{ - // Attempt to release as much memory from the - return sqlite3_release_memory(SQLITE_SOFT_HEAP_LIMIT); -} - -static JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen}, - {"dbclose", "()V", (void *)dbclose}, - {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing}, - {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling}, - {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL}, - {"lastInsertRow", "()J", (void *)lastInsertRow}, - {"lastChangeCount", "()I", (void *)lastChangeCount}, - {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale}, - {"native_getDbLookaside", "()I", (void *)native_getDbLookaside}, - {"releaseMemory", "()I", (void *)native_releaseMemory}, - {"setICURoot", "(Ljava/lang/String;)V", (void *)setICURoot}, -}; - -int register_android_database_SQLiteDatabase(JNIEnv *env) -{ - jclass clazz; - - clazz = env->FindClass("net/sqlcipher/database/SQLiteDatabase"); - if (clazz == NULL) { - LOGE("Can't find net/sqlcipher/database/SQLiteDatabase\n"); - return -1; - } - - offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "I"); - if (offset_db_handle == NULL) { - LOGE("Can't find SQLiteDatabase.mNativeHandle\n"); - return -1; - } - - return android::AndroidRuntime::registerNativeMethods(env, "net/sqlcipher/database/SQLiteDatabase", sMethods, NELEM(sMethods)); -} - - - - -//this code is not executed -extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - JNIEnv *env; - //gJavaVM = vm; - LOGI("JNI_OnLoad called"); - if (vm->GetEnv((void**) &env, JNI_VERSION_1_2) != JNI_OK) { - LOGE("Failed to get the environment using GetEnv()"); - return -1; - } - - LOGI("JNI_OnLoad register methods "); - - register_android_database_SQLiteDatabase(env); - register_android_database_SQLiteCompiledSql(env); - - register_android_database_SQLiteQuery(env); - - register_android_database_SQLiteProgram(env); - - register_android_database_SQLiteStatement(env); - - register_android_database_CursorWindow(env); - - //register_android_database_SQLiteDebug(env); - -return JNI_VERSION_1_2; - -} - -/* throw a SQLiteException with a message appropriate for the error in handle */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) { - throw_sqlite3_exception(env, handle, NULL); -} - -/* throw a SQLiteException with the given message */ -void throw_sqlite3_exception(JNIEnv* env, const char* message) { - throw_sqlite3_exception(env, NULL, message); -} - -/* throw a SQLiteException with a message appropriate for the error in handle - concatenated with the given message - */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) { - if (handle) { - throw_sqlite3_exception(env, sqlite3_errcode(handle), - sqlite3_errmsg(handle), message); - } else { - // we use SQLITE_OK so that a generic SQLiteException is thrown; - // any code not specified in the switch statement below would do. - throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message); - } -} - -/* throw a SQLiteException for a given error code */ -void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) { - if (errcode == SQLITE_DONE) { - throw_sqlite3_exception(env, errcode, NULL, message); - } else { - char temp[21]; - sprintf(temp, "error code %d", errcode); - throw_sqlite3_exception(env, errcode, temp, message); - } -} - -/* throw a SQLiteException for a given error code, sqlite3message, and - user message - */ -void throw_sqlite3_exception(JNIEnv* env, int errcode, - const char* sqlite3Message, const char* message) { - const char* exceptionClass; - switch (errcode) { - case SQLITE_IOERR: - exceptionClass = "net/sqlcipher/database/SQLiteDiskIOException"; - break; - case SQLITE_CORRUPT: - exceptionClass = "net/sqlcipher/database/SQLiteDatabaseCorruptException"; - break; - case SQLITE_CONSTRAINT: - exceptionClass = "net/sqlcipher/database/SQLiteConstraintException"; - break; - case SQLITE_ABORT: - exceptionClass = "net/sqlcipher/database/SQLiteAbortException"; - break; - case SQLITE_DONE: - exceptionClass = "net/sqlcipher/database/SQLiteDoneException"; - break; - case SQLITE_FULL: - exceptionClass = "net/sqlcipher/database/SQLiteFullException"; - break; - case SQLITE_MISUSE: - exceptionClass = "net/sqlcipher/database/SQLiteMisuseException"; - break; - default: - exceptionClass = "net/sqlcipher/database/sqlcipher/SQLiteException"; - break; - } - - if (sqlite3Message != NULL && message != NULL) { - char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3); - if (fullMessage != NULL) { - strcpy(fullMessage, sqlite3Message); - strcat(fullMessage, ": "); - strcat(fullMessage, message); - jniThrowException(env, exceptionClass, fullMessage); - free(fullMessage); - } else { - jniThrowException(env, exceptionClass, sqlite3Message); - } - } else if (sqlite3Message != NULL) { - jniThrowException(env, exceptionClass, sqlite3Message); - } else { - jniThrowException(env, exceptionClass, message); - } -} - - -} // namespace sqlcipher diff --git a/jni/tinyutils/smartpointer.h b/jni/tinyutils/smartpointer.h deleted file mode 100644 index 88032d7f..00000000 --- a/jni/tinyutils/smartpointer.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * smartpointer.h - * Android - * - * Copyright 2005 The Android Open Source Project - * - */ - -#ifndef ANDROID_SMART_POINTER_H -#define ANDROID_SMART_POINTER_H - -#include -#include -#include - -// --------------------------------------------------------------------------- -namespace android { - -// --------------------------------------------------------------------------- - -#define COMPARE(_op_) \ -inline bool operator _op_ (const sp& o) const { \ - return m_ptr _op_ o.m_ptr; \ -} \ -inline bool operator _op_ (const T* o) const { \ - return m_ptr _op_ o; \ -} \ -template \ -inline bool operator _op_ (const sp& o) const { \ - return m_ptr _op_ o.m_ptr; \ -} \ -template \ -inline bool operator _op_ (const U* o) const { \ - return m_ptr _op_ o; \ -} - -// --------------------------------------------------------------------------- - -template -class sp -{ -public: - inline sp() : m_ptr(0) { } - - sp(T* other); - sp(const sp& other); - template sp(U* other); - template sp(const sp& other); - - ~sp(); - - // Assignment - - sp& operator = (T* other); - sp& operator = (const sp& other); - - template sp& operator = (const sp& other); - template sp& operator = (U* other); - - // Reset - void clear(); - - // Accessors - - inline T& operator* () const { return *m_ptr; } - inline T* operator-> () const { return m_ptr; } - inline T* get() const { return m_ptr; } - - // Operators - - COMPARE(==) - COMPARE(!=) - COMPARE(>) - COMPARE(<) - COMPARE(<=) - COMPARE(>=) - -private: - template friend class sp; - - T* m_ptr; -}; - -// --------------------------------------------------------------------------- -// No user serviceable parts below here. - -template -sp::sp(T* other) - : m_ptr(other) -{ - if (other) other->incStrong(this); -} - -template -sp::sp(const sp& other) - : m_ptr(other.m_ptr) -{ - if (m_ptr) m_ptr->incStrong(this); -} - -template template -sp::sp(U* other) : m_ptr(other) -{ - if (other) other->incStrong(this); -} - -template template -sp::sp(const sp& other) - : m_ptr(other.m_ptr) -{ - if (m_ptr) m_ptr->incStrong(this); -} - -template -sp::~sp() -{ - if (m_ptr) m_ptr->decStrong(this); -} - -template -sp& sp::operator = (const sp& other) { - if (other.m_ptr) other.m_ptr->incStrong(this); - if (m_ptr) m_ptr->decStrong(this); - m_ptr = other.m_ptr; - return *this; -} - -template -sp& sp::operator = (T* other) -{ - if (other) other->incStrong(this); - if (m_ptr) m_ptr->decStrong(this); - m_ptr = other; - return *this; -} - -template template -sp& sp::operator = (const sp& other) -{ - if (other.m_ptr) other.m_ptr->incStrong(this); - if (m_ptr) m_ptr->decStrong(this); - m_ptr = other.m_ptr; - return *this; -} - -template template -sp& sp::operator = (U* other) -{ - if (other) other->incStrong(this); - if (m_ptr) m_ptr->decStrong(this); - m_ptr = other; - return *this; -} - -template -void sp::clear() -{ - if (m_ptr) { - m_ptr->decStrong(this); - m_ptr = 0; - } -} - -// --------------------------------------------------------------------------- - -}; // namespace android - -// --------------------------------------------------------------------------- - -#endif // ANDROID_SMART_POINTER_H diff --git a/libs/commons-codec.jar b/libs/commons-codec.jar deleted file mode 100644 index 957b6752..00000000 Binary files a/libs/commons-codec.jar and /dev/null differ diff --git a/libs/guava-r09.jar b/libs/guava-r09.jar deleted file mode 100644 index f8da8b1c..00000000 Binary files a/libs/guava-r09.jar and /dev/null differ diff --git a/project.properties b/project.properties deleted file mode 100644 index 5a709453..00000000 --- a/project.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-7 diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png deleted file mode 100644 index 8074c4c5..00000000 Binary files a/res/drawable-hdpi/icon.png and /dev/null differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png deleted file mode 100644 index 1095584e..00000000 Binary files a/res/drawable-ldpi/icon.png and /dev/null differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png deleted file mode 100644 index a07c69fa..00000000 Binary files a/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/res/layout/main.xml b/res/layout/main.xml deleted file mode 100644 index aa86016f..00000000 --- a/res/layout/main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/res/values/strings.xml b/res/values/strings.xml deleted file mode 100644 index 2f042c4d..00000000 --- a/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Hello World! - SQLCipher - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..79439766 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':android-database-sqlcipher' diff --git a/src/example/EventDataSQLHelper.java b/src/example/EventDataSQLHelper.java deleted file mode 100644 index 1f8440c6..00000000 --- a/src/example/EventDataSQLHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -package example; - -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteOpenHelper; -import android.content.Context; -import android.provider.BaseColumns; -import android.util.Log; - -/** Helper to the database, manages versions and creation */ -public class EventDataSQLHelper extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "events.db"; - private static final int DATABASE_VERSION = 1; - - // Table name - public static final String TABLE = "events"; - - // Columns - public static final String TIME = "time"; - public static final String TITLE = "title"; - - public EventDataSQLHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - String sql = "create table " + TABLE + "( " + BaseColumns._ID - + " integer primary key autoincrement, " + TIME + " integer, " - + TITLE + " text not null);"; - Log.d("EventsData", "onCreate: " + sql); - db.execSQL(sql); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion >= newVersion) - return; - - String sql = null; - if (oldVersion == 1) - sql = "alter table " + TABLE + " add note text;"; - if (oldVersion == 2) - sql = ""; - - Log.d("EventsData", "onUpgrade : " + sql); - if (sql != null) - db.execSQL(sql); - } - -} diff --git a/src/example/SQLDemoActivity.java b/src/example/SQLDemoActivity.java deleted file mode 100644 index efb5ca74..00000000 --- a/src/example/SQLDemoActivity.java +++ /dev/null @@ -1,76 +0,0 @@ -package example; - -import android.database.Cursor; -import net.sqlcipher.database.SQLiteDatabase; -import android.app.Activity; -import android.content.ContentValues; -import android.os.Bundle; -import android.util.Log; - -public class SQLDemoActivity extends Activity { - EventDataSQLHelper eventsData; - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - //you must set Context on SQLiteDatabase first - SQLiteDatabase.loadLibs(this); - - String password = "foo123"; - - eventsData = new EventDataSQLHelper(this); - - //then you can open the database using a password - SQLiteDatabase db = eventsData.getWritableDatabase(password); - - for (int i = 1; i < 100; i++) - addEvent("Hello Android Event: " + i, db); - - db.close(); - - db = eventsData.getReadableDatabase(password); - - Cursor cursor = getEvents(db); - showEvents(cursor); - - db.close(); - - } - - @Override - public void onDestroy() { - eventsData.close(); - } - - private void addEvent(String title, SQLiteDatabase db) { - - ContentValues values = new ContentValues(); - values.put(EventDataSQLHelper.TIME, System.currentTimeMillis()); - values.put(EventDataSQLHelper.TITLE, title); - db.insert(EventDataSQLHelper.TABLE, null, values); - } - - private Cursor getEvents(SQLiteDatabase db) { - - Cursor cursor = db.query(EventDataSQLHelper.TABLE, null, null, null, null, - null, null); - - startManagingCursor(cursor); - return cursor; - } - - private void showEvents(Cursor cursor) { - StringBuilder ret = new StringBuilder("Saved Events:\n\n"); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - long time = cursor.getLong(1); - String title = cursor.getString(2); - ret.append(id + ": " + time + ": " + title + "\n"); - } - - Log.i("sqldemo",ret.toString()); - } -} diff --git a/src/net/sqlcipher/CrossProcessCursorWrapper.java b/src/net/sqlcipher/CrossProcessCursorWrapper.java deleted file mode 100644 index 7d66c5ce..00000000 --- a/src/net/sqlcipher/CrossProcessCursorWrapper.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.sqlcipher; - -import android.database.Cursor; -import android.database.CrossProcessCursor; -import android.database.CursorWindow; -import android.database.CursorWrapper; - -public class CrossProcessCursorWrapper extends CursorWrapper implements CrossProcessCursor { - - public CrossProcessCursorWrapper(Cursor cursor) { - super(cursor); - } - - @Override - public CursorWindow getWindow() { - return null; - } - - @Override - public void fillWindow(int position, CursorWindow window) { - if (position < 0 || position > getCount()) { - return; - } - window.acquireReference(); - try { - moveToPosition(position - 1); - window.clear(); - window.setStartPosition(position); - int columnNum = getColumnCount(); - window.setNumColumns(columnNum); - while (moveToNext() && window.allocRow()) { - for (int i = 0; i < columnNum; i++) { - String field = getString(i); - if (field != null) { - if (!window.putString(field, getPosition(), i)) { - window.freeLastRow(); - break; - } - } else { - if (!window.putNull(getPosition(), i)) { - window.freeLastRow(); - break; - } - } - } - } - } catch (IllegalStateException e) { - // simply ignore it - } finally { - window.releaseReference(); - } - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - return true; - } -} diff --git a/src/net/sqlcipher/database/SQLiteAbortException.java b/src/net/sqlcipher/database/SQLiteAbortException.java deleted file mode 100644 index 89b066ce..00000000 --- a/src/net/sqlcipher/database/SQLiteAbortException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that the SQLite program was aborted. - * This can happen either through a call to ABORT in a trigger, - * or as the result of using the ABORT conflict clause. - */ -public class SQLiteAbortException extends SQLiteException { - public SQLiteAbortException() {} - - public SQLiteAbortException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteConstraintException.java b/src/net/sqlcipher/database/SQLiteConstraintException.java deleted file mode 100644 index d9d548f6..00000000 --- a/src/net/sqlcipher/database/SQLiteConstraintException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that an integrity constraint was violated. - */ -public class SQLiteConstraintException extends SQLiteException { - public SQLiteConstraintException() {} - - public SQLiteConstraintException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteDatabase.java b/src/net/sqlcipher/database/SQLiteDatabase.java deleted file mode 100644 index 9054c15e..00000000 --- a/src/net/sqlcipher/database/SQLiteDatabase.java +++ /dev/null @@ -1,2347 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -import net.sqlcipher.CrossProcessCursorWrapper; -import net.sqlcipher.DatabaseUtils; -import net.sqlcipher.SQLException; -import net.sqlcipher.database.SQLiteDebug.DbStats; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.lang.ref.WeakReference; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Pattern; -import java.util.zip.ZipInputStream; - -import android.content.ContentValues; - -import android.content.Context; -import android.database.Cursor; -import android.os.Debug; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Config; -import android.util.Log; -import android.util.Pair; - -import com.google.common.collect.Maps; - -/** - * Exposes methods to manage a SQLite database. - *

SQLiteDatabase has methods to create, delete, execute SQL commands, and - * perform other common database management tasks. - *

See the Notepad sample application in the SDK for an example of creating - * and managing a database. - *

Database names must be unique within an application, not across all - * applications. - * - *

Localized Collation - ORDER BY

- *

In addition to SQLite's default BINARY collator, Android supplies - * two more, LOCALIZED, which changes with the system's current locale - * if you wire it up correctly (XXX a link needed!), and UNICODE, which - * is the Unicode Collation Algorithm and not tailored to the current locale. - */ -public class SQLiteDatabase extends SQLiteClosable { - private static final String TAG = "Database"; - private static final int EVENT_DB_OPERATION = 52000; - private static final int EVENT_DB_CORRUPT = 75004; - - private static void loadICUData(Context context) { - - try { - File applicationFilesDirectory = context.getFilesDir(); - File icuDir = new File(applicationFilesDirectory, "icu"); - if(!icuDir.exists()) icuDir.mkdirs(); - File icuDataFile = new File(icuDir, "icudt46l.dat"); - if(!icuDataFile.exists()) { - ZipInputStream in = new ZipInputStream(context.getAssets().open("icudt46l.zip")); - in.getNextEntry(); - - OutputStream out = new FileOutputStream(icuDataFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - in.close(); - out.flush(); - out.close(); - } - } - catch (Exception e) { - Log.e(TAG, "Error copying icu data file" + e.getMessage()); - } - } - - public static void loadLibs (Context context) - { - System.loadLibrary("stlport_shared"); - System.loadLibrary("sqlcipher_android"); - System.loadLibrary("database_sqlcipher"); - - boolean systemICUFileExists = new File("/system/usr/icu/icudt46l.dat").exists(); - File applicationFilesDirectory = context.getFilesDir(); - String icuRootPath = systemICUFileExists ? "/system/usr" : applicationFilesDirectory.getAbsolutePath(); - setICURoot(icuRootPath); - - if(!systemICUFileExists){ - loadICUData(context); - } - } - - /** - * Algorithms used in ON CONFLICT clause - * http://www.sqlite.org/lang_conflict.html - */ - /** - * When a constraint violation occurs, an immediate ROLLBACK occurs, - * thus ending the current transaction, and the command aborts with a - * return code of SQLITE_CONSTRAINT. If no transaction is active - * (other than the implied transaction that is created on every command) - * then this algorithm works the same as ABORT. - */ - public static final int CONFLICT_ROLLBACK = 1; - - /** - * When a constraint violation occurs,no ROLLBACK is executed - * so changes from prior commands within the same transaction - * are preserved. This is the default behavior. - */ - public static final int CONFLICT_ABORT = 2; - - /** - * When a constraint violation occurs, the command aborts with a return - * code SQLITE_CONSTRAINT. But any changes to the database that - * the command made prior to encountering the constraint violation - * are preserved and are not backed out. - */ - public static final int CONFLICT_FAIL = 3; - - /** - * When a constraint violation occurs, the one row that contains - * the constraint violation is not inserted or changed. - * But the command continues executing normally. Other rows before and - * after the row that contained the constraint violation continue to be - * inserted or updated normally. No error is returned. - */ - public static final int CONFLICT_IGNORE = 4; - - /** - * When a UNIQUE constraint violation occurs, the pre-existing rows that - * are causing the constraint violation are removed prior to inserting - * or updating the current row. Thus the insert or update always occurs. - * The command continues executing normally. No error is returned. - * If a NOT NULL constraint violation occurs, the NULL value is replaced - * by the default value for that column. If the column has no default - * value, then the ABORT algorithm is used. If a CHECK constraint - * violation occurs then the IGNORE algorithm is used. When this conflict - * resolution strategy deletes rows in order to satisfy a constraint, - * it does not invoke delete triggers on those rows. - * This behavior might change in a future release. - */ - public static final int CONFLICT_REPLACE = 5; - - /** - * use the following when no conflict action is specified. - */ - public static final int CONFLICT_NONE = 0; - private static final String[] CONFLICT_VALUES = new String[] - {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; - - /** - * Maximum Length Of A LIKE Or GLOB Pattern - * The pattern matching algorithm used in the default LIKE and GLOB implementation - * of SQLite can exhibit O(N^2) performance (where N is the number of characters in - * the pattern) for certain pathological cases. To avoid denial-of-service attacks - * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes. - * The default value of this limit is 50000. A modern workstation can evaluate - * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly. - * The denial of service problem only comes into play when the pattern length gets - * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns - * are at most a few dozen bytes in length, paranoid application developers may - * want to reduce this parameter to something in the range of a few hundred - * if they know that external users are able to generate arbitrary patterns. - */ - public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; - - /** - * Flag for {@link #openDatabase} to open the database for reading and writing. - * If the disk is full, this may fail even before you actually write anything. - * - * {@more} Note that the value of this flag is 0, so it is the default. - */ - public static final int OPEN_READWRITE = 0x00000000; // update native code if changing - - /** - * Flag for {@link #openDatabase} to open the database for reading only. - * This is the only reliable way to open a database if the disk may be full. - */ - public static final int OPEN_READONLY = 0x00000001; // update native code if changing - - private static final int OPEN_READ_MASK = 0x00000001; // update native code if changing - - /** - * Flag for {@link #openDatabase} to open the database without support for localized collators. - * - * {@more} This causes the collator LOCALIZED not to be created. - * You must be consistent when using this flag to use the setting the database was - * created with. If this is set, {@link #setLocale} will do nothing. - */ - public static final int NO_LOCALIZED_COLLATORS = 0x00000010; // update native code if changing - - /** - * Flag for {@link #openDatabase} to create the database file if it does not already exist. - */ - public static final int CREATE_IF_NECESSARY = 0x10000000; // update native code if changing - - /** - * Indicates whether the most-recently started transaction has been marked as successful. - */ - private boolean mInnerTransactionIsSuccessful; - - /** - * Valid during the life of a transaction, and indicates whether the entire transaction (the - * outer one and all of the inner ones) so far has been successful. - */ - private boolean mTransactionIsSuccessful; - - /** - * Valid during the life of a transaction. - */ - private SQLiteTransactionListener mTransactionListener; - - /** Synchronize on this when accessing the database */ - private final ReentrantLock mLock = new ReentrantLock(true); - - private long mLockAcquiredWallTime = 0L; - private long mLockAcquiredThreadTime = 0L; - - // limit the frequency of complaints about each database to one within 20 sec - // unless run command adb shell setprop log.tag.Database VERBOSE - private static final int LOCK_WARNING_WINDOW_IN_MS = 20000; - /** If the lock is held this long then a warning will be printed when it is released. */ - private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300; - private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100; - private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000; - - private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000; - - // The pattern we remove from database filenames before - // potentially logging them. - private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); - - private long mLastLockMessageTime = 0L; - - // Things related to query logging/sampling for debugging - // slow/frequent queries during development. Always log queries - // which take (by default) 500ms+; shorter queries are sampled - // accordingly. Commit statements, which are typically slow, are - // logged together with the most recently executed SQL statement, - // for disambiguation. The 500ms value is configurable via a - // SystemProperty, but developers actively debugging database I/O - // should probably use the regular log tunable, - // LOG_SLOW_QUERIES_PROPERTY, defined below. - private static int sQueryLogTimeInMillis = 0; // lazily initialized - private static final int QUERY_LOG_SQL_LENGTH = 64; - private static final String COMMIT_SQL = "COMMIT;"; - private final Random mRandom = new Random(); - private String mLastSqlStatement = null; - - // String prefix for slow database query EventLog records that show - // lock acquistions of the database. - /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:"; - - /** Used by native code, do not rename */ - /* package */ int mNativeHandle = 0; - - /** Used to make temp table names unique */ - /* package */ int mTempTableSequence = 0; - - /** The path for the database file */ - private String mPath; - - /** The anonymized path for the database file for logging purposes */ - private String mPathForLogs = null; // lazily populated - - /** The flags passed to open/create */ - private int mFlags; - - /** The optional factory to use when creating new Cursors */ - private CursorFactory mFactory; - - private WeakHashMap mPrograms; - - /** - * for each instance of this class, a cache is maintained to store - * the compiled query statement ids returned by sqlite database. - * key = sql statement with "?" for bind args - * value = {@link SQLiteCompiledSql} - * If an application opens the database and keeps it open during its entire life, then - * there will not be an overhead of compilation of sql statements by sqlite. - * - * why is this cache NOT static? because sqlite attaches compiledsql statements to the - * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is - * invoked. - * - * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method - * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because - * most of the apps don't use "?" syntax in their sql, caching is not useful for them. - */ - /* package */ Map mCompiledQueries = Maps.newHashMap(); - /** - * @hide - */ - public static final int MAX_SQL_CACHE_SIZE = 250; - private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance - private int mCacheFullWarnings; - private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1; - - /** maintain stats about number of cache hits and misses */ - private int mNumCacheHits; - private int mNumCacheMisses; - - /** the following 2 members maintain the time when a database is opened and closed */ - private String mTimeOpened = null; - private String mTimeClosed = null; - - /** Used to find out where this object was created in case it never got closed. */ - private Throwable mStackTrace = null; - - // System property that enables logging of slow queries. Specify the threshold in ms. - private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold"; - private final int mSlowQueryThreshold; - - /** - * @param closable - */ - void addSQLiteClosable(SQLiteClosable closable) { - lock(); - try { - mPrograms.put(closable, null); - } finally { - unlock(); - } - } - - void removeSQLiteClosable(SQLiteClosable closable) { - lock(); - try { - mPrograms.remove(closable); - } finally { - unlock(); - } - } - - @Override - protected void onAllReferencesReleased() { - if (isOpen()) { - if (SQLiteDebug.DEBUG_SQL_CACHE) { - mTimeClosed = getTime(); - } - dbclose(); - } - } - - /** - * Attempts to release memory that SQLite holds but does not require to - * operate properly. Typically this memory will come from the page cache. - * - * @return the number of bytes actually released - */ - static public native int releaseMemory(); - - /** - * Control whether or not the SQLiteDatabase is made thread-safe by using locks - * around critical sections. This is pretty expensive, so if you know that your - * DB will only be used by a single thread then you should set this to false. - * The default is true. - * @param lockingEnabled set to true to enable locks, false otherwise - */ - public void setLockingEnabled(boolean lockingEnabled) { - mLockingEnabled = lockingEnabled; - } - - /** - * If set then the SQLiteDatabase is made thread-safe by using locks - * around critical sections - */ - private boolean mLockingEnabled = true; - - /* package */ void onCorruption() { - Log.e(TAG, "Removing corrupt database: " + mPath); - // EventLog.writeEvent(EVENT_DB_CORRUPT, mPath); - try { - // Close the database (if we can), which will cause subsequent operations to fail. - close(); - } finally { - // Delete the corrupt file. Don't re-create it now -- that would just confuse people - // -- but the next time someone tries to open it, they can set it up from scratch. - if (!mPath.equalsIgnoreCase(":memory")) { - // delete is only for non-memory database files - new File(mPath).delete(); - } - } - } - - /** - * Locks the database for exclusive access. The database lock must be held when - * touch the native sqlite3* object since it is single threaded and uses - * a polling lock contention algorithm. The lock is recursive, and may be acquired - * multiple times by the same thread. This is a no-op if mLockingEnabled is false. - * - * @see #unlock() - */ - /* package */ void lock() { - if (!mLockingEnabled) return; - mLock.lock(); - if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { - if (mLock.getHoldCount() == 1) { - // Use elapsed real-time since the CPU may sleep when waiting for IO - mLockAcquiredWallTime = SystemClock.elapsedRealtime(); - mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); - } - } - } - - /** - * Locks the database for exclusive access. The database lock must be held when - * touch the native sqlite3* object since it is single threaded and uses - * a polling lock contention algorithm. The lock is recursive, and may be acquired - * multiple times by the same thread. - * - * @see #unlockForced() - */ - private void lockForced() { - mLock.lock(); - if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { - if (mLock.getHoldCount() == 1) { - // Use elapsed real-time since the CPU may sleep when waiting for IO - mLockAcquiredWallTime = SystemClock.elapsedRealtime(); - mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); - } - } - } - - /** - * Releases the database lock. This is a no-op if mLockingEnabled is false. - * - * @see #unlock() - */ - /* package */ void unlock() { - if (!mLockingEnabled) return; - if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { - if (mLock.getHoldCount() == 1) { - checkLockHoldTime(); - } - } - mLock.unlock(); - } - - /** - * Releases the database lock. - * - * @see #unlockForced() - */ - private void unlockForced() { - if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { - if (mLock.getHoldCount() == 1) { - checkLockHoldTime(); - } - } - mLock.unlock(); - } - - private void checkLockHoldTime() { - // Use elapsed real-time since the CPU may sleep when waiting for IO - long elapsedTime = SystemClock.elapsedRealtime(); - long lockedTime = elapsedTime - mLockAcquiredWallTime; - if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT && - !Log.isLoggable(TAG, Log.VERBOSE) && - (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) { - return; - } - if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) { - int threadTime = (int) - ((Debug.threadCpuTimeNanos() - mLockAcquiredThreadTime) / 1000000); - if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS || - lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) { - mLastLockMessageTime = elapsedTime; - String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was " - + threadTime + "ms"; - if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) { - Log.d(TAG, msg, new Exception()); - } else { - Log.d(TAG, msg); - } - } - } - } - - /** - * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of - * the work done in that transaction and all of the nested transactions will be committed or - * rolled back. The changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. - * - *

Here is the standard idiom for transactions: - * - *

-     *   db.beginTransaction();
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- */ - public void beginTransaction() { - beginTransactionWithListener(null /* transactionStatusCallback */); - } - - /** - * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of - * the work done in that transaction and all of the nested transactions will be committed or - * rolled back. The changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. - * - *

Here is the standard idiom for transactions: - * - *

-     *   db.beginTransactionWithListener(listener);
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- * @param transactionListener listener that should be notified when the transaction begins, - * commits, or is rolled back, either explicitly or by a call to - * {@link #yieldIfContendedSafely}. - */ - public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { - lockForced(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - boolean ok = false; - try { - // If this thread already had the lock then get out - if (mLock.getHoldCount() > 1) { - if (mInnerTransactionIsSuccessful) { - String msg = "Cannot call beginTransaction between " - + "calling setTransactionSuccessful and endTransaction"; - IllegalStateException e = new IllegalStateException(msg); - Log.e(TAG, "beginTransaction() failed", e); - throw e; - } - ok = true; - return; - } - - // This thread didn't already have the lock, so begin a database - // transaction now. - execSQL("BEGIN EXCLUSIVE;"); - mTransactionListener = transactionListener; - mTransactionIsSuccessful = true; - mInnerTransactionIsSuccessful = false; - if (transactionListener != null) { - try { - transactionListener.onBegin(); - } catch (RuntimeException e) { - execSQL("ROLLBACK;"); - throw e; - } - } - ok = true; - } finally { - if (!ok) { - // beginTransaction is called before the try block so we must release the lock in - // the case of failure. - unlockForced(); - } - } - } - - /** - * End a transaction. See beginTransaction for notes about how to use this and when transactions - * are committed and rolled back. - */ - public void endTransaction() { - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - if (!mLock.isHeldByCurrentThread()) { - throw new IllegalStateException("no transaction pending"); - } - try { - if (mInnerTransactionIsSuccessful) { - mInnerTransactionIsSuccessful = false; - } else { - mTransactionIsSuccessful = false; - } - if (mLock.getHoldCount() != 1) { - return; - } - RuntimeException savedException = null; - if (mTransactionListener != null) { - try { - if (mTransactionIsSuccessful) { - mTransactionListener.onCommit(); - } else { - mTransactionListener.onRollback(); - } - } catch (RuntimeException e) { - savedException = e; - mTransactionIsSuccessful = false; - } - } - if (mTransactionIsSuccessful) { - execSQL(COMMIT_SQL); - } else { - try { - execSQL("ROLLBACK;"); - if (savedException != null) { - throw savedException; - } - } catch (SQLException e) { - if (Config.LOGD) { - Log.d(TAG, "exception during rollback, maybe the DB previously " - + "performed an auto-rollback"); - } - } - } - } finally { - mTransactionListener = null; - unlockForced(); - if (Config.LOGV) { - Log.v(TAG, "unlocked " + Thread.currentThread() - + ", holdCount is " + mLock.getHoldCount()); - } - } - } - - /** - * Marks the current transaction as successful. Do not do any more database work between - * calling this and calling endTransaction. Do as little non-database work as possible in that - * situation too. If any errors are encountered between this and endTransaction the transaction - * will still be committed. - * - * @throws IllegalStateException if the current thread is not in a transaction or the - * transaction is already marked as successful. - */ - public void setTransactionSuccessful() { - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - if (!mLock.isHeldByCurrentThread()) { - throw new IllegalStateException("no transaction pending"); - } - if (mInnerTransactionIsSuccessful) { - throw new IllegalStateException( - "setTransactionSuccessful may only be called once per call to beginTransaction"); - } - mInnerTransactionIsSuccessful = true; - } - - /** - * return true if there is a transaction pending - */ - public boolean inTransaction() { - return mLock.getHoldCount() > 0; - } - - /** - * Checks if the database lock is held by this thread. - * - * @return true, if this thread is holding the database lock. - */ - public boolean isDbLockedByCurrentThread() { - return mLock.isHeldByCurrentThread(); - } - - /** - * Checks if the database is locked by another thread. This is - * just an estimate, since this status can change at any time, - * including after the call is made but before the result has - * been acted upon. - * - * @return true, if the database is locked by another thread - */ - public boolean isDbLockedByOtherThreads() { - return !mLock.isHeldByCurrentThread() && mLock.isLocked(); - } - - /** - * Temporarily end the transaction to let other threads run. The transaction is assumed to be - * successful so far. Do not call setTransactionSuccessful before calling this. When this - * returns a new transaction will have been created but not marked as successful. - * @return true if the transaction was yielded - * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock - * will not be yielded. Use yieldIfContendedSafely instead. - */ - @Deprecated - public boolean yieldIfContended() { - return yieldIfContendedHelper(false /* do not check yielding */, - -1 /* sleepAfterYieldDelay */); - } - - /** - * Temporarily end the transaction to let other threads run. The transaction is assumed to be - * successful so far. Do not call setTransactionSuccessful before calling this. When this - * returns a new transaction will have been created but not marked as successful. This assumes - * that there are no nested transactions (beginTransaction has only been called once) and will - * throw an exception if that is not the case. - * @return true if the transaction was yielded - */ - public boolean yieldIfContendedSafely() { - return yieldIfContendedHelper(true /* check yielding */, -1 /* sleepAfterYieldDelay*/); - } - - /** - * Temporarily end the transaction to let other threads run. The transaction is assumed to be - * successful so far. Do not call setTransactionSuccessful before calling this. When this - * returns a new transaction will have been created but not marked as successful. This assumes - * that there are no nested transactions (beginTransaction has only been called once) and will - * throw an exception if that is not the case. - * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if - * the lock was actually yielded. This will allow other background threads to make some - * more progress than they would if we started the transaction immediately. - * @return true if the transaction was yielded - */ - public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { - return yieldIfContendedHelper(true /* check yielding */, sleepAfterYieldDelay); - } - - private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) { - if (mLock.getQueueLength() == 0) { - // Reset the lock acquire time since we know that the thread was willing to yield - // the lock at this time. - mLockAcquiredWallTime = SystemClock.elapsedRealtime(); - mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); - return false; - } - setTransactionSuccessful(); - SQLiteTransactionListener transactionListener = mTransactionListener; - endTransaction(); - if (checkFullyYielded) { - if (this.isDbLockedByCurrentThread()) { - throw new IllegalStateException( - "Db locked more than once. yielfIfContended cannot yield"); - } - } - if (sleepAfterYieldDelay > 0) { - // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to - // check if anyone is using the database. If the database is not contended, - // retake the lock and return. - long remainingDelay = sleepAfterYieldDelay; - while (remainingDelay > 0) { - try { - Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ? - remainingDelay : SLEEP_AFTER_YIELD_QUANTUM); - } catch (InterruptedException e) { - Thread.interrupted(); - } - remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM; - if (mLock.getQueueLength() == 0) { - break; - } - } - } - beginTransactionWithListener(transactionListener); - return true; - } - - /** Maps table names to info about what to which _sync_time column to set - * to NULL on an update. This is used to support syncing. */ - private final Map mSyncUpdateInfo = - new HashMap(); - - public Map getSyncedTables() { - synchronized(mSyncUpdateInfo) { - HashMap tables = new HashMap(); - for (String table : mSyncUpdateInfo.keySet()) { - SyncUpdateInfo info = mSyncUpdateInfo.get(table); - if (info.deletedTable != null) { - tables.put(table, info.deletedTable); - } - } - return tables; - } - } - - /** - * Internal class used to keep track what needs to be marked as changed - * when an update occurs. This is used for syncing, so the sync engine - * knows what data has been updated locally. - */ - static private class SyncUpdateInfo { - /** - * Creates the SyncUpdateInfo class. - * - * @param masterTable The table to set _sync_time to NULL in - * @param deletedTable The deleted table that corresponds to the - * master table - * @param foreignKey The key that refers to the primary key in table - */ - SyncUpdateInfo(String masterTable, String deletedTable, - String foreignKey) { - this.masterTable = masterTable; - this.deletedTable = deletedTable; - this.foreignKey = foreignKey; - } - - /** The table containing the _sync_time column */ - String masterTable; - - /** The deleted table that corresponds to the master table */ - String deletedTable; - - /** The key in the local table the row in table. It may be _id, if table - * is the local table. */ - String foreignKey; - } - - /** - * Used to allow returning sub-classes of {@link Cursor} when calling query. - */ - public interface CursorFactory { - /** - * See - * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver, - * String, SQLiteQuery)}. - */ - public Cursor newCursor(SQLiteDatabase db, - SQLiteCursorDriver masterQuery, String editTable, - SQLiteQuery query); - } - - /** - * Open the database according to the flags {@link #OPEN_READWRITE} - * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}. - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - * @param path to database file to open and/or create - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called, or null for default - * @param flags to control database access mode - * @return the newly opened database - * @throws SQLiteException if the database cannot be opened - */ - public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) { - SQLiteDatabase sqliteDatabase = null; - try { - // Open the database. - sqliteDatabase = new SQLiteDatabase(path, password, factory, flags); - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - sqliteDatabase.enableSqlTracing(path); - } - if (SQLiteDebug.DEBUG_SQL_TIME) { - sqliteDatabase.enableSqlProfiling(path); - } - } catch (SQLiteDatabaseCorruptException e) { - // Try to recover from this, if we can. - // TODO: should we do this for other open failures? - Log.e(TAG, "Deleting and re-creating corrupt database " + path, e); - // EventLog.writeEvent(EVENT_DB_CORRUPT, path); - if (!path.equalsIgnoreCase(":memory")) { - // delete is only for non-memory database files - new File(path).delete(); - } - sqliteDatabase = new SQLiteDatabase(path, password, factory, flags); - } - ActiveDatabases.getInstance().mActiveDatabases.add( - new WeakReference(sqliteDatabase)); - return sqliteDatabase; - } - - /** - * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). - */ - public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) { - return openOrCreateDatabase(file.getPath(), password, factory); - } - - /** - * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY). - */ - public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) { - return openDatabase(path, password, factory, CREATE_IF_NECESSARY); - } - - /** - * Create a memory backed SQLite database. Its contents will be destroyed - * when the database is closed. - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called - * @return a SQLiteDatabase object, or null if the database can't be created - */ - public static SQLiteDatabase create(CursorFactory factory, String password) { - // This is a magic string with special meaning for SQLite. - return openDatabase(":memory:", password, factory, CREATE_IF_NECESSARY); - } - - /** - * Close the database. - */ - public void close() { - - if (!isOpen()) { - return; // already closed - } - lock(); - try { - closeClosable(); - // close this database instance - regardless of its reference count value - onAllReferencesReleased(); - } finally { - unlock(); - } - } - - private void closeClosable() { - /* deallocate all compiled sql statement objects from mCompiledQueries cache. - * this should be done before de-referencing all {@link SQLiteClosable} objects - * from this database object because calling - * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database - * to be closed. sqlite doesn't let a database close if there are - * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries. - */ - deallocCachedSqlStatements(); - - Iterator> iter = mPrograms.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - SQLiteClosable program = entry.getKey(); - if (program != null) { - program.onAllReferencesReleasedFromContainer(); - } - } - } - - /** - * Native call to close the database. - */ - private native void dbclose(); - - /** - * Gets the database version. - * - * @return the database version - */ - public int getVersion() { - SQLiteStatement prog = null; - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - try { - prog = new SQLiteStatement(this, "PRAGMA user_version;"); - long version = prog.simpleQueryForLong(); - return (int) version; - } finally { - if (prog != null) prog.close(); - unlock(); - } - } - - /** - * Sets the database version. - * - * @param version the new database version - */ - public void setVersion(int version) { - execSQL("PRAGMA user_version = " + version); - } - - /** - * Returns the maximum size the database may grow to. - * - * @return the new maximum database size - */ - public long getMaximumSize() { - SQLiteStatement prog = null; - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - try { - prog = new SQLiteStatement(this, - "PRAGMA max_page_count;"); - long pageCount = prog.simpleQueryForLong(); - return pageCount * getPageSize(); - } finally { - if (prog != null) prog.close(); - unlock(); - } - } - - /** - * Sets the maximum size the database will grow to. The maximum size cannot - * be set below the current size. - * - * @param numBytes the maximum database size, in bytes - * @return the new maximum database size - */ - public long setMaximumSize(long numBytes) { - SQLiteStatement prog = null; - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - try { - long pageSize = getPageSize(); - long numPages = numBytes / pageSize; - // If numBytes isn't a multiple of pageSize, bump up a page - if ((numBytes % pageSize) != 0) { - numPages++; - } - prog = new SQLiteStatement(this, - "PRAGMA max_page_count = " + numPages); - long newPageCount = prog.simpleQueryForLong(); - return newPageCount * pageSize; - } finally { - if (prog != null) prog.close(); - unlock(); - } - } - - /** - * Returns the current database page size, in bytes. - * - * @return the database page size, in bytes - */ - public long getPageSize() { - SQLiteStatement prog = null; - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - try { - prog = new SQLiteStatement(this, - "PRAGMA page_size;"); - long size = prog.simpleQueryForLong(); - return size; - } finally { - if (prog != null) prog.close(); - unlock(); - } - } - - /** - * Sets the database page size. The page size must be a power of two. This - * method does not work if any data has been written to the database file, - * and must be called right after the database has been created. - * - * @param numBytes the database page size, in bytes - */ - public void setPageSize(long numBytes) { - execSQL("PRAGMA page_size = " + numBytes); - } - - /** - * Mark this table as syncable. When an update occurs in this table the - * _sync_dirty field will be set to ensure proper syncing operation. - * - * @param table the table to mark as syncable - * @param deletedTable The deleted table that corresponds to the - * syncable table - */ - public void markTableSyncable(String table, String deletedTable) { - markTableSyncable(table, "_id", table, deletedTable); - } - - /** - * Mark this table as syncable, with the _sync_dirty residing in another - * table. When an update occurs in this table the _sync_dirty field of the - * row in updateTable with the _id in foreignKey will be set to - * ensure proper syncing operation. - * - * @param table an update on this table will trigger a sync time removal - * @param foreignKey this is the column in table whose value is an _id in - * updateTable - * @param updateTable this is the table that will have its _sync_dirty - */ - public void markTableSyncable(String table, String foreignKey, - String updateTable) { - markTableSyncable(table, foreignKey, updateTable, null); - } - - /** - * Mark this table as syncable, with the _sync_dirty residing in another - * table. When an update occurs in this table the _sync_dirty field of the - * row in updateTable with the _id in foreignKey will be set to - * ensure proper syncing operation. - * - * @param table an update on this table will trigger a sync time removal - * @param foreignKey this is the column in table whose value is an _id in - * updateTable - * @param updateTable this is the table that will have its _sync_dirty - * @param deletedTable The deleted table that corresponds to the - * updateTable - */ - private void markTableSyncable(String table, String foreignKey, - String updateTable, String deletedTable) { - lock(); - try { - native_execSQL("SELECT _sync_dirty FROM " + updateTable - + " LIMIT 0"); - native_execSQL("SELECT " + foreignKey + " FROM " + table - + " LIMIT 0"); - } finally { - unlock(); - } - - SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable, - foreignKey); - synchronized (mSyncUpdateInfo) { - mSyncUpdateInfo.put(table, info); - } - } - - /** - * Call for each row that is updated in a cursor. - * - * @param table the table the row is in - * @param rowId the row ID of the updated row - */ - /* package */ void rowUpdated(String table, long rowId) { - SyncUpdateInfo info; - synchronized (mSyncUpdateInfo) { - info = mSyncUpdateInfo.get(table); - } - if (info != null) { - execSQL("UPDATE " + info.masterTable - + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey - + " FROM " + table + " WHERE _id=" + rowId + ")"); - } - } - - /** - * Finds the name of the first table, which is editable. - * - * @param tables a list of tables - * @return the first table listed - */ - public static String findEditTable(String tables) { - if (!TextUtils.isEmpty(tables)) { - // find the first word terminated by either a space or a comma - int spacepos = tables.indexOf(' '); - int commapos = tables.indexOf(','); - - if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { - return tables.substring(0, spacepos); - } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { - return tables.substring(0, commapos); - } - return tables; - } else { - throw new IllegalStateException("Invalid tables"); - } - } - - /** - * Compiles an SQL statement into a reusable pre-compiled statement object. - * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the - * statement and fill in those values with {@link SQLiteProgram#bindString} - * and {@link SQLiteProgram#bindLong} each time you want to run the - * statement. Statements may not return result sets larger than 1x1. - * - * @param sql The raw SQL statement, may contain ? for unknown values to be - * bound later. - * @return A pre-compiled {@link SQLiteStatement} object. Note that - * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. - */ - public SQLiteStatement compileStatement(String sql) throws SQLException { - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - try { - return new SQLiteStatement(this, sql); - } finally { - unlock(); - } - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. The values will be bound as Strings. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { - return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, - groupBy, having, orderBy, limit); - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. The values will be bound as Strings. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor queryWithFactory(CursorFactory cursorFactory, - boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - String sql = SQLiteQueryBuilder.buildQueryString( - distinct, table, columns, selection, groupBy, having, orderBy, limit); - - return rawQueryWithFactory( - cursorFactory, sql, selectionArgs, findEditTable(table)); - } - - /** - * Query the given table, returning a {@link Cursor} over the result set. - * - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. The values will be bound as Strings. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String groupBy, String having, - String orderBy) { - - return query(false, table, columns, selection, selectionArgs, groupBy, - having, orderBy, null /* limit */); - } - - /** - * Query the given table, returning a {@link Cursor} over the result set. - * - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. The values will be bound as Strings. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String groupBy, String having, - String orderBy, String limit) { - - return query(false, table, columns, selection, selectionArgs, groupBy, - having, orderBy, limit); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQuery(String sql, String[] selectionArgs) { - return rawQueryWithFactory(null, sql, selectionArgs, null); - } - - /** - * Runs the provided SQL and returns a cursor over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. - * @param editTable the name of the first table, which is editable - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQueryWithFactory( - CursorFactory cursorFactory, String sql, String[] selectionArgs, - String editTable) { - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - long timeStart = 0; - - if (Config.LOGV || mSlowQueryThreshold != -1) { - timeStart = System.currentTimeMillis(); - } - - SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable); - - Cursor cursor = null; - try { - cursor = driver.query( - cursorFactory != null ? cursorFactory : mFactory, - selectionArgs); - } finally { - if (Config.LOGV || mSlowQueryThreshold != -1) { - - // Force query execution - int count = -1; - if (cursor != null) { - count = cursor.getCount(); - } - - long duration = System.currentTimeMillis() - timeStart; - - if (Config.LOGV || duration >= mSlowQueryThreshold) { - Log.v(TAG, - "query (" + duration + " ms): " + driver.toString() + ", args are " - + (selectionArgs != null - ? TextUtils.join(",", selectionArgs) - : "") + ", count is " + count); - } - } - } - return new CrossProcessCursorWrapper(cursor); - } - - /** - * Runs the provided SQL and returns a cursor over the result set. - * The cursor will read an initial set of rows and the return to the caller. - * It will continue to read in batches and send data changed notifications - * when the later batches are ready. - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. - * @param initialRead set the initial count of items to read from the cursor - * @param maxRead set the count of items to read on each iteration after the first - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * - * This work is incomplete and not fully tested or reviewed, so currently - * hidden. - * @hide - */ - public Cursor rawQuery(String sql, String[] selectionArgs, - int initialRead, int maxRead) { - SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory( - null, sql, selectionArgs, null); - c.setLoadStyle(initialRead, maxRead); - - return c; - } - - /** - * Convenience method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack SQL doesn't allow inserting a completely empty row, - * so if initialValues is empty this column will explicitly be - * assigned a NULL value - * @param values this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long insert(String table, String nullColumnHack, ContentValues values) { - try { - return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); - } catch (SQLException e) { - Log.e(TAG, "Error inserting " + values, e); - return -1; - } - } - - /** - * Convenience method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack SQL doesn't allow inserting a completely empty row, - * so if initialValues is empty this column will explicitly be - * assigned a NULL value - * @param values this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @throws SQLException - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long insertOrThrow(String table, String nullColumnHack, ContentValues values) - throws SQLException { - return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); - } - - /** - * Convenience method for replacing a row in the database. - * - * @param table the table in which to replace the row - * @param nullColumnHack SQL doesn't allow inserting a completely empty row, - * so if initialValues is empty this row will explicitly be - * assigned a NULL value - * @param initialValues this map contains the initial column values for - * the row. The key - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long replace(String table, String nullColumnHack, ContentValues initialValues) { - try { - return insertWithOnConflict(table, nullColumnHack, initialValues, - CONFLICT_REPLACE); - } catch (SQLException e) { - Log.e(TAG, "Error inserting " + initialValues, e); - return -1; - } - } - - /** - * Convenience method for replacing a row in the database. - * - * @param table the table in which to replace the row - * @param nullColumnHack SQL doesn't allow inserting a completely empty row, - * so if initialValues is empty this row will explicitly be - * assigned a NULL value - * @param initialValues this map contains the initial column values for - * the row. The key - * @throws SQLException - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long replaceOrThrow(String table, String nullColumnHack, - ContentValues initialValues) throws SQLException { - return insertWithOnConflict(table, nullColumnHack, initialValues, - CONFLICT_REPLACE); - } - - /** - * General method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack SQL doesn't allow inserting a completely empty row, - * so if initialValues is empty this column will explicitly be - * assigned a NULL value - * @param initialValues this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @param conflictAlgorithm for insert conflict resolver - * @return the row ID of the newly inserted row - * OR the primary key of the existing row if the input param 'conflictAlgorithm' = - * {@link #CONFLICT_IGNORE} - * OR -1 if any error - */ - public long insertWithOnConflict(String table, String nullColumnHack, - ContentValues initialValues, int conflictAlgorithm) { - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - - // Measurements show most sql lengths <= 152 - StringBuilder sql = new StringBuilder(152); - sql.append("INSERT"); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(" INTO "); - sql.append(table); - // Measurements show most values lengths < 40 - StringBuilder values = new StringBuilder(40); - - Set> entrySet = null; - if (initialValues != null && initialValues.size() > 0) { - entrySet = initialValues.valueSet(); - Iterator> entriesIter = entrySet.iterator(); - sql.append('('); - - boolean needSeparator = false; - while (entriesIter.hasNext()) { - if (needSeparator) { - sql.append(", "); - values.append(", "); - } - needSeparator = true; - Map.Entry entry = entriesIter.next(); - sql.append(entry.getKey()); - values.append('?'); - } - - sql.append(')'); - } else { - sql.append("(" + nullColumnHack + ") "); - values.append("NULL"); - } - - sql.append(" VALUES("); - sql.append(values); - sql.append(");"); - - lock(); - SQLiteStatement statement = null; - try { - statement = compileStatement(sql.toString()); - - // Bind the values - if (entrySet != null) { - int size = entrySet.size(); - Iterator> entriesIter = entrySet.iterator(); - for (int i = 0; i < size; i++) { - Map.Entry entry = entriesIter.next(); - DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue()); - - } - } - - // Run the program and then cleanup - statement.execute(); - - long insertedRowId = lastInsertRow(); - if (insertedRowId == -1) { - Log.e(TAG, "Error inserting " + initialValues + " using " + sql); - } else { - if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Inserting row " + insertedRowId + " from " - + initialValues + " using " + sql); - } - } - return insertedRowId; - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } finally { - if (statement != null) { - statement.close(); - } - unlock(); - } - } - - /** - * Convenience method for deleting rows in the database. - * - * @param table the table to delete from - * @param whereClause the optional WHERE clause to apply when deleting. - * Passing null will delete all rows. - * @return the number of rows affected if a whereClause is passed in, 0 - * otherwise. To remove all rows and get a count pass "1" as the - * whereClause. - */ - public int delete(String table, String whereClause, String[] whereArgs) { - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - SQLiteStatement statement = null; - try { - statement = compileStatement("DELETE FROM " + table - + (!TextUtils.isEmpty(whereClause) - ? " WHERE " + whereClause : "")); - if (whereArgs != null) { - int numArgs = whereArgs.length; - for (int i = 0; i < numArgs; i++) { - DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]); - } - } - statement.execute(); - return lastChangeCount(); - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } finally { - if (statement != null) { - statement.close(); - } - unlock(); - } - } - - /** - * Convenience method for updating rows in the database. - * - * @param table the table to update in - * @param values a map from column names to new column values. null is a - * valid value that will be translated to NULL. - * @param whereClause the optional WHERE clause to apply when updating. - * Passing null will update all rows. - * @return the number of rows affected - */ - public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { - return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); - } - - /** - * Convenience method for updating rows in the database. - * - * @param table the table to update in - * @param values a map from column names to new column values. null is a - * valid value that will be translated to NULL. - * @param whereClause the optional WHERE clause to apply when updating. - * Passing null will update all rows. - * @param conflictAlgorithm for update conflict resolver - * @return the number of rows affected - */ - public int updateWithOnConflict(String table, ContentValues values, - String whereClause, String[] whereArgs, int conflictAlgorithm) { - if (values == null || values.size() == 0) { - throw new IllegalArgumentException("Empty values"); - } - - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(table); - sql.append(" SET "); - - Set> entrySet = values.valueSet(); - Iterator> entriesIter = entrySet.iterator(); - - while (entriesIter.hasNext()) { - Map.Entry entry = entriesIter.next(); - sql.append(entry.getKey()); - sql.append("=?"); - if (entriesIter.hasNext()) { - sql.append(", "); - } - } - - if (!TextUtils.isEmpty(whereClause)) { - sql.append(" WHERE "); - sql.append(whereClause); - } - - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - SQLiteStatement statement = null; - try { - statement = compileStatement(sql.toString()); - - // Bind the values - int size = entrySet.size(); - entriesIter = entrySet.iterator(); - int bindArg = 1; - for (int i = 0; i < size; i++) { - Map.Entry entry = entriesIter.next(); - DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue()); - bindArg++; - } - - if (whereArgs != null) { - size = whereArgs.length; - for (int i = 0; i < size; i++) { - statement.bindString(bindArg, whereArgs[i]); - bindArg++; - } - } - - // Run the program and then cleanup - statement.execute(); - int numChangedRows = lastChangeCount(); - if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql); - } - return numChangedRows; - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } catch (SQLException e) { - Log.e(TAG, "Error updating " + values + " using " + sql); - throw e; - } finally { - if (statement != null) { - statement.close(); - } - unlock(); - } - } - - /** - * Execute a single SQL statement that is not a query. For example, CREATE - * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not - * supported. it takes a write lock - * - * @throws SQLException If the SQL string is invalid for some reason - */ - public void execSQL(String sql) throws SQLException { - long timeStart = SystemClock.uptimeMillis(); - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); - try { - native_execSQL(sql); - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } finally { - unlock(); - } - - // Log commit statements along with the most recently executed - // SQL statement for disambiguation. Note that instance - // equality to COMMIT_SQL is safe here. - if (sql == COMMIT_SQL) { - logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL); - } else { - logTimeStat(sql, timeStart, null); - } - } - - /** - * Execute a single SQL statement that is not a query. For example, CREATE - * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not - * supported. it takes a write lock, - * - * @param sql - * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. - * @throws SQLException If the SQL string is invalid for some reason - */ - public void execSQL(String sql, Object[] bindArgs) throws SQLException { - if (bindArgs == null) { - throw new IllegalArgumentException("Empty bindArgs"); - } - long timeStart = SystemClock.uptimeMillis(); - lock(); - if (!isOpen()) { - throw new IllegalStateException("database not open"); - } - SQLiteStatement statement = null; - try { - statement = compileStatement(sql); - if (bindArgs != null) { - int numArgs = bindArgs.length; - for (int i = 0; i < numArgs; i++) { - DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]); - } - } - statement.execute(); - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } finally { - if (statement != null) { - statement.close(); - } - unlock(); - } - logTimeStat(sql, timeStart); - } - - @Override - protected void finalize() { - if (isOpen()) { - Log.e(TAG, "close() was never explicitly called on database '" + - mPath + "' ", mStackTrace); - closeClosable(); - onAllReferencesReleased(); - } - } - - /** - * Private constructor. See {@link #create} and {@link #openDatabase}. - * - * @param path The full path to the database - * @param factory The factory to use when creating cursors, may be NULL. - * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already - * exists, mFlags will be updated appropriately. - */ - public SQLiteDatabase(String path, String password, CursorFactory factory, int flags) { - - - if (path == null) { - throw new IllegalArgumentException("path should not be null"); - } - mFlags = flags; - mPath = path; - mSlowQueryThreshold = -1;//SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1); - mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); - mFactory = factory; - dbopen(mPath, mFlags); - - execSQL("PRAGMA key = '" + password + "'"); - - if (SQLiteDebug.DEBUG_SQL_CACHE) { - mTimeOpened = getTime(); - } - mPrograms = new WeakHashMap(); - try { - setLocale(Locale.getDefault()); - } catch (RuntimeException e) { - Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e); - dbclose(); - if (SQLiteDebug.DEBUG_SQL_CACHE) { - mTimeClosed = getTime(); - } - throw e; - } - } - - private String getTime() { - return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis()); - } - - /** - * return whether the DB is opened as read only. - * @return true if DB is opened as read only - */ - public boolean isReadOnly() { - return (mFlags & OPEN_READ_MASK) == OPEN_READONLY; - } - - /** - * @return true if the DB is currently open (has not been closed) - */ - public boolean isOpen() { - return mNativeHandle != 0; - } - - public boolean needUpgrade(int newVersion) { - return newVersion > getVersion(); - } - - /** - * Getter for the path to the database file. - * - * @return the path to our database file. - */ - public final String getPath() { - return mPath; - } - - /* package */ void logTimeStat(String sql, long beginMillis) { - logTimeStat(sql, beginMillis, null); - } - - /* package */ void logTimeStat(String sql, long beginMillis, String prefix) { - // Keep track of the last statement executed here, as this is - // the common funnel through which all methods of hitting - // libsqlite eventually flow. - mLastSqlStatement = sql; - - // Sample fast queries in proportion to the time taken. - // Quantize the % first, so the logged sampling probability - // exactly equals the actual sampling rate for this query. - - int samplePercent; - long durationMillis = SystemClock.uptimeMillis() - beginMillis; - if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) { - // The common case is locks being uncontended. Don't log those, - // even at 1%, which is our default below. - return; - } - if (sQueryLogTimeInMillis == 0) { - sQueryLogTimeInMillis = 500;//SystemProperties.getInt("db.db_operation.threshold_ms", 500); - } - if (durationMillis >= sQueryLogTimeInMillis) { - samplePercent = 100; - } else {; - samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1; - if (mRandom.nextInt(100) >= samplePercent) return; - } - - // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null. We wait to do - // it here so we avoid allocating in the common case. - if (prefix != null) { - sql = prefix + sql; - } - - if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH); - - // ActivityThread.currentPackageName() only returns non-null if the - // current thread is an application main thread. This parameter tells - // us whether an event loop is blocked, and if so, which app it is. - // - // Sadly, there's no fast way to determine app name if this is *not* a - // main thread, or when we are invoked via Binder (e.g. ContentProvider). - // Hopefully the full path to the database will be informative enough. - - //TODO get the current package name - String blockingPackage = "unknown";//ActivityThread.currentPackageName(); - if (blockingPackage == null) blockingPackage = ""; - - /* - EventLog.writeEvent( - EVENT_DB_OPERATION, - getPathForLogs(), - sql, - durationMillis, - blockingPackage, - samplePercent);*/ - } - - /** - * Removes email addresses from database filenames before they're - * logged to the EventLog where otherwise apps could potentially - * read them. - */ - private String getPathForLogs() { - if (mPathForLogs != null) { - return mPathForLogs; - } - if (mPath == null) { - return null; - } - if (mPath.indexOf('@') == -1) { - mPathForLogs = mPath; - } else { - mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY"); - } - return mPathForLogs; - } - - /** - * Sets the locale for this database. Does nothing if this database has - * the NO_LOCALIZED_COLLATORS flag set or was opened read only. - * @throws SQLException if the locale could not be set. The most common reason - * for this is that there is no collator available for the locale you requested. - * In this case the database remains unchanged. - */ - public void setLocale(Locale locale) { - lock(); - try { - native_setLocale(locale.toString(), mFlags); - } finally { - unlock(); - } - } - - /* - * ============================================================================ - * - * The following methods deal with compiled-sql cache - * ============================================================================ - */ - /** - * adds the given sql and its compiled-statement-id-returned-by-sqlite to the - * cache of compiledQueries attached to 'this'. - * - * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql, - * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current - * mapping is NOT replaced with the new mapping). - */ - /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { - if (mMaxSqlCacheSize == 0) { - // for this database, there is no cache of compiled sql. - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); - } - return; - } - - SQLiteCompiledSql compiledSql = null; - synchronized(mCompiledQueries) { - // don't insert the new mapping if a mapping already exists - compiledSql = mCompiledQueries.get(sql); - if (compiledSql != null) { - return; - } - // add this to the cache - if (mCompiledQueries.size() == mMaxSqlCacheSize) { - /* - * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. - * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times - * chances are it is NOT using ? for bindargs - so caching is useless. - * TODO: either let the callers set max cchesize for their app, or intelligently - * figure out what should be cached for a given app. - */ - if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) { - Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + - getPath() + "; i.e., NO space for this sql statement in cache: " + - sql + ". Please change your sql statements to use '?' for " + - "bindargs, instead of using actual values"); - } - // don't add this entry to cache - } else { - // cache is NOT full. add this to cache. - mCompiledQueries.put(sql, compiledStatement); - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + - mCompiledQueries.size() + "|" + sql); - } - } - } - return; - } - - - private void deallocCachedSqlStatements() { - synchronized (mCompiledQueries) { - for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { - compiledSql.releaseSqlStatement(); - } - mCompiledQueries.clear(); - } - } - - /** - * from the compiledQueries cache, returns the compiled-statement-id for the given sql. - * returns null, if not found in the cache. - */ - /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { - SQLiteCompiledSql compiledStatement = null; - boolean cacheHit; - synchronized(mCompiledQueries) { - if (mMaxSqlCacheSize == 0) { - // for this database, there is no cache of compiled sql. - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|cache NOT found|" + getPath()); - } - return null; - } - cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; - } - if (cacheHit) { - mNumCacheHits++; - } else { - mNumCacheMisses++; - } - - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|cache_stats|" + - getPath() + "|" + mCompiledQueries.size() + - "|" + mNumCacheHits + "|" + mNumCacheMisses + - "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql); - } - return compiledStatement; - } - - /** - * returns true if the given sql is cached in compiled-sql cache. - * @hide - */ - public boolean isInCompiledSqlCache(String sql) { - synchronized(mCompiledQueries) { - return mCompiledQueries.containsKey(sql); - } - } - - /** - * purges the given sql from the compiled-sql cache. - * @hide - */ - public void purgeFromCompiledSqlCache(String sql) { - synchronized(mCompiledQueries) { - mCompiledQueries.remove(sql); - } - } - - /** - * remove everything from the compiled sql cache - * @hide - */ - public void resetCompiledSqlCache() { - synchronized(mCompiledQueries) { - mCompiledQueries.clear(); - } - } - - /** - * return the current maxCacheSqlCacheSize - * @hide - */ - public synchronized int getMaxSqlCacheSize() { - return mMaxSqlCacheSize; - } - - /** - * set the max size of the compiled sql cache for this database after purging the cache. - * (size of the cache = number of compiled-sql-statements stored in the cache). - * - * max cache size can ONLY be increased from its current size (default = 0). - * if this method is called with smaller size than the current value of mMaxSqlCacheSize, - * then IllegalStateException is thrown - * - * synchronized because we don't want t threads to change cache size at the same time. - * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) - * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or - * < the value set with previous setMaxSqlCacheSize() call. - * - * @hide - */ - public synchronized void setMaxSqlCacheSize(int cacheSize) { - if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { - throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); - } else if (cacheSize < mMaxSqlCacheSize) { - throw new IllegalStateException("cannot set cacheSize to a value less than the value " + - "set with previous setMaxSqlCacheSize() call."); - } - mMaxSqlCacheSize = cacheSize; - } - - static class ActiveDatabases { - private static final ActiveDatabases activeDatabases = new ActiveDatabases(); - private HashSet> mActiveDatabases = - new HashSet>(); - private ActiveDatabases() {} // disable instantiation of this class - static ActiveDatabases getInstance() {return activeDatabases;} - } - - /** - * this method is used to collect data about ALL open databases in the current process. - * bugreport is a user of this data. - */ - /* package */ static ArrayList getDbStats() { - ArrayList dbStatsList = new ArrayList(); - for (WeakReference w : ActiveDatabases.getInstance().mActiveDatabases) { - SQLiteDatabase db = w.get(); - if (db == null || !db.isOpen()) { - continue; - } - // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db - int lookasideUsed = db.native_getDbLookaside(); - - // get the lastnode of the dbname - String path = db.getPath(); - int indx = path.lastIndexOf("/"); - String lastnode = path.substring((indx != -1) ? ++indx : 0); - - // get list of attached dbs and for each db, get its size and pagesize - ArrayList> attachedDbs = getAttachedDbs(db); - if (attachedDbs == null) { - continue; - } - for (int i = 0; i < attachedDbs.size(); i++) { - Pair p = attachedDbs.get(i); - long pageCount = getPragmaVal(db, p.first + ".page_count;"); - - // first entry in the attached db list is always the main database - // don't worry about prefixing the dbname with "main" - String dbName; - if (i == 0) { - dbName = lastnode; - } else { - // lookaside is only relevant for the main db - lookasideUsed = 0; - dbName = " (attached) " + p.first; - // if the attached db has a path, attach the lastnode from the path to above - if (p.second.trim().length() > 0) { - int idx = p.second.lastIndexOf("/"); - dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0); - } - } - if (pageCount > 0) { - dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), - lookasideUsed)); - } - } - } - return dbStatsList; - } - - /** - * get the specified pragma value from sqlite for the specified database. - * only handles pragma's that return int/long. - * NO JAVA locks are held in this method. - * TODO: use this to do all pragma's in this class - */ - private static long getPragmaVal(SQLiteDatabase db, String pragma) { - if (!db.isOpen()) { - return 0; - } - SQLiteStatement prog = null; - try { - prog = new SQLiteStatement(db, "PRAGMA " + pragma); - long val = prog.simpleQueryForLong(); - return val; - } finally { - if (prog != null) prog.close(); - } - } - - /** - * returns list of full pathnames of all attached databases - * including the main database - * TODO: move this to {@link DatabaseUtils} - */ - private static ArrayList> getAttachedDbs(SQLiteDatabase dbObj) { - if (!dbObj.isOpen()) { - return null; - } - ArrayList> attachedDbs = new ArrayList>(); - Cursor c = dbObj.rawQuery("pragma database_list;", null); - while (c.moveToNext()) { - attachedDbs.add(new Pair(c.getString(1), c.getString(2))); - } - c.close(); - return attachedDbs; - } - - /** - * Sets the root directory to search for the ICU data file - */ - public static native void setICURoot(String path); - - /** - * Native call to open the database. - * - * @param path The full path to the database - */ - private native void dbopen(String path, int flags); - - /** - * Native call to setup tracing of all sql statements - * - * @param path the full path to the database - */ - private native void enableSqlTracing(String path); - - /** - * Native call to setup profiling of all sql statements. - * currently, sqlite's profiling = printing of execution-time - * (wall-clock time) of each of the sql statements, as they - * are executed. - * - * @param path the full path to the database - */ - private native void enableSqlProfiling(String path); - - /** - * Native call to execute a raw SQL statement. {@link #lock} must be held - * when calling this method. - * - * @param sql The raw SQL string - * @throws SQLException - */ - /* package */ native void native_execSQL(String sql) throws SQLException; - - /** - * Native call to set the locale. {@link #lock} must be held when calling - * this method. - * @throws SQLException - */ - /* package */ native void native_setLocale(String loc, int flags); - - /** - * Returns the row ID of the last row inserted into the database. - * - * @return the row ID of the last row inserted into the database. - */ - /* package */ native long lastInsertRow(); - - /** - * Returns the number of changes made in the last statement executed. - * - * @return the number of changes made in the last statement executed. - */ - /* package */ native int lastChangeCount(); - - /** - * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here - * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html - * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED - */ - private native int native_getDbLookaside(); -} diff --git a/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java b/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java deleted file mode 100644 index 2e7373ca..00000000 --- a/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that the SQLite database file is corrupt. - */ -public class SQLiteDatabaseCorruptException extends SQLiteException { - public SQLiteDatabaseCorruptException() {} - - public SQLiteDatabaseCorruptException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteDiskIOException.java b/src/net/sqlcipher/database/SQLiteDiskIOException.java deleted file mode 100644 index 7302abe4..00000000 --- a/src/net/sqlcipher/database/SQLiteDiskIOException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that an IO error occured while accessing the - * SQLite database file. - */ -public class SQLiteDiskIOException extends SQLiteException { - public SQLiteDiskIOException() {} - - public SQLiteDiskIOException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteDoneException.java b/src/net/sqlcipher/database/SQLiteDoneException.java deleted file mode 100644 index f0f6f0dc..00000000 --- a/src/net/sqlcipher/database/SQLiteDoneException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that the SQLite program is done. - * Thrown when an operation that expects a row (such as {@link - * SQLiteStatement#simpleQueryForString} or {@link - * SQLiteStatement#simpleQueryForLong}) does not get one. - */ -public class SQLiteDoneException extends SQLiteException { - public SQLiteDoneException() {} - - public SQLiteDoneException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteFullException.java b/src/net/sqlcipher/database/SQLiteFullException.java deleted file mode 100644 index 66af19fe..00000000 --- a/src/net/sqlcipher/database/SQLiteFullException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -/** - * An exception that indicates that the SQLite database is full. - */ -public class SQLiteFullException extends SQLiteException { - public SQLiteFullException() {} - - public SQLiteFullException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteMisuseException.java b/src/net/sqlcipher/database/SQLiteMisuseException.java deleted file mode 100644 index ef261fc7..00000000 --- a/src/net/sqlcipher/database/SQLiteMisuseException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -public class SQLiteMisuseException extends SQLiteException { - public SQLiteMisuseException() {} - - public SQLiteMisuseException(String error) { - super(error); - } -} diff --git a/src/net/sqlcipher/database/SQLiteOpenHelper.java b/src/net/sqlcipher/database/SQLiteOpenHelper.java deleted file mode 100644 index 05402437..00000000 --- a/src/net/sqlcipher/database/SQLiteOpenHelper.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.sqlcipher.database; - -import java.io.File; - -import android.content.Context; -import net.sqlcipher.database.SQLiteDatabase.CursorFactory; -import android.util.Log; - -/** - * A helper class to manage database creation and version management. - * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and - * optionally {@link #onOpen}, and this class takes care of opening the database - * if it exists, creating it if it does not, and upgrading it as necessary. - * Transactions are used to make sure the database is always in a sensible state. - *

For an example, see the NotePadProvider class in the NotePad sample application, - * in the samples/ directory of the SDK.

- */ -public abstract class SQLiteOpenHelper { - private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); - - private final Context mContext; - private final String mName; - private final CursorFactory mFactory; - private final int mNewVersion; - - private SQLiteDatabase mDatabaseRO, mDatabaseRW = null; - private boolean mIsInitializing = false; - - /** - * Create a helper object to create, open, and/or manage a database. - * The database is not actually created or opened until one of - * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. - * - * @param context to use to open or create the database - * @param name of the database file, or null for an in-memory database - * @param factory to use for creating cursor objects, or null for the default - * @param version number of the database (starting at 1); if the database is older, - * {@link #onUpgrade} will be used to upgrade the database - */ - public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { - if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); - - mContext = context; - mName = name; - mFactory = factory; - mNewVersion = version; - } - - /** - * Create and/or open a database that will be used for reading and writing. - * Once opened successfully, the database is cached, so you can call this - * method every time you need to write to the database. Make sure to call - * {@link #close} when you no longer need it. - * - *

Errors such as bad permissions or a full disk may cause this operation - * to fail, but future attempts may succeed if the problem is fixed.

- * - * @throws SQLiteException if the database cannot be opened for writing - * @return a read/write database object valid until {@link #close} is called - */ - public synchronized SQLiteDatabase getWritableDatabase(String password) { - if (mDatabaseRW != null && mDatabaseRW.isOpen()) { - return mDatabaseRW; // The database is already open for business - } - - if (mIsInitializing) { - throw new IllegalStateException("getWritableDatabase called recursively"); - } - - // If we have a read-only database open, someone could be using it - // (though they shouldn't), which would cause a lock to be held on - // the file, and our attempts to open the database read-write would - // fail waiting for the file lock. To prevent that, we acquire the - // lock on the read-only database, which shuts out other users. - - boolean success = false; - SQLiteDatabase db = null; - if (mDatabaseRW != null) mDatabaseRW.lock(); - try { - mIsInitializing = true; - if (mName == null) { - db = SQLiteDatabase.create(null, password); - - } else { - String path = mContext.getDatabasePath(mName).getPath(); - - File dbPathFile = new File (path); - if (!dbPathFile.exists()) - dbPathFile.getParentFile().mkdirs(); - - db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory); - - // db = SQLiteDatabase.openDatabase(path,mFactory , SQLiteDatabase.OPEN_READWRITE); - - //db = mContext.openOrCreateDatabase(mName, 0, mFactory); - - } - - - int version = db.getVersion(); - if (version != mNewVersion) { - db.beginTransaction(); - try { - if (version == 0) { - onCreate(db); - } else { - onUpgrade(db, version, mNewVersion); - } - db.setVersion(mNewVersion); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - onOpen(db); - success = true; - return db; - } finally { - mIsInitializing = false; - if (success) { - if (mDatabaseRW != null) { - try { mDatabaseRW.close(); } catch (Exception e) { } - mDatabaseRW.unlock(); - } - mDatabaseRW = db; - } else { - if (mDatabaseRW != null) mDatabaseRW.unlock(); - if (db != null) db.close(); - } - } - } - - /** - * Create and/or open a database. This will be the same object returned by - * {@link #getWritableDatabase} unless some problem, such as a full disk, - * requires the database to be opened read-only. In that case, a read-only - * database object will be returned. If the problem is fixed, a future call - * to {@link #getWritableDatabase} may succeed, in which case the read-only - * database object will be closed and the read/write object will be returned - * in the future. - * - * @throws SQLiteException if the database cannot be opened - * @return a database object valid until {@link #getWritableDatabase} - * or {@link #close} is called. - */ - public synchronized SQLiteDatabase getReadableDatabase(String password) { - if (mDatabaseRO != null && mDatabaseRO.isOpen()) { - return mDatabaseRO; // The database is already open for business - } - - if (mIsInitializing) { - throw new IllegalStateException("getReadableDatabase called recursively"); - } - - SQLiteDatabase db = null; - try { - mIsInitializing = true; - String path = mContext.getDatabasePath(mName).getPath(); - db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY); - if (db.getVersion() != mNewVersion) { - throw new SQLiteException("Can't upgrade read-only database from version " + - db.getVersion() + " to " + mNewVersion + ": " + path); - } - - onOpen(db); - Log.w(TAG, "Opened " + mName + " in read-only mode"); - mDatabaseRO = db; - return mDatabaseRO; - } finally { - mIsInitializing = false; - if (db != null && db != mDatabaseRO) db.close(); - } - } - - /** - * Close any open database object. - */ - public synchronized void close() { - if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); - - if (mDatabaseRO != null && mDatabaseRO.isOpen()) { - mDatabaseRO.close(); - mDatabaseRO = null; - } - - if (mDatabaseRW != null && mDatabaseRW.isOpen()) { - mDatabaseRW.close(); - mDatabaseRW = null; - } - } - - /** - * Called when the database is created for the first time. This is where the - * creation of tables and the initial population of the tables should happen. - * - * @param db The database. - */ - public abstract void onCreate(SQLiteDatabase db); - - /** - * Called when the database needs to be upgraded. The implementation - * should use this method to drop tables, add tables, or do anything else it - * needs to upgrade to the new schema version. - * - *

The SQLite ALTER TABLE documentation can be found - * here. If you add new columns - * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns - * you can use ALTER TABLE to rename the old table, then create the new table and then - * populate the new table with the contents of the old table. - * - * @param db The database. - * @param oldVersion The old database version. - * @param newVersion The new database version. - */ - public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); - - /** - * Called when the database has been opened. - * Override method should check {@link SQLiteDatabase#isReadOnly} before - * updating the database. - * - * @param db The database. - */ - public void onOpen(SQLiteDatabase db) {} -} diff --git a/src/net/sqlcipher/database/package.html b/src/net/sqlcipher/database/package.html deleted file mode 100644 index ff0f9f58..00000000 --- a/src/net/sqlcipher/database/package.html +++ /dev/null @@ -1,20 +0,0 @@ - - -Contains the SQLite database management -classes that an application would use to manage its own private database. -

-Applications use these classes to maange private databases. If creating a -content provider, you will probably have to use these classes to create and -manage your own database to store content. See Content Providers to learn -the conventions for implementing a content provider. See the -NotePadProvider class in the NotePad sample application in the SDK for an -example of a content provider. Android ships with SQLite version 3.4.0 -

If you are working with data sent to you by a provider, you will not use -these SQLite classes, but instead use the generic {@link android.database} -classes. -

Android ships with the sqlite3 database tool in the tools/ -folder. You can use this tool to browse or run SQL commands on the device. Run by -typing sqlite3 in a shell window. - - diff --git a/src/package.html b/src/package.html deleted file mode 100644 index 1f76d9f1..00000000 --- a/src/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - -Contains classes to explore data returned through a content provider. -

-If you need to manage data in a private database, use the {@link -android.database.sqlite} classes. These classes are used to manage the {@link -android.database.Cursor} object returned from a content provider query. Databases -are usually created and opened with {@link android.content.Context#openOrCreateDatabase} -To make requests through -content providers, you can use the {@link android.content.ContentResolver -content.ContentResolver} class. -

All databases are stored on the device in /data/data/<package_name>/databases - -