diff --git a/.classpath b/.classpath deleted file mode 100644 index b2393f6e..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 590efdcc..94a7c0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,14 @@ -TAGS -external/libs -jni/libs -obj/ +.gradle +build +.DS_Store +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 70b36fa6..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +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:guardianproject/sqlcipher-android.git -[submodule "external/dalvik"] - path = external/dalvik - url = git://android.git.kernel.org/platform/dalvik.git -[submodule "external/android-sqlite"] - path = external/android-sqlite - url = git://android.git.kernel.org/platform/external/sqlite.git -[submodule "external/platform-system-core"] - path = external/platform-system-core - url = git://android.git.kernel.org/platform/system/core.git -[submodule "external/platform-frameworks-base"] - path = external/platform-frameworks-base - url = git://android.git.kernel.org/platform/frameworks/base.git -[submodule "external/icu4c"] - path = external/icu4c - url = git://android.git.kernel.org/platform/external/icu4c.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/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index 4262c033..00000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file 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 new file mode 100644 index 00000000..7a42f371 --- /dev/null +++ b/Makefile @@ -0,0 +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 + +clean: + $(GRADLE) clean + +distclean: + $(GRADLE) distclean \ + -PsqlcipherRoot="$(SQLCIPHER_ROOT)" + +build-openssl: + $(GRADLE) buildOpenSSL + +check: + $(GRADLE) check + +format: + $(GRADLE) editorconfigFormat + +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)" + +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/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/info/guardianproject/database/IContentObserver.aidl b/android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl similarity index 81% rename from src/info/guardianproject/database/IContentObserver.aidl rename to android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl index 2536dbd2..22857515 100755 --- a/src/info/guardianproject/database/IContentObserver.aidl +++ b/android-database-sqlcipher/src/main/aidl/net/sqlcipher/IContentObserver.aidl @@ -2,20 +2,20 @@ ** ** 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. */ -package info.guardianproject.database; +package net.sqlcipher; /** * @hide 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 a1666f9f..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 { -namespace guardianproject { - -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 { @@ -410,4 +403,4 @@ bool CursorWindow::getNull(unsigned int row, unsigned int col, bool * valueOut) return true; } -}; // namespace guardianproject +}; // namespace sqlcipher diff --git a/jni/CursorWindow.h b/android-database-sqlcipher/src/main/cpp/CursorWindow.h similarity index 88% rename from jni/CursorWindow.h rename to android-database-sqlcipher/src/main/cpp/CursorWindow.h index f52eb5a9..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,14 +47,17 @@ #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 -namespace guardianproject { +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,16 +185,18 @@ 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 guardianproject +}; // namespace sqlcipher #endif 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/info_guardianproject_database_sqlcipher_SQLiteCompiledSql.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp similarity index 77% rename from jni/info_guardianproject_database_sqlcipher_SQLiteCompiledSql.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp index f9dabaf3..4db2864e 100644 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteCompiledSql.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteCompiledSql.cpp @@ -18,30 +18,30 @@ #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 guardianproject { +namespace sqlcipher { static jfieldID gHandleField; 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); } } @@ -113,25 +113,23 @@ int register_android_database_SQLiteCompiledSql(JNIEnv * env) { jclass clazz; - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteCompiledSql"); + clazz = env->FindClass("net/sqlcipher/database/SQLiteCompiledSql"); if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/SQLiteCompiledSql"); + LOGE("Can't find net/sqlcipher/database/SQLiteCompiledSql"); 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, - "info/guardianproject/database/sqlcipher/SQLiteCompiledSql", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } -} // namespace guardianproject +} // namespace sqlcipher 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/info_guardianproject_database_sqlcipher_SQLiteDebug.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp similarity index 92% rename from jni/info_guardianproject_database_sqlcipher_SQLiteDebug.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp index 1d218b0b..15a31cf6 100644 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteDebug.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteDebug.cpp @@ -29,7 +29,7 @@ // From mem_mspace.c in libsqlite extern "C" mspace sqlite3_get_mspace(); -namespace guardianproject { +namespace sqlcipher { static jfieldID gMemoryUsedField; static jfieldID gPageCacheOverfloField; @@ -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 } @@ -187,7 +187,7 @@ static void getHeapDirtyPages(JNIEnv *env, jobject clazz, jintArray pages) static JNINativeMethod gMethods[] = { - { "getPagerStats", "(Linfo/guardianproject/database/sqlcipher/SQLiteDebug$PagerStats;)V", + { "getPagerStats", "(Lnet/sqlcipher/database/SQLiteDebug$PagerStats;)V", (void*) getPagerStats }, { "getHeapSize", "()J", (void*) getHeapSize }, { "getHeapAllocatedSize", "()J", (void*) getHeapAllocatedSize }, @@ -199,9 +199,9 @@ int register_android_database_SQLiteDebug(JNIEnv *env) { jclass clazz; - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteDebug$PagerStats"); + clazz = env->FindClass("net/sqlcipher/database/SQLiteDebug$PagerStats"); if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/SQLiteDebug$PagerStats"); + LOGE("Can't find net/sqlcipher/database/SQLiteDebug$PagerStats"); return -1; } @@ -223,8 +223,8 @@ int register_android_database_SQLiteDebug(JNIEnv *env) return -1; } - return jniRegisterNativeMethods(env, "info/guardianproject/database/sqlcipher/SQLiteDebug", + return jniRegisterNativeMethods(env, "net/sqlcipher/database/SQLiteDebug", gMethods, NELEM(gMethods)); } -} // namespace guardianproject +} // namespace sqlcipher diff --git a/jni/info_guardianproject_database_sqlcipher_SQLiteProgram.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp similarity index 87% rename from jni/info_guardianproject_database_sqlcipher_SQLiteProgram.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp index d894424f..2a526077 100644 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteProgram.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteProgram.cpp @@ -17,31 +17,30 @@ #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 guardianproject { +namespace sqlcipher { static jfieldID gHandleField; 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) { @@ -174,23 +173,21 @@ int register_android_database_SQLiteProgram(JNIEnv * env) { jclass clazz; - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteProgram"); + clazz = env->FindClass("net/sqlcipher/database/SQLiteProgram"); if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/SQLiteProgram"); + LOGE("Can't find net/sqlcipher/database/SQLiteProgram"); 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, - "info/guardianproject/database/sqlcipher/SQLiteProgram", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } -} // namespace guardianproject +} // namespace sqlcipher diff --git a/jni/info_guardianproject_database_sqlcipher_SQLiteQuery.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp similarity index 79% rename from jni/info_guardianproject_database_sqlcipher_SQLiteQuery.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp index e069a1de..8dcf139f 100644 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteQuery.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteQuery.cpp @@ -18,22 +18,21 @@ #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 guardianproject { +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", "(Linfo/guardianproject/database/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}, }; @@ -345,23 +375,21 @@ int register_android_database_SQLiteQuery(JNIEnv * env) { jclass clazz; - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteQuery"); + clazz = env->FindClass("net/sqlcipher/database/SQLiteQuery"); if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/SQLiteQuery"); + LOGE("Can't find net/sqlcipher/database/SQLiteQuery"); 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, - "info/guardianproject/database/sqlcipher/SQLiteQuery", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } -} // namespace guardianproject +} // namespace sqlcipher diff --git a/jni/info_guardianproject_database_sqlcipher_SQLiteStatement.cpp b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp similarity index 75% rename from jni/info_guardianproject_database_sqlcipher_SQLiteStatement.cpp rename to android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp index 4e4bdabd..6f6ca32c 100644 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteStatement.cpp +++ b/android-database-sqlcipher/src/main/cpp/net_sqlcipher_database_SQLiteStatement.cpp @@ -19,20 +19,16 @@ #define LOG_TAG "Cursor" #include -#include -#include - #include - -#include - #include #include #include +#include "log.h" +#include "jni_elements.h" #include "sqlite3_exception.h" -namespace guardianproject { +namespace sqlcipher { sqlite3_stmt * compile(JNIEnv* env, jobject object, @@ -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)); } @@ -129,23 +132,21 @@ int register_android_database_SQLiteStatement(JNIEnv * env) { jclass clazz; - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteStatement"); + clazz = env->FindClass("net/sqlcipher/database/SQLiteStatement"); if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/SQLiteStatement"); + LOGE("Can't find net/sqlcipher/database/SQLiteStatement"); 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, - "info/guardianproject/database/sqlcipher/SQLiteStatement", sMethods, NELEM(sMethods)); + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); } -} // namespace guardianproject +} // namespace sqlcipher diff --git a/jni/sqlcipher_loading.h b/android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h similarity index 92% rename from jni/sqlcipher_loading.h rename to android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h index d98a7b6f..c9201ed9 100644 --- a/jni/sqlcipher_loading.h +++ b/android-database-sqlcipher/src/main/cpp/sqlcipher_loading.h @@ -17,12 +17,12 @@ #include -#include -#include +/* #include */ +/* #include */ #include -namespace guardianproject { +namespace sqlcipher { int register_android_database_SQLiteDatabase(JNIEnv *env); diff --git a/jni/sqlite3_exception.h b/android-database-sqlcipher/src/main/cpp/sqlite3_exception.h similarity index 82% rename from jni/sqlite3_exception.h rename to android-database-sqlcipher/src/main/cpp/sqlite3_exception.h index 983db3fd..db31e571 100644 --- a/jni/sqlite3_exception.h +++ b/android-database-sqlcipher/src/main/cpp/sqlite3_exception.h @@ -19,12 +19,12 @@ #define _SQLITE3_EXCEPTION_H 1 #include -#include -#include +/* #include */ +/* #include */ #include -namespace guardianproject { +namespace sqlcipher { /* throw a SQLiteException with a message appropriate for the error in handle */ void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle); @@ -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/info/guardianproject/database/AbstractCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java similarity index 90% rename from src/info/guardianproject/database/AbstractCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java index bb1c627f..f3eaf8aa 100644 --- a/src/info/guardianproject/database/AbstractCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractCursor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; import java.lang.ref.WeakReference; import java.util.HashMap; @@ -22,6 +22,10 @@ import android.content.ContentResolver; import android.database.CharArrayBuffer; +import android.database.ContentObservable; +import android.database.DataSetObservable; +import android.database.DataSetObserver; +import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.util.Config; @@ -32,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 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(); @@ -52,6 +58,8 @@ public abstract class AbstractCursor implements CrossProcessCursor { 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"); @@ -69,26 +77,26 @@ public CursorWindow getWindow() { public int getColumnCount() { return getColumnNames().length; } - + public void deactivate() { deactivateInternal(); } - + /** * @hide */ public void deactivateInternal() { if (mSelfObserver != null) { - // mContentResolver.unregisterContentObserver(mSelfObserver); + mContentResolver.unregisterContentObserver(mSelfObserver); mSelfObserverRegistered = false; } mDataSetObservable.notifyInvalidated(); } - + public boolean requery() { if (mSelfObserver != null && mSelfObserverRegistered == false) { - - // mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); + + mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); mSelfObserverRegistered = true; } mDataSetObservable.notifyChanged(); @@ -98,7 +106,7 @@ public boolean requery() { public boolean isClosed() { return mClosed; } - + public void close() { mClosed = true; mContentObservable.unregisterAll(); @@ -135,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); @@ -147,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() { @@ -194,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, 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(); - } + public void fillWindow(int position, android.database.CursorWindow window) { + DatabaseUtils.cursorFillWindow(this, position, window); } public final boolean move(int offset) { @@ -392,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); } @@ -411,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 @@ -458,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 @@ -466,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) { @@ -519,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) { @@ -608,6 +593,7 @@ public void onChange(boolean selfChange) { cursor.onChange(false); } } + } /** @@ -625,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/info/guardianproject/database/AbstractWindowedCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java similarity index 97% rename from src/info/guardianproject/database/AbstractWindowedCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java index 5b654434..deb25f33 100644 --- a/src/info/guardianproject/database/AbstractWindowedCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/AbstractWindowedCursor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; import android.database.CharArrayBuffer; @@ -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/info/guardianproject/database/BulkCursorNative.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java similarity index 97% rename from src/info/guardianproject/database/BulkCursorNative.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java index 250495a9..868dde6a 100644 --- a/src/info/guardianproject/database/BulkCursorNative.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorNative.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; import android.os.Binder; import android.os.RemoteException; @@ -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/info/guardianproject/database/BulkCursorToCursorAdaptor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java similarity index 98% rename from src/info/guardianproject/database/BulkCursorToCursorAdaptor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java index ba104e0b..932507e3 100644 --- a/src/info/guardianproject/database/BulkCursorToCursorAdaptor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/BulkCursorToCursorAdaptor.java @@ -14,17 +14,18 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; + +import java.util.Map; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.DataSetObserver; -import android.os.RemoteException; + import android.os.Bundle; +import android.os.RemoteException; import android.util.Log; -import java.util.Map; - /** * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local * process. @@ -89,7 +90,7 @@ public synchronized IContentObserver getObserver() { if (mObserverBridge == null) { mObserverBridge = new SelfContentObserver(this); } - return mObserverBridge.getContentObserver(); + return null;//mObserverBridge.getContentObserver(); //TODO nf fix this } @Override @@ -138,7 +139,7 @@ public void deactivate() { } mWindow = null; } - + @Override public void close() { super.close(); @@ -147,7 +148,7 @@ public void close() { } catch (RemoteException ex) { Log.w(TAG, "Remote process exception when closing"); } - mWindow = null; + mWindow = null; } @Override @@ -188,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) { @@ -245,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/info/guardianproject/database/CursorIndexOutOfBoundsException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java similarity index 96% rename from src/info/guardianproject/database/CursorIndexOutOfBoundsException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java index 7f2b35d3..0788b377 100644 --- a/src/info/guardianproject/database/CursorIndexOutOfBoundsException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorIndexOutOfBoundsException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; /** * An exception indicating that a cursor is out of bounds. diff --git a/src/info/guardianproject/database/CursorWindow.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java similarity index 70% rename from src/info/guardianproject/database/CursorWindow.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java index b9d3d032..b130dc26 100644 --- a/src/info/guardianproject/database/CursorWindow.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWindow.java @@ -14,13 +14,25 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; -import info.guardianproject.database.sqlcipher.SQLiteClosable; 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. @@ -28,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 @@ -40,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()); } /** @@ -60,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() { @@ -75,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 */ @@ -90,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 @@ -105,9 +145,9 @@ public boolean allocRow(){ releaseReference(); } } - - private native boolean allocRow_native(); - + + private native boolean allocRow_native(); + /** * Free the last row */ @@ -119,7 +159,7 @@ public void freeLastRow(){ releaseReference(); } } - + private native void freeLastRow_native(); /** @@ -137,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 @@ -155,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 @@ -173,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 @@ -192,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 @@ -209,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} @@ -228,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. * @@ -247,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(); @@ -271,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(); @@ -287,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(); @@ -303,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(); @@ -318,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 */ @@ -364,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 */ @@ -384,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 */ @@ -403,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 */ @@ -425,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 */ @@ -438,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 */ @@ -454,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 @@ -464,7 +581,7 @@ public float getFloat(int row, int col) { public void clear() { acquireReference(); try { - mStartPos = 0; + mStartPos = 0; native_clear(); } finally { releaseReference(); @@ -480,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) { @@ -514,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(); @@ -527,13 +647,16 @@ 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); @Override protected void onAllReferencesReleased() { - close_native(); + close_native(); + + 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/info/guardianproject/database/sqlcipher/SQLiteException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java similarity index 57% rename from src/info/guardianproject/database/sqlcipher/SQLiteException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java index beb291df..aae6be0f 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/CursorWrapper.java @@ -14,17 +14,26 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; - -import info.guardianproject.database.*; +package net.sqlcipher; /** - * A SQLite 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 SQLiteException extends SQLException { - public SQLiteException() {} +public class CursorWrapper extends android.database.CursorWrapper implements Cursor { + + private final Cursor mCursor; + + public CursorWrapper(Cursor cursor) { + super(cursor); + mCursor = cursor; + } - public SQLiteException(String error) { - super(error); + 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/info/guardianproject/database/sqlcipher/SQLiteFullException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java similarity index 51% rename from src/info/guardianproject/database/sqlcipher/SQLiteFullException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java index 67964049..58096f1d 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteFullException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 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,15 +14,20 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher; + +import net.sqlcipher.database.SQLiteDatabase; /** - * An exception that indicates that the SQLite database is full. + * An interface to let the apps define the actions to take when the following errors are detected + * database corruption */ -public class SQLiteFullException extends SQLiteException { - public SQLiteFullException() {} +public interface DatabaseErrorHandler { - public SQLiteFullException(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/info/guardianproject/database/DatabaseUtils.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java similarity index 87% rename from src/info/guardianproject/database/DatabaseUtils.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java index 075f1ea1..a89bebab 100644 --- a/src/info/guardianproject/database/DatabaseUtils.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/DatabaseUtils.java @@ -14,19 +14,11 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; -import android.database.Cursor; - -import info.guardianproject.database.sqlcipher.SQLiteAbortException; -import info.guardianproject.database.sqlcipher.SQLiteConstraintException; -import info.guardianproject.database.sqlcipher.SQLiteDatabase; -import info.guardianproject.database.sqlcipher.SQLiteDatabaseCorruptException; -import info.guardianproject.database.sqlcipher.SQLiteDiskIOException; -import info.guardianproject.database.sqlcipher.SQLiteException; -import info.guardianproject.database.sqlcipher.SQLiteFullException; -import info.guardianproject.database.sqlcipher.SQLiteProgram; -import info.guardianproject.database.sqlcipher.SQLiteStatement; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteProgram; +import net.sqlcipher.database.SQLiteStatement; import java.io.FileNotFoundException; import java.io.PrintStream; @@ -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/info/guardianproject/database/IBulkCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java similarity index 98% rename from src/info/guardianproject/database/IBulkCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java index 8b3ef2fb..a0a86164 100644 --- a/src/info/guardianproject/database/IBulkCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/IBulkCursor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; import android.os.RemoteException; import android.os.IBinder; 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/info/guardianproject/database/MatrixCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java similarity index 87% rename from src/info/guardianproject/database/MatrixCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java index c8a42593..6ca0798c 100644 --- a/src/info/guardianproject/database/MatrixCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/MatrixCursor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; import java.util.ArrayList; @@ -274,68 +274,15 @@ 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; } - @Override - public void registerContentObserver(ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterContentObserver(ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void copyStringToBuffer(int columnIndex, - android.database.CharArrayBuffer buffer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } } 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/info/guardianproject/database/StaleDataException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java similarity index 96% rename from src/info/guardianproject/database/StaleDataException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java index 9c891e3e..17209bcc 100644 --- a/src/info/guardianproject/database/StaleDataException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/StaleDataException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database; +package net.sqlcipher; /** * This exception is thrown when a Cursor contains stale data and must be 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/info/guardianproject/database/sqlcipher/DatabaseObjectNotClosedException.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java similarity index 95% rename from src/info/guardianproject/database/sqlcipher/DatabaseObjectNotClosedException.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java index b983e525..89f2adf7 100644 --- a/src/info/guardianproject/database/sqlcipher/DatabaseObjectNotClosedException.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/DatabaseObjectNotClosedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; /** * An exception that indicates that garbage-collector is finalizing a database object diff --git a/src/info/guardianproject/database/sqlcipher/SQLiteClosable.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java similarity index 96% rename from src/info/guardianproject/database/sqlcipher/SQLiteClosable.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java index 7fb6661f..93c50db2 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteClosable.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteClosable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; -import info.guardianproject.database.*; +import net.sqlcipher.*; /** * An object created from a SQLiteDatabase that can be closed. diff --git a/src/info/guardianproject/database/sqlcipher/SQLiteCompiledSql.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java similarity index 87% rename from src/info/guardianproject/database/sqlcipher/SQLiteCompiledSql.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java index 710984f6..52787c14 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteCompiledSql.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCompiledSql.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; import android.util.Log; @@ -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/info/guardianproject/database/sqlcipher/SQLiteContentHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java similarity index 73% rename from src/info/guardianproject/database/sqlcipher/SQLiteContentHelper.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java index de770c16..3300b610 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteContentHelper.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteContentHelper.java @@ -14,8 +14,8 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; -import info.guardianproject.database.*; +package net.sqlcipher.database; +import net.sqlcipher.*; import android.content.res.AssetFileDescriptor; import android.os.MemoryFile; @@ -45,18 +45,29 @@ public class SQLiteContentHelper { * value of column 0 is NULL, or if there is an error creating the * asset file descriptor. */ - public static MemoryFile getBlobColumnAsAssetFile(SQLiteDatabase db, String sql, - String[] selectionArgs) throws FileNotFoundException { - try { - MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs); - if (file == null) { - throw new FileNotFoundException("No results."); - } - return file; - } catch (IOException ex) { - throw new FileNotFoundException(ex.toString()); - } - } + public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql, + String[] selectionArgs) throws FileNotFoundException { + android.os.ParcelFileDescriptor fd = null; + + try { + MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs); + if (file == null) { + throw new FileNotFoundException("No results."); + } + Class c = file.getClass(); + try { + java.lang.reflect.Method m = c.getDeclaredMethod("getParcelFileDescriptor"); + m.setAccessible(true); + 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) { + throw new FileNotFoundException(ex.toString()); + } + } /** * Runs an SQLite query and returns a MemoryFile for the @@ -84,6 +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/info/guardianproject/database/sqlcipher/SQLiteCursor.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java similarity index 77% rename from src/info/guardianproject/database/sqlcipher/SQLiteCursor.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java index f9da9743..a216d986 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteCursor.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursor.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; -import info.guardianproject.database.AbstractWindowedCursor; -import info.guardianproject.database.CursorWindow; -import info.guardianproject.database.DataSetObserver; -import info.guardianproject.database.SQLException; +import net.sqlcipher.AbstractWindowedCursor; +import net.sqlcipher.BuildConfig; +import net.sqlcipher.CursorWindow; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import android.database.CharArrayBuffer; -import android.database.ContentObserver; -import android.database.CrossProcessCursor; +import android.database.DataSetObserver; +import android.database.SQLException; import android.os.Handler; import android.os.Message; import android.os.Process; @@ -43,7 +43,7 @@ * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple * threads should perform its own synchronization when using the SQLiteCursor. */ -public class SQLiteCursor extends AbstractWindowedCursor implements CrossProcessCursor { +public class SQLiteCursor extends AbstractWindowedCursor { static final String TAG = "Cursor"; static final int NO_COUNT = -1; @@ -65,14 +65,18 @@ public class SQLiteCursor extends AbstractWindowedCursor implements CrossProcess /** 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; @@ -80,13 +84,17 @@ public class SQLiteCursor extends AbstractWindowedCursor implements CrossProcess 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 @@ -96,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 */ @@ -125,7 +133,7 @@ private void sendMessage() { } else { mPendingData = true; } - + } public void run() { // use cached mWindow, to avoid get null mWindow @@ -133,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(); @@ -145,7 +156,7 @@ public void run() { if (count == NO_COUNT){ mCount += mMaxRead; sendMessage(); - } else { + } else { mCount = count; sendMessage(); break; @@ -160,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; @@ -195,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 @@ -234,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; @@ -275,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 */); @@ -288,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 @@ -315,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); @@ -337,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; @@ -404,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; } /* @@ -489,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 { @@ -607,52 +650,51 @@ protected void finalize() { } } - @Override - public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { - - - } - - @Override - public void registerContentObserver(ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterContentObserver(ContentObserver observer) { - // TODO Auto-generated method stub - - } - @Override - public void unregisterDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } @Override - public void fillWindow(int startPos, android.database.CursorWindow window) { - - - window.setStartPosition(startPos); - mCount = mQuery.fillWindow((info.guardianproject.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(); - } - + 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/info/guardianproject/database/sqlcipher/SQLiteCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java similarity index 84% rename from src/info/guardianproject/database/sqlcipher/SQLiteCursorDriver.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java index c2a8fb65..1ea66aba 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteCursorDriver.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; -import info.guardianproject.database.sqlcipher.SQLiteDatabase.CursorFactory; -import info.guardianproject.database.*; +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; +import net.sqlcipher.*; /** * A driver for SQLiteCursors that is used to create them and gets notified @@ -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,10 +40,10 @@ public interface SQLiteCursorDriver { /** * Called by a SQLiteCursor when it is requeryed. - * + * * @return The new count value. */ - void cursorRequeried(Cursor cursor); + void cursorRequeried(android.database.Cursor cursor); /** * Called by a SQLiteCursor when it it closed to destroy this object as well. 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/info/guardianproject/database/sqlcipher/SQLiteDebug.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java similarity index 99% rename from src/info/guardianproject/database/sqlcipher/SQLiteDebug.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java index 5a8288dc..d90f0174 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteDebug.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; import java.util.ArrayList; diff --git a/src/info/guardianproject/database/sqlcipher/SQLiteDirectCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java similarity index 70% rename from src/info/guardianproject/database/sqlcipher/SQLiteDirectCursorDriver.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java index a7af18cf..36ae59a4 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteDirectCursorDriver.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; -import info.guardianproject.database.*; -import info.guardianproject.database.sqlcipher.SQLiteDatabase.CursorFactory; +import net.sqlcipher.Cursor; +import net.sqlcipher.database.SQLiteDatabase.CursorFactory; /** * 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,6 +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); } @@ -75,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/src/info/guardianproject/database/sqlcipher/SQLiteOpenHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java similarity index 54% rename from src/info/guardianproject/database/sqlcipher/SQLiteOpenHelper.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java index b883358d..e3a24f43 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteOpenHelper.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java @@ -14,12 +14,16 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; import java.io.File; import android.content.Context; -import info.guardianproject.database.sqlcipher.SQLiteDatabase.CursorFactory; +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; /** @@ -38,10 +42,30 @@ public abstract class SQLiteOpenHelper { 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 @@ -52,14 +76,43 @@ public abstract class SQLiteOpenHelper { * @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) { + 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; } /** @@ -74,7 +127,16 @@ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int * @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 } @@ -95,24 +157,19 @@ public synchronized SQLiteDatabase getWritableDatabase(String password) { try { mIsInitializing = true; if (mName == null) { - db = SQLiteDatabase.create(null, password); - + db = SQLiteDatabase.create(null, ""); } else { String path = mContext.getDatabasePath(mName).getPath(); - File dbPathFile = new File (path); - if (!dbPathFile.exists()) + 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); - + } + db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory, mHook, mErrorHandler); } - - + if(mDeferSetWriteAheadLoggingEnabled) { + mEnableWriteAheadLogging = db.enableWriteAheadLogging(); + } + onConfigure(db); int version = db.getVersion(); if (version != mNewVersion) { db.beginTransaction(); @@ -120,7 +177,11 @@ public synchronized SQLiteDatabase getWritableDatabase(String password) { if (version == 0) { onCreate(db); } else { + if(version > mNewVersion) { + onDowngrade(db, version, mNewVersion); + } else { onUpgrade(db, version, mNewVersion); + } } db.setVersion(mNewVersion); db.setTransactionSuccessful(); @@ -161,6 +222,14 @@ public synchronized SQLiteDatabase getWritableDatabase(String password) { * 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 } @@ -180,7 +249,19 @@ public synchronized SQLiteDatabase getReadableDatabase(String password) { try { mIsInitializing = true; String path = mContext.getDatabasePath(mName).getPath(); - db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY); + 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); @@ -208,6 +289,81 @@ public synchronized void close() { } } + /** + * 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. diff --git a/src/info/guardianproject/database/sqlcipher/SQLiteProgram.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java similarity index 88% rename from src/info/guardianproject/database/sqlcipher/SQLiteProgram.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java index 99fcce57..e43826d9 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteProgram.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +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/info/guardianproject/database/sqlcipher/SQLiteQuery.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java similarity index 66% rename from src/info/guardianproject/database/sqlcipher/SQLiteQuery.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java index f29f7ee7..c87bd590 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteQuery.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; -import info.guardianproject.database.*; +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/info/guardianproject/database/sqlcipher/SQLiteQueryBuilder.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java similarity index 98% rename from src/info/guardianproject/database/sqlcipher/SQLiteQueryBuilder.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java index 56307909..d47f5593 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteQueryBuilder.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; -import info.guardianproject.database.*; +package net.sqlcipher.database; + +import net.sqlcipher.*; import android.provider.BaseColumns; import android.text.TextUtils; @@ -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/info/guardianproject/database/sqlcipher/SQLiteStatement.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java similarity index 86% rename from src/info/guardianproject/database/sqlcipher/SQLiteStatement.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java index f8f189e0..84b7b4c2 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteStatement.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +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/info/guardianproject/database/sqlcipher/SQLiteTransactionListener.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java similarity index 95% rename from src/info/guardianproject/database/sqlcipher/SQLiteTransactionListener.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java index 22e6ddfc..69680ee3 100644 --- a/src/info/guardianproject/database/sqlcipher/SQLiteTransactionListener.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; /** * A listener for transaction events. diff --git a/src/info/guardianproject/database/sqlcipher/SqliteWrapper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java similarity index 94% rename from src/info/guardianproject/database/sqlcipher/SqliteWrapper.java rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java index 424128c5..1d15f99e 100644 --- a/src/info/guardianproject/database/sqlcipher/SqliteWrapper.java +++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java @@ -15,12 +15,15 @@ * limitations under the License. */ -package info.guardianproject.database.sqlcipher; +package net.sqlcipher.database; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import info.guardianproject.database.*; + +import net.sqlcipher.*; + +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.util.Log; import android.widget.Toast; @@ -63,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/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/default.properties b/default.properties deleted file mode 100644 index 1c386fdb..00000000 --- a/default.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, -# "build.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-6 diff --git a/external/Android.mk b/external/Android.mk deleted file mode 100644 index 3cbf5d02..00000000 --- a/external/Android.mk +++ /dev/null @@ -1,92 +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 - -LOCAL_PATH := $(call my-dir) -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 - 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 - 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/ -## install -p -m644 android-2.2/*.so ../obj/local/armeabi/ - -project_ldflags:= -Llibs/armeabi/ -Landroid-2.3/ - -#------------------------------------------------------------------------------# -# 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 - -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 := openssl/include sqlcipher -LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDLIBS += -lcrypto -LOCAL_MODULE := libsqlcipher -LOCAL_SRC_FILES := $(sqlcipher_files) - -include $(BUILD_SHARED_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 \ -# 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 -LOCAL_CFLAGS += $(android_sqlite_cflags) $(sqlite_cflags) -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/sqlcipher \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/icu4c/i18n \ - $(LOCAL_PATH)/icu4c/common \ -# $(LOCAL_PATH)/platform-system-core/include \ -# $(LOCAL_PATH)/platform-frameworks-base/include -#LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDFLAGS += -L$(LOCAL_PATH)/android-2.3/ -L$(LOCAL_PATH)/libs/armeabi/ -LOCAL_LDLIBS := -lsqlcipher -llog -licuuc -licui18n -lutils -lcutils -LOCAL_MODULE := libsqlcipher_android -LOCAL_SRC_FILES := $(libsqlite3_android_local_src_files) - -include $(BUILD_SHARED_LIBRARY) diff --git a/external/Android.mk-2.2 b/external/Android.mk-2.2 deleted file mode 100644 index d5b962ec..00000000 --- a/external/Android.mk-2.2 +++ /dev/null @@ -1,92 +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 - -LOCAL_PATH := $(call my-dir) -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 - 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 - 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/ -## install -p -m644 android-2.2/*.so ../obj/local/armeabi/ - -project_ldflags:= -Llibs/armeabi/ -Landroid-2.2/ - -#------------------------------------------------------------------------------# -# 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 - -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 := openssl/include sqlcipher -LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDLIBS += -lcrypto -LOCAL_MODULE := libsqlcipher -LOCAL_SRC_FILES := $(sqlcipher_files) - -include $(BUILD_SHARED_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 \ -# 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 -LOCAL_CFLAGS += $(android_sqlite_cflags) $(sqlite_cflags) -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/sqlcipher \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/icu4c/i18n \ - $(LOCAL_PATH)/icu4c/common \ -# $(LOCAL_PATH)/platform-system-core/include \ -# $(LOCAL_PATH)/platform-frameworks-base/include -#LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDFLAGS += -L$(LOCAL_PATH)/android-2.2/ -L$(LOCAL_PATH)/libs/armeabi/ -LOCAL_LDLIBS := -lsqlcipher -llog -licuuc -licui18n -lutils -lcutils -LOCAL_MODULE := libsqlcipher_android -LOCAL_SRC_FILES := $(libsqlite3_android_local_src_files) - -include $(BUILD_SHARED_LIBRARY) diff --git a/external/Android.mk-2.3 b/external/Android.mk-2.3 deleted file mode 100644 index 3cbf5d02..00000000 --- a/external/Android.mk-2.3 +++ /dev/null @@ -1,92 +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 - -LOCAL_PATH := $(call my-dir) -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 - 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 - 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/ -## install -p -m644 android-2.2/*.so ../obj/local/armeabi/ - -project_ldflags:= -Llibs/armeabi/ -Landroid-2.3/ - -#------------------------------------------------------------------------------# -# 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 - -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 := openssl/include sqlcipher -LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDLIBS += -lcrypto -LOCAL_MODULE := libsqlcipher -LOCAL_SRC_FILES := $(sqlcipher_files) - -include $(BUILD_SHARED_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 \ -# 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 -LOCAL_CFLAGS += $(android_sqlite_cflags) $(sqlite_cflags) -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/sqlcipher \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/icu4c/i18n \ - $(LOCAL_PATH)/icu4c/common \ -# $(LOCAL_PATH)/platform-system-core/include \ -# $(LOCAL_PATH)/platform-frameworks-base/include -#LOCAL_LDFLAGS += $(project_ldflags) -LOCAL_LDFLAGS += -L$(LOCAL_PATH)/android-2.3/ -L$(LOCAL_PATH)/libs/armeabi/ -LOCAL_LDLIBS := -lsqlcipher -llog -licuuc -licui18n -lutils -lcutils -LOCAL_MODULE := libsqlcipher_android -LOCAL_SRC_FILES := $(libsqlite3_android_local_src_files) - -include $(BUILD_SHARED_LIBRARY) diff --git a/external/android-2.2/libcrypto.so b/external/android-2.2/libcrypto.so deleted file mode 100644 index 0686d909..00000000 Binary files a/external/android-2.2/libcrypto.so and /dev/null differ diff --git a/external/android-2.2/libcutils.so b/external/android-2.2/libcutils.so deleted file mode 100644 index 842fc307..00000000 Binary files a/external/android-2.2/libcutils.so and /dev/null differ diff --git a/external/android-2.2/libicui18n.so b/external/android-2.2/libicui18n.so deleted file mode 100644 index 0b9875d9..00000000 Binary files a/external/android-2.2/libicui18n.so and /dev/null differ diff --git a/external/android-2.2/libicuuc.so b/external/android-2.2/libicuuc.so deleted file mode 100644 index 5de770f8..00000000 Binary files a/external/android-2.2/libicuuc.so and /dev/null differ diff --git a/external/android-2.2/liblog.so b/external/android-2.2/liblog.so deleted file mode 100644 index b0674f8f..00000000 Binary files a/external/android-2.2/liblog.so and /dev/null differ diff --git a/external/android-2.2/libnativehelper.so b/external/android-2.2/libnativehelper.so deleted file mode 100644 index 5e84fffc..00000000 Binary files a/external/android-2.2/libnativehelper.so and /dev/null differ diff --git a/external/android-2.2/libssl.so b/external/android-2.2/libssl.so deleted file mode 100644 index 59398d0e..00000000 Binary files a/external/android-2.2/libssl.so and /dev/null differ diff --git a/external/android-2.2/libstlport_shared.so b/external/android-2.2/libstlport_shared.so deleted file mode 100755 index 165ca68f..00000000 Binary files a/external/android-2.2/libstlport_shared.so and /dev/null differ diff --git a/external/android-2.2/libutils.so b/external/android-2.2/libutils.so deleted file mode 100644 index 8208fae1..00000000 Binary files a/external/android-2.2/libutils.so and /dev/null differ diff --git a/external/android-2.3/libcrypto.so b/external/android-2.3/libcrypto.so deleted file mode 100644 index 3fc3849b..00000000 Binary files a/external/android-2.3/libcrypto.so and /dev/null differ diff --git a/external/android-2.3/libcutils.so b/external/android-2.3/libcutils.so deleted file mode 100644 index d6019f9c..00000000 Binary files a/external/android-2.3/libcutils.so and /dev/null differ diff --git a/external/android-2.3/libicui18n.so b/external/android-2.3/libicui18n.so deleted file mode 100644 index 8c38c575..00000000 Binary files a/external/android-2.3/libicui18n.so and /dev/null differ diff --git a/external/android-2.3/libicuuc.so b/external/android-2.3/libicuuc.so deleted file mode 100644 index b26575ce..00000000 Binary files a/external/android-2.3/libicuuc.so and /dev/null differ diff --git a/external/android-2.3/liblog.so b/external/android-2.3/liblog.so deleted file mode 100644 index 01f7a8d2..00000000 Binary files a/external/android-2.3/liblog.so and /dev/null differ diff --git a/external/android-2.3/libnativehelper.so b/external/android-2.3/libnativehelper.so deleted file mode 100644 index 3d300ba3..00000000 Binary files a/external/android-2.3/libnativehelper.so and /dev/null differ diff --git a/external/android-2.3/libssl.so b/external/android-2.3/libssl.so deleted file mode 100644 index 5c000c6e..00000000 Binary files a/external/android-2.3/libssl.so and /dev/null differ diff --git a/external/android-2.3/libstlport_shared.so b/external/android-2.3/libstlport_shared.so deleted file mode 100755 index 165ca68f..00000000 Binary files a/external/android-2.3/libstlport_shared.so and /dev/null differ diff --git a/external/android-2.3/libutils.so b/external/android-2.3/libutils.so deleted file mode 100644 index 6b5f517f..00000000 Binary files a/external/android-2.3/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 fa7b84a2..00000000 --- a/external/icu4c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fa7b84a26dd6c44cb0d219ef7b2f7e258219b616 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 93552de8..00000000 --- a/external/platform-frameworks-base +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93552de8e305027fb003401e347b9493c64c981f diff --git a/external/platform-system-core b/external/platform-system-core deleted file mode 160000 index 034117e4..00000000 --- a/external/platform-system-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 034117e47f2601d46563461a0bfe3cc22f89a0f0 diff --git a/external/sqlcipher b/external/sqlcipher deleted file mode 160000 index 5e4b18d1..00000000 --- a/external/sqlcipher +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5e4b18d1d9df26eae286eed6f89770dd7c9d748c 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 7d4333db..00000000 --- a/jni/Android.mk +++ /dev/null @@ -1,71 +0,0 @@ -uOCAL_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:= \ - info_guardianproject_database_sqlcipher_SQLiteCompiledSql.cpp \ - info_guardianproject_database_sqlcipher_SQLiteDatabase.cpp \ - info_guardianproject_database_sqlcipher_SQLiteProgram.cpp \ - info_guardianproject_database_sqlcipher_SQLiteQuery.cpp \ - info_guardianproject_database_sqlcipher_SQLiteStatement.cpp \ - info_guardianproject_database_CursorWindow.cpp \ - CursorWindow.cpp -# info_guardianproject_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-2.2/ -L../external/libs/armeabi/ -LOCAL_LDFLAGS += -L/home/n8fr8/android/mydroid/out/target/product/generic/obj/SHARED_LIBRARIES/libutils_intermediates/LINKED/ -L/home/n8fr8/android/mydroid/out/target/product/generic/obj/SHARED_LIBRARIES/libbinder_intermediates/LINKED/ -L/home/n8fr8/android/mydroid/out/target/product/generic/obj/SHARED_LIBRARIES/libandroid_runtime_intermediates/LINKED/ - -# 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_LDLIBS += -lsqlcipher -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/include/utils/String16.h b/jni/include/utils/String16.h deleted file mode 100644 index 07a0c118..00000000 --- a/jni/include/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/jni/info_guardianproject_database_CursorWindow.cpp b/jni/info_guardianproject_database_CursorWindow.cpp deleted file mode 100644 index d2498033..00000000 --- a/jni/info_guardianproject_database_CursorWindow.cpp +++ /dev/null @@ -1,725 +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 -#include - -#include "CursorWindow.h" -#include "sqlite3_exception.h" -#include "android_util_Binder.h" - -namespace guardianproject { - -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("android/database/CursorWindow"); - if (clazz == NULL) { - LOGE("Can't find android/database/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, "info/guardianproject/database/CursorWindow", - sMethods, NELEM(sMethods)); -} - -} // namespace guardianproject diff --git a/jni/info_guardianproject_database_sqlcipher_SQLiteDatabase.cpp b/jni/info_guardianproject_database_sqlcipher_SQLiteDatabase.cpp deleted file mode 100644 index bffdb749..00000000 --- a/jni/info_guardianproject_database_sqlcipher_SQLiteDatabase.cpp +++ /dev/null @@ -1,609 +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 -#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 guardianproject { - - -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 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, "info/guardianproject/database/sqlcipher/SQLiteDatabaseCorruptException", text); - goto done; - } - } -#endif - - err = register_android_functions(handle, UTF16_STORAGE); - if (err) { - throw_sqlite3_exception(env, handle); - goto done; - } - - 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}, - -}; - -int register_android_database_SQLiteDatabase(JNIEnv *env) -{ - jclass clazz; - - clazz = env->FindClass("info/guardianproject/database/sqlcipher/SQLiteDatabase"); - if (clazz == NULL) { - LOGE("Can't find info/guardianproject/database/sqlcipher/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, "info/guardianproject/database/sqlcipher/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 = "info/guardianproject/database/sqlcipher/SQLiteDiskIOException"; - break; - case SQLITE_CORRUPT: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteDatabaseCorruptException"; - break; - case SQLITE_CONSTRAINT: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteConstraintException"; - break; - case SQLITE_ABORT: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteAbortException"; - break; - case SQLITE_DONE: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteDoneException"; - break; - case SQLITE_FULL: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteFullException"; - break; - case SQLITE_MISUSE: - exceptionClass = "info/guardianproject/database/sqlcipher/SQLiteMisuseException"; - break; - default: - exceptionClass = "info/guardianproject/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 guardianproject 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/armeabi/libdatabase_sqlcipher.so b/libs/armeabi/libdatabase_sqlcipher.so deleted file mode 100755 index 27ecd1a8..00000000 Binary files a/libs/armeabi/libdatabase_sqlcipher.so and /dev/null differ diff --git a/libs/armeabi/libsqlcipher.so b/libs/armeabi/libsqlcipher.so deleted file mode 100644 index 4fec579b..00000000 Binary files a/libs/armeabi/libsqlcipher.so and /dev/null differ diff --git a/libs/armeabi/libsqlcipher_android-10.so b/libs/armeabi/libsqlcipher_android-10.so deleted file mode 100755 index 1c754942..00000000 Binary files a/libs/armeabi/libsqlcipher_android-10.so and /dev/null differ diff --git a/libs/armeabi/libsqlcipher_android-8.so b/libs/armeabi/libsqlcipher_android-8.so deleted file mode 100755 index 79c38f6b..00000000 Binary files a/libs/armeabi/libsqlcipher_android-8.so and /dev/null differ diff --git a/libs/armeabi/libsqlcipher_android-9.so b/libs/armeabi/libsqlcipher_android-9.so deleted file mode 100755 index 1c754942..00000000 Binary files a/libs/armeabi/libsqlcipher_android-9.so and /dev/null differ diff --git a/libs/armeabi/libstlport_shared.so b/libs/armeabi/libstlport_shared.so deleted file mode 100755 index 165ca68f..00000000 Binary files a/libs/armeabi/libstlport_shared.so and /dev/null differ 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/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 3a5f117d..00000000 --- a/res/layout/main.xml +++ /dev/null @@ -1,12 +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 9580615a..00000000 --- a/src/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/src/example/SQLDemoActivity.java b/src/example/SQLDemoActivity.java deleted file mode 100644 index 012492b7..00000000 --- a/src/example/SQLDemoActivity.java +++ /dev/null @@ -1,72 +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"; - - //then you can open the database using a password - SQLiteDatabase db = eventsData.getWritableDatabase(password); - db.close(); - - eventsData = new EventDataSQLHelper(this); - addEvent("Hello Android Event", password); - Cursor cursor = getEvents(password); - showEvents(cursor); - } - - @Override - public void onDestroy() { - eventsData.close(); - } - - private void addEvent(String title, String password) { - SQLiteDatabase db = eventsData.getWritableDatabase(password); - - ContentValues values = new ContentValues(); - values.put(EventDataSQLHelper.TIME, System.currentTimeMillis()); - values.put(EventDataSQLHelper.TITLE, title); - db.insert(EventDataSQLHelper.TABLE, null, values); - - } - - private Cursor getEvents(String password) { - SQLiteDatabase db = eventsData.getReadableDatabase(password); - - - - 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/src/info/guardianproject/database/ContentObservable.java b/src/info/guardianproject/database/ContentObservable.java deleted file mode 100644 index 92ac3394..00000000 --- a/src/info/guardianproject/database/ContentObservable.java +++ /dev/null @@ -1,56 +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 info.guardianproject.database; - -/** - * A specialization of Observable for ContentObserver that provides methods for - * invoking the various callback methods of ContentObserver. - */ -public class ContentObservable extends Observable { - - @Override - public void registerObserver(ContentObserver observer) { - super.registerObserver(observer); - } - - /** - * invokes dispatchUpdate on each observer, unless the observer doesn't want - * self-notifications and the update is from a self-notification - * @param selfChange - */ - public void dispatchChange(boolean selfChange) { - synchronized(mObservers) { - for (ContentObserver observer : mObservers) { - if (!selfChange || observer.deliverSelfNotifications()) { - observer.dispatchChange(selfChange); - } - } - } - } - - /** - * invokes onChange on each observer - * @param selfChange - */ - public void notifyChange(boolean selfChange) { - synchronized(mObservers) { - for (ContentObserver observer : mObservers) { - observer.onChange(selfChange); - } - } - } -} diff --git a/src/info/guardianproject/database/ContentObserver.java b/src/info/guardianproject/database/ContentObserver.java deleted file mode 100644 index f3c64cf1..00000000 --- a/src/info/guardianproject/database/ContentObserver.java +++ /dev/null @@ -1,133 +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 info.guardianproject.database; - -import android.os.Handler; - -/** - * Receives call backs for changes to content. Must be implemented by objects which are added - * to a {@link ContentObservable}. - */ -public abstract class ContentObserver extends android.database.ContentObserver { - - private Transport mTransport; - - // Protects mTransport - private Object lock = new Object(); - - /* package */ Handler mHandler; - - private final class NotificationRunnable implements Runnable { - - private boolean mSelf; - - public NotificationRunnable(boolean self) { - mSelf = self; - } - - public void run() { - ContentObserver.this.onChange(mSelf); - } - } - - private static final class Transport extends IContentObserver.Stub { - ContentObserver mContentObserver; - - public Transport(ContentObserver contentObserver) { - mContentObserver = contentObserver; - } - - public boolean deliverSelfNotifications() { - ContentObserver contentObserver = mContentObserver; - if (contentObserver != null) { - return contentObserver.deliverSelfNotifications(); - } - return false; - } - - public void onChange(boolean selfChange) { - ContentObserver contentObserver = mContentObserver; - if (contentObserver != null) { - contentObserver.dispatchChange(selfChange); - } - } - - public void releaseContentObserver() { - mContentObserver = null; - } - } - - /** - * onChange() will happen on the provider Handler. - * - * @param handler The handler to run {@link #onChange} on. - */ - public ContentObserver(Handler handler) { - - super(handler); - mHandler = handler; - } - - /** - * Gets access to the binder transport object. Not for public consumption. - * - * {@hide} - */ - public IContentObserver getContentObserver() { - synchronized(lock) { - if (mTransport == null) { - mTransport = new Transport(this); - } - return mTransport; - } - } - - /** - * Gets access to the binder transport object, and unlinks the transport object - * from the ContentObserver. Not for public consumption. - * - * {@hide} - */ - public IContentObserver releaseContentObserver() { - synchronized(lock) { - Transport oldTransport = mTransport; - if (oldTransport != null) { - oldTransport.releaseContentObserver(); - mTransport = null; - } - return oldTransport; - } - } - - /** - * Returns true if this observer is interested in notifications for changes - * made through the cursor the observer is registered with. - */ - public boolean deliverSelfNotifications() { - return false; - } - - /** - * This method is called when a change occurs to the cursor that - * is being observed. - * - * @param selfChange true if the update was caused by a call to commit on the - * cursor that is being observed. - */ - public void onChange(boolean selfChange) {} - -} diff --git a/src/info/guardianproject/database/CrossProcessCursor.java b/src/info/guardianproject/database/CrossProcessCursor.java deleted file mode 100644 index 901c8f73..00000000 --- a/src/info/guardianproject/database/CrossProcessCursor.java +++ /dev/null @@ -1,42 +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 info.guardianproject.database; - -public interface CrossProcessCursor extends Cursor{ - /** - * returns a pre-filled window, return NULL if no such window - */ - CursorWindow getWindow(); - - /** - * copies cursor data into the window start at pos - */ - void fillWindow(int pos, CursorWindow winow); - - /** - * This function is called every time the cursor is successfully scrolled - * to a new position, giving the subclass a chance to update any state it - * may have. If it returns false the move function will also do so and the - * cursor will scroll to the beforeFirst position. - * - * @param oldPosition the position that we're moving from - * @param newPosition the position that we're moving to - * @return true if the move is successful, false otherwise - */ - boolean onMove(int oldPosition, int newPosition); - -} diff --git a/src/info/guardianproject/database/Cursor.java b/src/info/guardianproject/database/Cursor.java deleted file mode 100644 index c14695eb..00000000 --- a/src/info/guardianproject/database/Cursor.java +++ /dev/null @@ -1,588 +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 info.guardianproject.database; - -import java.util.Map; - -import android.content.ContentResolver; -import android.database.CharArrayBuffer; -import android.net.Uri; -import android.os.Bundle; - -/** - * This interface provides random read-write access to the result set returned - * by a database query. - */ -public interface Cursor extends android.database.Cursor { - /** - * Returns the numbers of rows in the cursor. - * - * @return the number of rows in the cursor. - */ - int getCount(); - - /** - * Returns the current position of the cursor in the row set. - * The value is zero-based. When the row set is first returned the cursor - * will be at positon -1, which is before the first row. After the - * last row is returned another call to next() will leave the cursor past - * the last entry, at a position of count(). - * - * @return the current cursor position. - */ - int getPosition(); - - /** - * Move the cursor by a relative amount, forward or backward, from the - * current position. Positive offsets move forwards, negative offsets move - * backwards. If the final position is outside of the bounds of the result - * set then the resultant position will be pinned to -1 or count() depending - * on whether the value is off the front or end of the set, respectively. - * - *

This method will return true if the requested destination was - * reachable, otherwise, it returns false. For example, if the cursor is at - * currently on the second entry in the result set and move(-5) is called, - * the position will be pinned at -1, and false will be returned. - * - * @param offset the offset to be applied from the current position. - * @return whether the requested move fully succeeded. - */ - boolean move(int offset); - - /** - * Move the cursor to an absolute position. The valid - * range of values is -1 <= position <= count. - * - *

This method will return true if the request destination was reachable, - * otherwise, it returns false. - * - * @param position the zero-based position to move to. - * @return whether the requested move fully succeeded. - */ - boolean moveToPosition(int position); - - /** - * Move the cursor to the first row. - * - *

This method will return false if the cursor is empty. - * - * @return whether the move succeeded. - */ - boolean moveToFirst(); - - /** - * Move the cursor to the last row. - * - *

This method will return false if the cursor is empty. - * - * @return whether the move succeeded. - */ - boolean moveToLast(); - - /** - * Move the cursor to the next row. - * - *

This method will return false if the cursor is already past the - * last entry in the result set. - * - * @return whether the move succeeded. - */ - boolean moveToNext(); - - /** - * Move the cursor to the previous row. - * - *

This method will return false if the cursor is already before the - * first entry in the result set. - * - * @return whether the move succeeded. - */ - boolean moveToPrevious(); - - /** - * Returns whether the cursor is pointing to the first row. - * - * @return whether the cursor is pointing at the first entry. - */ - boolean isFirst(); - - /** - * Returns whether the cursor is pointing to the last row. - * - * @return whether the cursor is pointing at the last entry. - */ - boolean isLast(); - - /** - * Returns whether the cursor is pointing to the position before the first - * row. - * - * @return whether the cursor is before the first result. - */ - boolean isBeforeFirst(); - - /** - * Returns whether the cursor is pointing to the position after the last - * row. - * - * @return whether the cursor is after the last result. - */ - boolean isAfterLast(); - - /** - * Removes the row at the current cursor position from the underlying data - * store. After this method returns the cursor will be pointing to the row - * after the row that is deleted. This has the side effect of decrementing - * the result of count() by one. - *

- * The query must have the row ID column in its selection, otherwise this - * call will fail. - * - * @hide - * @return whether the record was successfully deleted. - * @deprecated use {@link ContentResolver#delete(Uri, String, String[])} - */ - @Deprecated - boolean deleteRow(); - - /** - * Returns the zero-based index for the given column name, or -1 if the column doesn't exist. - * If you expect the column to exist use {@link #getColumnIndexOrThrow(String)} instead, which - * will make the error more clear. - * - * @param columnName the name of the target column. - * @return the zero-based column index for the given column name, or -1 if - * the column name does not exist. - * @see #getColumnIndexOrThrow(String) - */ - int getColumnIndex(String columnName); - - /** - * Returns the zero-based index for the given column name, or throws - * {@link IllegalArgumentException} if the column doesn't exist. If you're not sure if - * a column will exist or not use {@link #getColumnIndex(String)} and check for -1, which - * is more efficient than catching the exceptions. - * - * @param columnName the name of the target column. - * @return the zero-based column index for the given column name - * @see #getColumnIndex(String) - * @throws IllegalArgumentException if the column does not exist - */ - int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException; - - /** - * Returns the column name at the given zero-based column index. - * - * @param columnIndex the zero-based index of the target column. - * @return the column name for the given column index. - */ - String getColumnName(int columnIndex); - - /** - * Returns a string array holding the names of all of the columns in the - * result set in the order in which they were listed in the result. - * - * @return the names of the columns returned in this query. - */ - String[] getColumnNames(); - - /** - * Return total number of columns - * @return number of columns - */ - int getColumnCount(); - - /** - * Returns the value of the requested column as a byte array. - * - *

If the native content of that column is not blob exception may throw - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a byte array. - */ - byte[] getBlob(int columnIndex); - - /** - * Returns the value of the requested column as a String. - * - *

If the native content of that column is not text the result will be - * the result of passing the column value to String.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a String. - */ - String getString(int columnIndex); - - /** - * Retrieves the requested column text and stores it in the buffer provided. - * If the buffer size is not sufficient, a new char buffer will be allocated - * and assigned to CharArrayBuffer.data - * @param columnIndex the zero-based index of the target column. - * if the target column is null, return buffer - * @param buffer the buffer to copy the text into. - */ - void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer); - - /** - * Returns the value of the requested column as a short. - * - *

If the native content of that column is not numeric the result will be - * the result of passing the column value to Short.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a short. - */ - short getShort(int columnIndex); - - /** - * Returns the value of the requested column as an int. - * - *

If the native content of that column is not numeric the result will be - * the result of passing the column value to Integer.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as an int. - */ - int getInt(int columnIndex); - - /** - * Returns the value of the requested column as a long. - * - *

If the native content of that column is not numeric the result will be - * the result of passing the column value to Long.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a long. - */ - long getLong(int columnIndex); - - /** - * Returns the value of the requested column as a float. - * - *

If the native content of that column is not numeric the result will be - * the result of passing the column value to Float.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a float. - */ - float getFloat(int columnIndex); - - /** - * Returns the value of the requested column as a double. - * - *

If the native content of that column is not numeric the result will be - * the result of passing the column value to Double.valueOf(x). - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a double. - */ - double getDouble(int columnIndex); - - /** - * Returns true if the value in the indicated column is null. - * - * @param columnIndex the zero-based index of the target column. - * @return whether the column value is null. - */ - boolean isNull(int columnIndex); - - /** - * Returns true if the cursor supports updates. - * - * @return whether the cursor supports updates. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean supportsUpdates(); - - /** - * 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 use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean hasUpdates(); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateBlob(int columnIndex, byte[] value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateString(int columnIndex, String value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateShort(int columnIndex, short value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateInt(int columnIndex, int value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateLong(int columnIndex, long value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateFloat(int columnIndex, float value); - - /** - * Updates the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @param value the new value. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateDouble(int columnIndex, double value); - - /** - * Removes the value for the given column in the row the cursor is - * currently pointing at. Updates are not committed to the backing store - * until {@link #commitUpdates()} is called. - * - * @param columnIndex the zero-based index of the target column. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean updateToNull(int columnIndex); - - /** - * Atomically commits all updates to the backing store. After completion, - * this method leaves the data in an inconsistent state and you should call - * {@link #requery} before reading data from the cursor again. - * - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean commitUpdates(); - - /** - * Atomically commits all updates to the backing store, as well as the - * updates included in values. After completion, - * this method leaves the data in an inconsistent state and you should call - * {@link #requery} before reading data from the cursor again. - * - * @param values A map from row IDs to Maps associating column names with - * updated values. A null value indicates the field should be - removed. - * @return whether the operation succeeded. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - boolean commitUpdates(Map> values); - - /** - * Reverts all updates made to the cursor since the last call to - * commitUpdates. - * @hide - * @deprecated use the {@link ContentResolver} update methods instead of the Cursor - * update methods - */ - @Deprecated - void abortUpdates(); - - /** - * Deactivates the Cursor, making all calls on it fail until {@link #requery} is called. - * Inactive Cursors use fewer resources than active Cursors. - * Calling {@link #requery} will make the cursor active again. - */ - void deactivate(); - - /** - * Performs the query that created the cursor again, refreshing its - * contents. This may be done at any time, including after a call to {@link - * #deactivate}. - * - * @return true if the requery succeeded, false if not, in which case the - * cursor becomes invalid. - */ - boolean requery(); - - /** - * Closes the Cursor, releasing all of its resources and making it completely invalid. - * Unlike {@link #deactivate()} a call to {@link #requery()} will not make the Cursor valid - * again. - */ - void close(); - - /** - * return true if the cursor is closed - * @return true if the cursor is closed. - */ - boolean isClosed(); - - /** - * Register an observer that is called when changes happen to the content backing this cursor. - * Typically the data set won't change until {@link #requery()} is called. - * - * @param observer the object that gets notified when the content backing the cursor changes. - * @see #unregisterContentObserver(ContentObserver) - */ - void registerContentObserver(ContentObserver observer); - - /** - * Unregister an observer that has previously been registered with this - * cursor via {@link #registerContentObserver}. - * - * @param observer the object to unregister. - * @see #registerContentObserver(ContentObserver) - */ - void unregisterContentObserver(ContentObserver observer); - - /** - * Register an observer that is called when changes happen to the contents - * of the this cursors data set, for example, when the data set is changed via - * {@link #requery()}, {@link #deactivate()}, or {@link #close()}. - * - * @param observer the object that gets notified when the cursors data set changes. - * @see #unregisterDataSetObserver(DataSetObserver) - */ - void registerDataSetObserver(DataSetObserver observer); - - /** - * Unregister an observer that has previously been registered with this - * cursor via {@link #registerContentObserver}. - * - * @param observer the object to unregister. - * @see #registerDataSetObserver(DataSetObserver) - */ - void unregisterDataSetObserver(DataSetObserver observer); - - /** - * Register to watch a content URI for changes. This can be the URI of a specific data row (for - * example, "content://my_provider_type/23"), or a a generic URI for a content type. - * - * @param cr The content resolver from the caller's context. The listener attached to - * this resolver will be notified. - * @param uri The content URI to watch. - */ - void setNotificationUri(ContentResolver cr, Uri uri); - - /** - * onMove() will only be called across processes if this method returns true. - * @return whether all cursor movement should result in a call to onMove(). - */ - boolean getWantsAllOnMoveCalls(); - - /** - * Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band - * metadata to their users. One use of this is for reporting on the progress of network requests - * that are required to fetch data for the cursor. - * - *

These values may only change when requery is called. - * @return cursor-defined values, or Bundle.EMTPY if there are no values. Never null. - */ - Bundle getExtras(); - - /** - * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The - * structure of each bundle is entirely defined by the cursor. - * - *

One use of this is to tell a cursor that it should retry its network request after it - * reported an error. - * @param extras extra values, or Bundle.EMTPY. Never null. - * @return extra values, or Bundle.EMTPY. Never null. - */ - Bundle respond(Bundle extras); -} diff --git a/src/info/guardianproject/database/CursorJoiner.java b/src/info/guardianproject/database/CursorJoiner.java deleted file mode 100644 index 907d9dd5..00000000 --- a/src/info/guardianproject/database/CursorJoiner.java +++ /dev/null @@ -1,265 +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 info.guardianproject.database; - -import java.util.Iterator; - -/** - * Does a join on two cursors using the specified columns. The cursors must already - * be sorted on each of the specified columns in ascending order. This joiner only - * supports the case where the tuple of key column values is unique. - *

- * Typical usage: - * - *

- * CursorJoiner joiner = new CursorJoiner(cursorA, keyColumnsofA, cursorB, keyColumnsofB);
- * for (CursorJointer.Result joinerResult : joiner) {
- *     switch (joinerResult) {
- *         case LEFT:
- *             // handle case where a row in cursorA is unique
- *             break;
- *         case RIGHT:
- *             // handle case where a row in cursorB is unique
- *             break;
- *         case BOTH:
- *             // handle case where a row with the same key is in both cursors
- *             break;
- *     }
- * }
- * 
- */ -public final class CursorJoiner - implements Iterator, Iterable { - private Cursor mCursorLeft; - private Cursor mCursorRight; - private boolean mCompareResultIsValid; - private Result mCompareResult; - private int[] mColumnsLeft; - private int[] mColumnsRight; - private String[] mValues; - - /** - * The result of a call to next(). - */ - public enum Result { - /** The row currently pointed to by the left cursor is unique */ - RIGHT, - /** The row currently pointed to by the right cursor is unique */ - LEFT, - /** The rows pointed to by both cursors are the same */ - BOTH - } - - /** - * Initializes the CursorJoiner and resets the cursors to the first row. The left and right - * column name arrays must have the same number of columns. - * @param cursorLeft The left cursor to compare - * @param columnNamesLeft The column names to compare from the left cursor - * @param cursorRight The right cursor to compare - * @param columnNamesRight The column names to compare from the right cursor - */ - public CursorJoiner( - Cursor cursorLeft, String[] columnNamesLeft, - Cursor cursorRight, String[] columnNamesRight) { - if (columnNamesLeft.length != columnNamesRight.length) { - throw new IllegalArgumentException( - "you must have the same number of columns on the left and right, " - + columnNamesLeft.length + " != " + columnNamesRight.length); - } - - mCursorLeft = cursorLeft; - mCursorRight = cursorRight; - - mCursorLeft.moveToFirst(); - mCursorRight.moveToFirst(); - - mCompareResultIsValid = false; - - mColumnsLeft = buildColumnIndiciesArray(cursorLeft, columnNamesLeft); - mColumnsRight = buildColumnIndiciesArray(cursorRight, columnNamesRight); - - mValues = new String[mColumnsLeft.length * 2]; - } - - public Iterator iterator() { - return this; - } - - /** - * Lookup the indicies of the each column name and return them in an array. - * @param cursor the cursor that contains the columns - * @param columnNames the array of names to lookup - * @return an array of column indices - */ - private int[] buildColumnIndiciesArray(Cursor cursor, String[] columnNames) { - int[] columns = new int[columnNames.length]; - for (int i = 0; i < columnNames.length; i++) { - columns[i] = cursor.getColumnIndexOrThrow(columnNames[i]); - } - return columns; - } - - /** - * Returns whether or not there are more rows to compare using next(). - * @return true if there are more rows to compare - */ - public boolean hasNext() { - if (mCompareResultIsValid) { - switch (mCompareResult) { - case BOTH: - return !mCursorLeft.isLast() || !mCursorRight.isLast(); - - case LEFT: - return !mCursorLeft.isLast() || !mCursorRight.isAfterLast(); - - case RIGHT: - return !mCursorLeft.isAfterLast() || !mCursorRight.isLast(); - - default: - throw new IllegalStateException("bad value for mCompareResult, " - + mCompareResult); - } - } else { - return !mCursorLeft.isAfterLast() || !mCursorRight.isAfterLast(); - } - } - - /** - * Returns the comparison result of the next row from each cursor. If one cursor - * has no more rows but the other does then subsequent calls to this will indicate that - * the remaining rows are unique. - *

- * The caller must check that hasNext() returns true before calling this. - *

- * Once next() has been called the cursors specified in the result of the call to - * next() are guaranteed to point to the row that was indicated. Reading values - * from the cursor that was not indicated in the call to next() will result in - * undefined behavior. - * @return LEFT, if the row pointed to by the left cursor is unique, RIGHT - * if the row pointed to by the right cursor is unique, BOTH if the rows in both - * cursors are the same. - */ - public Result next() { - if (!hasNext()) { - throw new IllegalStateException("you must only call next() when hasNext() is true"); - } - incrementCursors(); - assert hasNext(); - - boolean hasLeft = !mCursorLeft.isAfterLast(); - boolean hasRight = !mCursorRight.isAfterLast(); - - if (hasLeft && hasRight) { - populateValues(mValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */); - populateValues(mValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */); - switch (compareStrings(mValues)) { - case -1: - mCompareResult = Result.LEFT; - break; - case 0: - mCompareResult = Result.BOTH; - break; - case 1: - mCompareResult = Result.RIGHT; - break; - } - } else if (hasLeft) { - mCompareResult = Result.LEFT; - } else { - assert hasRight; - mCompareResult = Result.RIGHT; - } - mCompareResultIsValid = true; - return mCompareResult; - } - - public void remove() { - throw new UnsupportedOperationException("not implemented"); - } - - /** - * Reads the strings from the cursor that are specifed in the columnIndicies - * array and saves them in values beginning at startingIndex, skipping a slot - * for each value. If columnIndicies has length 3 and startingIndex is 1, the - * values will be stored in slots 1, 3, and 5. - * @param values the String[] to populate - * @param cursor the cursor from which to read - * @param columnIndicies the indicies of the values to read from the cursor - * @param startingIndex the slot in which to start storing values, and must be either 0 or 1. - */ - private static void populateValues(String[] values, Cursor cursor, int[] columnIndicies, - int startingIndex) { - assert startingIndex == 0 || startingIndex == 1; - for (int i = 0; i < columnIndicies.length; i++) { - values[startingIndex + i*2] = cursor.getString(columnIndicies[i]); - } - } - - /** - * Increment the cursors past the rows indicated in the most recent call to next(). - * This will only have an affect once per call to next(). - */ - private void incrementCursors() { - if (mCompareResultIsValid) { - switch (mCompareResult) { - case LEFT: - mCursorLeft.moveToNext(); - break; - case RIGHT: - mCursorRight.moveToNext(); - break; - case BOTH: - mCursorLeft.moveToNext(); - mCursorRight.moveToNext(); - break; - } - mCompareResultIsValid = false; - } - } - - /** - * Compare the values. Values contains n pairs of strings. If all the pairs of strings match - * then returns 0. Otherwise returns the comparison result of the first non-matching pair - * of values, -1 if the first of the pair is less than the second of the pair or 1 if it - * is greater. - * @param values the n pairs of values to compare - * @return -1, 0, or 1 as described above. - */ - private static int compareStrings(String... values) { - if ((values.length % 2) != 0) { - throw new IllegalArgumentException("you must specify an even number of values"); - } - - for (int index = 0; index < values.length; index+=2) { - if (values[index] == null) { - if (values[index+1] == null) continue; - return -1; - } - - if (values[index+1] == null) { - return 1; - } - - int comp = values[index].compareTo(values[index+1]); - if (comp != 0) { - return comp < 0 ? -1 : 1; - } - } - - return 0; - } -} diff --git a/src/info/guardianproject/database/CursorToBulkCursorAdaptor.java b/src/info/guardianproject/database/CursorToBulkCursorAdaptor.java deleted file mode 100644 index b9a9712c..00000000 --- a/src/info/guardianproject/database/CursorToBulkCursorAdaptor.java +++ /dev/null @@ -1,232 +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 info.guardianproject.database; - -import info.guardianproject.database.*; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Config; -import android.util.Log; - -import java.util.Map; - - -/** - * Wraps a BulkCursor around an existing Cursor making it remotable. - * - * {@hide} - */ -public final class CursorToBulkCursorAdaptor extends BulkCursorNative - implements IBinder.DeathRecipient { - private static final String TAG = "Cursor"; - private final CrossProcessCursor mCursor; - private CursorWindow mWindow; - private final String mProviderName; - private final boolean mReadOnly; - private ContentObserverProxy mObserver; - - private static final class ContentObserverProxy extends ContentObserver - { - protected IContentObserver mRemote; - - public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) { - super(null); - mRemote = remoteObserver; - try { - remoteObserver.asBinder().linkToDeath(recipient, 0); - } catch (RemoteException e) { - // Do nothing, the far side is dead - } - } - - public boolean unlinkToDeath(DeathRecipient recipient) { - return mRemote.asBinder().unlinkToDeath(recipient, 0); - } - - @Override - public boolean deliverSelfNotifications() { - // The far side handles the self notifications. - return false; - } - - @Override - public void onChange(boolean selfChange) { - try { - mRemote.onChange(selfChange); - } catch (RemoteException ex) { - // Do nothing, the far side is dead - } - } - } - - public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName, - boolean allowWrite, CursorWindow window) { - try { - mCursor = (CrossProcessCursor) cursor; - if (mCursor instanceof AbstractWindowedCursor) { - AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; - if (windowedCursor.hasWindow()) { - if (Log.isLoggable(TAG, Log.VERBOSE) || Config.LOGV) { - Log.v(TAG, "Cross process cursor has a local window before setWindow in " - + providerName, new RuntimeException()); - } - } - windowedCursor.setWindow(window); - } else { - mWindow = window; - mCursor.fillWindow(0, window); - } - } catch (ClassCastException e) { - // TODO Implement this case. - throw new UnsupportedOperationException( - "Only CrossProcessCursor cursors are supported across process for now", e); - } - mProviderName = providerName; - mReadOnly = !allowWrite; - - createAndRegisterObserverProxy(observer); - } - - public void binderDied() { - mCursor.close(); - if (mWindow != null) { - mWindow.close(); - } - } - - public CursorWindow getWindow(int startPos) { - mCursor.moveToPosition(startPos); - - if (mWindow != null) { - if (startPos < mWindow.getStartPosition() || - startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mCursor.fillWindow(startPos, mWindow); - } - return mWindow; - } else { - return ((AbstractWindowedCursor)mCursor).getWindow(); - } - } - - public void onMove(int position) { - mCursor.onMove(mCursor.getPosition(), position); - } - - public int count() { - return mCursor.getCount(); - } - - public String[] getColumnNames() { - return mCursor.getColumnNames(); - } - - public void deactivate() { - maybeUnregisterObserverProxy(); - mCursor.deactivate(); - } - - public void close() { - maybeUnregisterObserverProxy(); - mCursor.close(); - } - - public int requery(IContentObserver observer, CursorWindow window) { - if (mWindow == null) { - ((AbstractWindowedCursor)mCursor).setWindow(window); - } - try { - if (!mCursor.requery()) { - return -1; - } - } catch (IllegalStateException e) { - IllegalStateException leakProgram = new IllegalStateException( - mProviderName + " Requery misuse db, mCursor isClosed:" + - mCursor.isClosed(), e); - throw leakProgram; - } - - if (mWindow != null) { - mCursor.fillWindow(0, window); - mWindow = window; - } - maybeUnregisterObserverProxy(); - createAndRegisterObserverProxy(observer); - return mCursor.getCount(); - } - - public boolean getWantsAllOnMoveCalls() { - return mCursor.getWantsAllOnMoveCalls(); - } - - /** - * Create a ContentObserver from the observer and register it as an observer on the - * underlying cursor. - * @param observer the IContentObserver that wants to monitor the cursor - * @throws IllegalStateException if an observer is already registered - */ - private void createAndRegisterObserverProxy(IContentObserver observer) { - if (mObserver != null) { - throw new IllegalStateException("an observer is already registered"); - } - mObserver = new ContentObserverProxy(observer, this); - mCursor.registerContentObserver(mObserver); - } - - /** Unregister the observer if it is already registered. */ - private void maybeUnregisterObserverProxy() { - if (mObserver != null) { - mCursor.unregisterContentObserver(mObserver); - mObserver.unlinkToDeath(this); - mObserver = null; - } - } - - public boolean updateRows(Map> values) { - if (mReadOnly) { - Log.w("ContentProvider", "Permission Denial: modifying " - + mProviderName - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return false; - } - return mCursor.commitUpdates(values); - } - - public boolean deleteRow(int position) { - if (mReadOnly) { - Log.w("ContentProvider", "Permission Denial: modifying " - + mProviderName - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return false; - } - if (mCursor.moveToPosition(position) == false) { - return false; - } - return mCursor.deleteRow(); - } - - public Bundle getExtras() { - return mCursor.getExtras(); - } - - public Bundle respond(Bundle extras) { - return mCursor.respond(extras); - } -} diff --git a/src/info/guardianproject/database/CursorWrapper.java b/src/info/guardianproject/database/CursorWrapper.java deleted file mode 100644 index 2f1eaa9d..00000000 --- a/src/info/guardianproject/database/CursorWrapper.java +++ /dev/null @@ -1,340 +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 info.guardianproject.database; - -import android.content.ContentResolver; -import android.database.CharArrayBuffer; -import android.net.Uri; -import android.os.Bundle; - -import java.util.Map; - -/** - * Wrapper class for Cursor that delegates all calls to the actual cursor object - */ - -public class CursorWrapper implements Cursor { - - public CursorWrapper(Cursor cursor) { - mCursor = cursor; - } - - /** - * @hide - * @deprecated - */ - public void abortUpdates() { - mCursor.abortUpdates(); - } - - public void close() { - mCursor.close(); - } - - public boolean isClosed() { - return mCursor.isClosed(); - } - - /** - * @hide - * @deprecated - */ - public boolean commitUpdates() { - return mCursor.commitUpdates(); - } - - /** - * @hide - * @deprecated - */ - public boolean commitUpdates( - Map> values) { - return mCursor.commitUpdates(values); - } - - public int getCount() { - return mCursor.getCount(); - } - - public void deactivate() { - mCursor.deactivate(); - } - - /** - * @hide - * @deprecated - */ - public boolean deleteRow() { - return mCursor.deleteRow(); - } - - public boolean moveToFirst() { - return mCursor.moveToFirst(); - } - - public int getColumnCount() { - return mCursor.getColumnCount(); - } - - public int getColumnIndex(String columnName) { - return mCursor.getColumnIndex(columnName); - } - - public int getColumnIndexOrThrow(String columnName) - throws IllegalArgumentException { - return mCursor.getColumnIndexOrThrow(columnName); - } - - public String getColumnName(int columnIndex) { - return mCursor.getColumnName(columnIndex); - } - - public String[] getColumnNames() { - return mCursor.getColumnNames(); - } - - public double getDouble(int columnIndex) { - return mCursor.getDouble(columnIndex); - } - - public Bundle getExtras() { - return mCursor.getExtras(); - } - - public float getFloat(int columnIndex) { - return mCursor.getFloat(columnIndex); - } - - public int getInt(int columnIndex) { - return mCursor.getInt(columnIndex); - } - - public long getLong(int columnIndex) { - return mCursor.getLong(columnIndex); - } - - public short getShort(int columnIndex) { - return mCursor.getShort(columnIndex); - } - - public String getString(int columnIndex) { - return mCursor.getString(columnIndex); - } - - - public byte[] getBlob(int columnIndex) { - return mCursor.getBlob(columnIndex); - } - - public boolean getWantsAllOnMoveCalls() { - return mCursor.getWantsAllOnMoveCalls(); - } - - /** - * @hide - * @deprecated - */ - public boolean hasUpdates() { - return mCursor.hasUpdates(); - } - - public boolean isAfterLast() { - return mCursor.isAfterLast(); - } - - public boolean isBeforeFirst() { - return mCursor.isBeforeFirst(); - } - - public boolean isFirst() { - return mCursor.isFirst(); - } - - public boolean isLast() { - return mCursor.isLast(); - } - - public boolean isNull(int columnIndex) { - return mCursor.isNull(columnIndex); - } - - public boolean moveToLast() { - return mCursor.moveToLast(); - } - - public boolean move(int offset) { - return mCursor.move(offset); - } - - public boolean moveToPosition(int position) { - return mCursor.moveToPosition(position); - } - - public boolean moveToNext() { - return mCursor.moveToNext(); - } - - public int getPosition() { - return mCursor.getPosition(); - } - - public boolean moveToPrevious() { - return mCursor.moveToPrevious(); - } - - public void registerContentObserver(ContentObserver observer) { - mCursor.registerContentObserver(observer); - } - - public void registerDataSetObserver(DataSetObserver observer) { - mCursor.registerDataSetObserver(observer); - } - - public boolean requery() { - return mCursor.requery(); - } - - public Bundle respond(Bundle extras) { - return mCursor.respond(extras); - } - - public void setNotificationUri(ContentResolver cr, Uri uri) { - mCursor.setNotificationUri(cr, uri); - } - - /** - * @hide - * @deprecated - */ - public boolean supportsUpdates() { - return mCursor.supportsUpdates(); - } - - public void unregisterContentObserver(ContentObserver observer) { - mCursor.unregisterContentObserver(observer); - } - - public void unregisterDataSetObserver(DataSetObserver observer) { - mCursor.unregisterDataSetObserver(observer); - } - - /** - * @hide - * @deprecated - */ - public boolean updateDouble(int columnIndex, double value) { - return mCursor.updateDouble(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateFloat(int columnIndex, float value) { - return mCursor.updateFloat(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateInt(int columnIndex, int value) { - return mCursor.updateInt(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateLong(int columnIndex, long value) { - return mCursor.updateLong(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateShort(int columnIndex, short value) { - return mCursor.updateShort(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateString(int columnIndex, String value) { - return mCursor.updateString(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateBlob(int columnIndex, byte[] value) { - return mCursor.updateBlob(columnIndex, value); - } - - /** - * @hide - * @deprecated - */ - public boolean updateToNull(int columnIndex) { - return mCursor.updateToNull(columnIndex); - } - - private Cursor mCursor; - - @Override - public void copyStringToBuffer(int columnIndex, - android.database.CharArrayBuffer buffer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - - - -} - diff --git a/src/info/guardianproject/database/DataSetObservable.java b/src/info/guardianproject/database/DataSetObservable.java deleted file mode 100644 index c025735d..00000000 --- a/src/info/guardianproject/database/DataSetObservable.java +++ /dev/null @@ -1,47 +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 info.guardianproject.database; - -/** - * A specialization of Observable for DataSetObserver that provides methods for - * invoking the various callback methods of DataSetObserver. - */ -public class DataSetObservable extends Observable { - /** - * Invokes onChanged on each observer. Called when the data set being observed has - * changed, and which when read contains the new state of the data. - */ - public void notifyChanged() { - synchronized(mObservers) { - for (DataSetObserver observer : mObservers) { - observer.onChanged(); - } - } - } - - /** - * Invokes onInvalidated on each observer. Called when the data set being monitored - * has changed such that it is no longer valid. - */ - public void notifyInvalidated() { - synchronized (mObservers) { - for (DataSetObserver observer : mObservers) { - observer.onInvalidated(); - } - } - } -} diff --git a/src/info/guardianproject/database/DataSetObserver.java b/src/info/guardianproject/database/DataSetObserver.java deleted file mode 100644 index 53204cfe..00000000 --- a/src/info/guardianproject/database/DataSetObserver.java +++ /dev/null @@ -1,41 +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 info.guardianproject.database; - -/** - * Receives call backs when a data set has been changed, or made invalid. The typically data sets - * that are observed are {@link Cursor}s or {@link android.widget.Adapter}s. - * DataSetObserver must be implemented by objects which are added to a DataSetObservable. - */ -public abstract class DataSetObserver { - /** - * This method is called when the entire data set has changed, - * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}. - */ - public void onChanged() { - // Do nothing - } - - /** - * This method is called when the entire data becomes invalid, - * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a - * {@link Cursor}. - */ - public void onInvalidated() { - // Do nothing - } -} diff --git a/src/info/guardianproject/database/MergeCursor.java b/src/info/guardianproject/database/MergeCursor.java deleted file mode 100644 index 39fe45d4..00000000 --- a/src/info/guardianproject/database/MergeCursor.java +++ /dev/null @@ -1,302 +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 info.guardianproject.database; - -import android.database.CharArrayBuffer; - - - -/** - * A convience class that lets you present an array of Cursors as a single linear Cursor. - * The schema of the cursors presented is entirely up to the creator of the MergeCursor, and - * may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the - * value for the row that the MergeCursor is currently pointing at. - */ -public class MergeCursor extends AbstractCursor -{ - private DataSetObserver mObserver = new DataSetObserver() { - - @Override - public void onChanged() { - // Reset our position so the optimizations in move-related code - // don't screw us over - mPos = -1; - } - - @Override - public void onInvalidated() { - mPos = -1; - } - }; - - public MergeCursor(Cursor[] cursors) - { - mCursors = cursors; - mCursor = cursors[0]; - - for (int i = 0; i < mCursors.length; i++) { - if (mCursors[i] == null) continue; - - mCursors[i].registerDataSetObserver(mObserver); - } - } - - @Override - public int getCount() - { - int count = 0; - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - count += mCursors[i].getCount(); - } - } - return count; - } - - @Override - public boolean onMove(int oldPosition, int newPosition) - { - /* Find the right cursor */ - mCursor = null; - int cursorStartPos = 0; - int length = mCursors.length; - for (int i = 0 ; i < length; i++) { - if (mCursors[i] == null) { - continue; - } - - if (newPosition < (cursorStartPos + mCursors[i].getCount())) { - mCursor = mCursors[i]; - break; - } - - cursorStartPos += mCursors[i].getCount(); - } - - /* Move it to the right position */ - if (mCursor != null) { - boolean ret = mCursor.moveToPosition(newPosition - cursorStartPos); - return ret; - } - return false; - } - - /** - * @hide - * @deprecated - */ - @Override - public boolean deleteRow() - { - return mCursor.deleteRow(); - } - - /** - * @hide - * @deprecated - */ - @Override - public boolean commitUpdates() { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].commitUpdates(); - } - } - onChange(true); - return true; - } - - @Override - public String getString(int column) - { - return mCursor.getString(column); - } - - @Override - public short getShort(int column) - { - return mCursor.getShort(column); - } - - @Override - public int getInt(int column) - { - return mCursor.getInt(column); - } - - @Override - public long getLong(int column) - { - return mCursor.getLong(column); - } - - @Override - public float getFloat(int column) - { - return mCursor.getFloat(column); - } - - @Override - public double getDouble(int column) - { - return mCursor.getDouble(column); - } - - @Override - public boolean isNull(int column) - { - return mCursor.isNull(column); - } - - @Override - public byte[] getBlob(int column) - { - return mCursor.getBlob(column); - } - - @Override - public String[] getColumnNames() - { - if (mCursor != null) { - return mCursor.getColumnNames(); - } else { - return new String[0]; - } - } - - @Override - public void deactivate() - { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].deactivate(); - } - } - super.deactivate(); - } - - @Override - public void close() { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] == null) continue; - mCursors[i].close(); - } - super.close(); - } - - @Override - public void registerContentObserver(ContentObserver observer) { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].registerContentObserver(observer); - } - } - } - @Override - public void unregisterContentObserver(ContentObserver observer) { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].unregisterContentObserver(observer); - } - } - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].registerDataSetObserver(observer); - } - } - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] != null) { - mCursors[i].unregisterDataSetObserver(observer); - } - } - } - - @Override - public boolean requery() - { - int length = mCursors.length; - for (int i = 0 ; i < length ; i++) { - if (mCursors[i] == null) { - continue; - } - - if (mCursors[i].requery() == false) { - return false; - } - } - - return true; - } - - private Cursor mCursor; // updated in onMove - private Cursor[] mCursors; - - - @Override - public void registerContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void registerDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterContentObserver( - android.database.ContentObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void unregisterDataSetObserver( - android.database.DataSetObserver observer) { - // TODO Auto-generated method stub - - } - - @Override - public void copyStringToBuffer(int columnIndex, - android.database.CharArrayBuffer buffer) { - // TODO Auto-generated method stub - - } - - - -} diff --git a/src/info/guardianproject/database/Observable.java b/src/info/guardianproject/database/Observable.java deleted file mode 100644 index 0a11db97..00000000 --- a/src/info/guardianproject/database/Observable.java +++ /dev/null @@ -1,78 +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 info.guardianproject.database; - -import java.util.ArrayList; - -/** - * Provides methods for (un)registering arbitrary observers in an ArrayList. - */ -public abstract class Observable { - /** - * The list of observers. An observer can be in the list at most - * once and will never be null. - */ - protected final ArrayList mObservers = new ArrayList(); - - /** - * Adds an observer to the list. The observer cannot be null and it must not already - * be registered. - * @param observer the observer to register - * @throws IllegalArgumentException the observer is null - * @throws IllegalStateException the observer is already registered - */ - public void registerObserver(T observer) { - if (observer == null) { - throw new IllegalArgumentException("The observer is null."); - } - synchronized(mObservers) { - if (mObservers.contains(observer)) { - throw new IllegalStateException("Observer " + observer + " is already registered."); - } - mObservers.add(observer); - } - } - - /** - * Removes a previously registered observer. The observer must not be null and it - * must already have been registered. - * @param observer the observer to unregister - * @throws IllegalArgumentException the observer is null - * @throws IllegalStateException the observer is not yet registered - */ - public void unregisterObserver(T observer) { - if (observer == null) { - throw new IllegalArgumentException("The observer is null."); - } - synchronized(mObservers) { - int index = mObservers.indexOf(observer); - if (index == -1) { - throw new IllegalStateException("Observer " + observer + " was not registered."); - } - mObservers.remove(index); - } - } - - /** - * Remove all registered observer - */ - public void unregisterAll() { - synchronized(mObservers) { - mObservers.clear(); - } - } -} diff --git a/src/info/guardianproject/database/SQLException.java b/src/info/guardianproject/database/SQLException.java deleted file mode 100644 index 4de3d4ff..00000000 --- a/src/info/guardianproject/database/SQLException.java +++ /dev/null @@ -1,30 +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 info.guardianproject.database; - -/** - * An exception that indicates there was an error with SQL parsing or execution. - */ -public class SQLException extends RuntimeException -{ - public SQLException() {} - - public SQLException(String error) - { - super(error); - } -} diff --git a/src/info/guardianproject/database/sqlcipher/SQLiteAbortException.java b/src/info/guardianproject/database/sqlcipher/SQLiteAbortException.java deleted file mode 100644 index b33558c9..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -/** - * 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/info/guardianproject/database/sqlcipher/SQLiteConstraintException.java b/src/info/guardianproject/database/sqlcipher/SQLiteConstraintException.java deleted file mode 100644 index 39d68b15..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -/** - * 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/info/guardianproject/database/sqlcipher/SQLiteDatabase.java b/src/info/guardianproject/database/sqlcipher/SQLiteDatabase.java deleted file mode 100644 index 1559149c..00000000 --- a/src/info/guardianproject/database/sqlcipher/SQLiteDatabase.java +++ /dev/null @@ -1,2346 +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 info.guardianproject.database.sqlcipher; - -import info.guardianproject.database.DatabaseUtils; -import info.guardianproject.database.SQLException; -import info.guardianproject.database.sqlcipher.SQLiteDebug.DbStats; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -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 android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.os.Debug; -import android.os.Environment; -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; - - public static void loadLibs (Context context) - { - - System.loadLibrary("stlport_shared"); - - System.loadLibrary("sqlcipher"); - - String soFileName = "libsqlcipher_android"; - - File baseFile = context.getFilesDir(); - - File soFile = new File(baseFile,soFileName + ".so"); - - boolean soLoaded = soFile.exists(); - - if (!soLoaded) - { - File libFile = new File(baseFile.getParent(),"lib"); - File soSrcFile = new File(libFile,soFileName + "-" + android.os.Build.VERSION.SDK_INT + ".so"); - - try - { - InputStream in = new FileInputStream(soSrcFile); - - soFile.getParentFile().mkdirs(); - OutputStream out = new FileOutputStream(soFile); - - // Transfer bytes from in to out - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - in.close(); - out.close(); - } - catch (IOException ioe) - { - - } - - } - - System.load(soFile.getAbsolutePath()); - - System.loadLibrary("database_sqlcipher"); - } - - /** - * 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(SQLiteCursor.TAG, - "query (" + duration + " ms): " + driver.toString() + ", args are " - + (selectionArgs != null - ? TextUtils.join(",", selectionArgs) - : "") + ", count is " + count); - } - } - } - return 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; - } - - /** - * 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/info/guardianproject/database/sqlcipher/SQLiteDatabaseCorruptException.java b/src/info/guardianproject/database/sqlcipher/SQLiteDatabaseCorruptException.java deleted file mode 100644 index e40a1d06..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -/** - * 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/info/guardianproject/database/sqlcipher/SQLiteDiskIOException.java b/src/info/guardianproject/database/sqlcipher/SQLiteDiskIOException.java deleted file mode 100644 index a20cf1db..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -/** - * 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/info/guardianproject/database/sqlcipher/SQLiteDoneException.java b/src/info/guardianproject/database/sqlcipher/SQLiteDoneException.java deleted file mode 100644 index e4a84a89..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -/** - * 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/info/guardianproject/database/sqlcipher/SQLiteMisuseException.java b/src/info/guardianproject/database/sqlcipher/SQLiteMisuseException.java deleted file mode 100644 index 92a5e657..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 info.guardianproject.database.sqlcipher; - -public class SQLiteMisuseException extends SQLiteException { - public SQLiteMisuseException() {} - - public SQLiteMisuseException(String error) { - super(error); - } -} diff --git a/src/info/guardianproject/database/sqlcipher/package.html b/src/info/guardianproject/database/sqlcipher/package.html deleted file mode 100644 index ff0f9f58..00000000 --- a/src/info/guardianproject/database/sqlcipher/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 - -