From 1e46a0c78c64cd46d7f7d7f070efc15ea5189124 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 2 Jan 2021 04:57:20 +0330 Subject: [PATCH] android support! thanks to TachiWeb devs. --- AndroidCompat/Config/build.gradle | 4 + .../nulldev/ts/config/ConfigKodeinModule.kt | 12 + .../xyz/nulldev/ts/config/ConfigManager.kt | 74 + .../xyz/nulldev/ts/config/ConfigModule.kt | 8 + .../xyz/nulldev/ts/config/ServerConfig.kt | 35 + .../ts/config/util/ConfigExtensions.kt | 6 + ...xyz.nulldev.ts.api.v2.java.model.ServerAPI | 1 + .../Config/src/main/resources/al.png | Bin 0 -> 2444 bytes .../Config/src/main/resources/kitsu.png | Bin 0 -> 7091 bytes .../Config/src/main/resources/mal.png | Bin 0 -> 2267 bytes .../Config/src/main/resources/openapi.json | 4291 ++++++++++ .../src/main/resources/pref-schema.json | 22 + .../Config/src/main/resources/reference.conf | 87 + AndroidCompat/build.gradle.kts | 72 + AndroidCompat/lib/.gitignore | 1 + .../main/java/android/annotation/AnimRes.java | 35 + .../java/android/annotation/AnimatorRes.java | 35 + .../main/java/android/annotation/AnyRes.java | 37 + .../java/android/annotation/AppIdInt.java | 34 + .../java/android/annotation/ArrayRes.java | 35 + .../main/java/android/annotation/AttrRes.java | 35 + .../java/android/annotation/BinderThread.java | 40 + .../main/java/android/annotation/BoolRes.java | 35 + .../java/android/annotation/CallSuper.java | 38 + .../java/android/annotation/CheckResult.java | 58 + .../java/android/annotation/ColorInt.java | 39 + .../java/android/annotation/ColorRes.java | 35 + .../java/android/annotation/DimenRes.java | 35 + .../java/android/annotation/DrawableRes.java | 35 + .../java/android/annotation/FloatRange.java | 52 + .../java/android/annotation/FractionRes.java | 35 + .../main/java/android/annotation/IdRes.java | 35 + .../main/java/android/annotation/IntDef.java | 57 + .../java/android/annotation/IntRange.java | 44 + .../java/android/annotation/IntegerRes.java | 35 + .../android/annotation/InterpolatorRes.java | 35 + .../java/android/annotation/LayoutRes.java | 35 + .../java/android/annotation/MainThread.java | 40 + .../main/java/android/annotation/MenuRes.java | 35 + .../main/java/android/annotation/NonNull.java | 34 + .../java/android/annotation/Nullable.java | 41 + .../java/android/annotation/PluralsRes.java | 35 + .../main/java/android/annotation/RawRes.java | 35 + .../annotation/RequiresPermission.java | 131 + .../java/android/annotation/SdkConstant.java | 36 + .../main/java/android/annotation/Size.java | 49 + .../java/android/annotation/StringDef.java | 51 + .../java/android/annotation/StringRes.java | 35 + .../java/android/annotation/StyleRes.java | 35 + .../java/android/annotation/StyleableRes.java | 35 + .../java/android/annotation/SuppressLint.java | 33 + .../java/android/annotation/SystemApi.java | 39 + .../java/android/annotation/TargetApi.java | 33 + .../main/java/android/annotation/TestApi.java | 37 + .../android/annotation/TransitionRes.java | 35 + .../java/android/annotation/UiThread.java | 40 + .../java/android/annotation/UserIdInt.java | 34 + .../main/java/android/annotation/Widget.java | 37 + .../java/android/annotation/WorkerThread.java | 40 + .../main/java/android/annotation/XmlRes.java | 35 + .../main/java/android/app/Application.java | 284 + .../android/app/PackageDeleteObserver.java | 24 + .../android/app/PackageInstallObserver.java | 41 + .../src/main/java/android/app/Service.java | 722 ++ .../persistence/db/SimpleSQLiteQuery.java | 109 + .../persistence/db/SupportSQLiteDatabase.java | 604 ++ .../db/SupportSQLiteOpenHelper.java | 389 + .../persistence/db/SupportSQLiteProgram.java | 74 + .../persistence/db/SupportSQLiteQuery.java | 45 + .../db/SupportSQLiteQueryBuilder.java | 190 + .../db/SupportSQLiteStatement.java | 69 + .../db/framework/FrameworkSQLiteDatabase.java | 307 + .../framework/FrameworkSQLiteOpenHelper.java | 159 + .../FrameworkSQLiteOpenHelperFactory.java | 31 + .../db/framework/FrameworkSQLiteProgram.java | 65 + .../framework/FrameworkSQLiteStatement.java | 61 + .../android/content/ComponentCallbacks.java | 64 + .../android/content/ComponentCallbacks2.java | 168 + .../java/android/content/ComponentName.java | 362 + .../java/android/content/ContentValues.java | 473 + .../main/java/android/content/Context.java | 4047 +++++++++ .../java/android/content/ContextWrapper.java | 720 ++ .../src/main/java/android/content/Intent.java | 7625 +++++++++++++++++ .../android/content/SharedPreferences.java | 370 + .../android/content/pm/ApplicationInfo.java | 999 +++ .../java/android/content/pm/FeatureInfo.java | 132 + .../android/content/pm/InstantAppInfo.java | 127 + .../pm/IntentFilterVerificationInfo.java | 223 + .../main/java/android/content/pm/KeySet.java | 95 + .../java/android/content/pm/PackageInfo.java | 353 + .../android/content/pm/PackageItemInfo.java | 346 + .../android/content/pm/PackageManager.java | 5318 ++++++++++++ .../android/content/pm/PackageParser.java | 15 + .../java/android/content/pm/Signature.java | 178 + .../android/content/pm/VersionedPackage.java | 87 + .../content/res/CompatibilityInfo.java | 296 + .../java/android/database/AbstractCursor.java | 414 + .../database/AbstractWindowedCursor.java | 189 + .../android/database/CharArrayBuffer.java | 31 + .../android/database/ContentObservable.java | 91 + .../java/android/database/CursorWindow.java | 493 ++ .../android/database/DataSetObservable.java | 54 + .../java/android/database/DatabaseUtils.java | 1288 +++ .../database/DefaultDatabaseErrorHandler.java | 101 + .../java/android/database/Observable.java | 83 + .../java/android/database/SQLException.java | 29 + .../DatabaseObjectNotClosedException.java | 31 + .../database/sqlite/SQLiteAbortException.java | 30 + .../sqlite/SQLiteAccessPermException.java | 29 + ...eBindOrColumnIndexOutOfRangeException.java | 28 + .../sqlite/SQLiteBlobTooBigException.java | 25 + .../SQLiteCantOpenDatabaseException.java | 25 + .../database/sqlite/SQLiteClosable.java | 108 + .../sqlite/SQLiteConstraintException.java | 28 + .../android/database/sqlite/SQLiteCursor.java | 271 + .../database/sqlite/SQLiteCursorDriver.java | 56 + .../database/sqlite/SQLiteDatabase.java | 2208 +++++ .../sqlite/SQLiteDatabaseConfiguration.java | 156 + .../SQLiteDatabaseCorruptException.java | 28 + .../sqlite/SQLiteDatabaseLockedException.java | 33 + .../SQLiteDatatypeMismatchException.java | 25 + .../android/database/sqlite/SQLiteDebug.java | 170 + .../sqlite/SQLiteDirectCursorDriver.java | 83 + .../sqlite/SQLiteDiskIOException.java | 29 + .../database/sqlite/SQLiteDoneException.java | 31 + .../database/sqlite/SQLiteException.java | 35 + .../database/sqlite/SQLiteFullException.java | 28 + .../android/database/sqlite/SQLiteGlobal.java | 111 + .../sqlite/SQLiteMisuseException.java | 37 + .../database/sqlite/SQLiteOpenHelper.java | 378 + .../sqlite/SQLiteOutOfMemoryException.java | 25 + .../database/sqlite/SQLiteProgram.java | 235 + .../android/database/sqlite/SQLiteQuery.java | 78 + .../database/sqlite/SQLiteQueryBuilder.java | 648 ++ .../SQLiteReadOnlyDatabaseException.java | 25 + .../database/sqlite/SQLiteStatement.java | 167 + .../database/sqlite/SQLiteStatementInfo.java | 39 + .../sqlite/SQLiteTableLockedException.java | 25 + .../sqlite/SQLiteTransactionListener.java | 37 + .../database/sqlite/SqliteWrapper.java | 108 + .../java/android/database/sqlite/package.html | 49 + .../java/android/net/ConnectivityManager.java | 2924 +++++++ .../main/java/android/net/NetworkInfo.java | 496 ++ .../main/java/android/net/ParseException.java | 26 + .../src/main/java/android/net/Uri.java | 1992 +++++ .../src/main/java/android/net/WebAddress.java | 169 + .../src/main/java/android/os/BaseBundle.java | 1482 ++++ .../src/main/java/android/os/Build.java | 102 + .../src/main/java/android/os/Bundle.java | 1184 +++ .../src/main/java/android/os/Environment.java | 96 + .../src/main/java/android/os/Parcelable.java | 148 + .../java/android/os/PersistableBundle.java | 293 + .../main/java/android/os/PowerManager.java | 99 + .../src/main/java/android/os/Process.java | 916 ++ .../src/main/java/android/os/SimpleClock.java | 55 + .../src/main/java/android/os/SystemClock.java | 296 + .../java/android/os/SystemProperties.java | 126 + .../src/main/java/android/os/UserHandle.java | 418 + .../main/java/android/os/ZygoteProcess.java | 407 + .../android/preference/PreferenceManager.kt | 16 + .../android/support/annotation/AnimRes.java | 33 + .../support/annotation/AnimatorRes.java | 33 + .../android/support/annotation/AnyRes.java | 35 + .../android/support/annotation/AnyThread.java | 44 + .../android/support/annotation/ArrayRes.java | 33 + .../android/support/annotation/AttrRes.java | 33 + .../support/annotation/BinderThread.java | 40 + .../android/support/annotation/BoolRes.java | 33 + .../android/support/annotation/CallSuper.java | 38 + .../support/annotation/CheckResult.java | 58 + .../android/support/annotation/ColorInt.java | 37 + .../android/support/annotation/ColorRes.java | 33 + .../android/support/annotation/DimenRes.java | 33 + .../android/support/annotation/Dimension.java | 45 + .../support/annotation/DrawableRes.java | 33 + .../support/annotation/FloatRange.java | 50 + .../support/annotation/FractionRes.java | 33 + .../android/support/annotation/IdRes.java | 33 + .../android/support/annotation/IntDef.java | 58 + .../android/support/annotation/IntRange.java | 42 + .../support/annotation/IntegerRes.java | 33 + .../support/annotation/InterpolatorRes.java | 33 + .../java/android/support/annotation/Keep.java | 41 + .../android/support/annotation/LayoutRes.java | 33 + .../support/annotation/MainThread.java | 40 + .../android/support/annotation/MenuRes.java | 33 + .../android/support/annotation/NonNull.java | 34 + .../android/support/annotation/Nullable.java | 41 + .../support/annotation/PluralsRes.java | 33 + .../java/android/support/annotation/Px.java | 34 + .../android/support/annotation/RawRes.java | 33 + .../support/annotation/RequiresApi.java | 45 + .../annotation/RequiresPermission.java | 129 + .../java/android/support/annotation/Size.java | 47 + .../android/support/annotation/StringDef.java | 49 + .../android/support/annotation/StringRes.java | 33 + .../android/support/annotation/StyleRes.java | 33 + .../support/annotation/StyleableRes.java | 33 + .../support/annotation/TransitionRes.java | 33 + .../android/support/annotation/UiThread.java | 41 + .../support/annotation/VisibleForTesting.java | 28 + .../support/annotation/WorkerThread.java | 40 + .../android/support/annotation/XmlRes.java | 33 + .../support/annotation/package-info.java | 4 + .../android/support/multidex/MultiDex.java | 16 + .../support/v4/content/ContextCompat.java | 291 + .../support/v4/os/EnvironmentCompat.java | 53 + .../v7/preference/PreferenceDataStore.java | 193 + .../v7/preference/PreferenceScreen.java | 4 + .../src/main/java/android/text/Html.java | 110 + .../src/main/java/android/text/TextUtils.java | 1044 +++ .../java/android/text/format/Formatter.java | 31 + .../src/main/java/android/util/ArrayMap.java | 854 ++ .../src/main/java/android/util/Base64.java | 676 ++ .../java/android/util/ContainerHelpers.java | 51 + .../src/main/java/android/util/Log.java | 139 + .../java/android/util/MapCollections.java | 482 ++ .../java/android/view/DisplayAdjustments.java | 67 + .../main/java/android/webkit/MimeTypeMap.java | 168 + .../src/main/java/android/webkit/URLUtil.java | 364 + .../com/android/internal/util/ArrayUtils.java | 471 + .../internal/util/FastXmlSerializer.java | 370 + .../android/internal/util/Preconditions.java | 451 + .../com/android/internal/util/XmlUtils.java | 1609 ++++ .../main/java/com/f2prateek/package-info.java | 2 + .../rx/preferences/BooleanAdapter.java | 34 + .../f2prateek/rx/preferences/EnumAdapter.java | 40 + .../rx/preferences/FloatAdapter.java | 34 + .../rx/preferences/IntegerAdapter.java | 34 + .../f2prateek/rx/preferences/LongAdapter.java | 34 + .../rx/preferences/Preconditions.java | 30 + .../f2prateek/rx/preferences/Preference.java | 127 + .../rx/preferences/RxSharedPreferences.java | 178 + .../rx/preferences/StringAdapter.java | 17 + .../rx/preferences/StringSetAdapter.java | 22 + .../reactivenetwork/library/Connectivity.kt | 7 + .../library/ReactiveNetwork.kt | 14 + .../java/com/squareup/duktape/Duktape.java | 98 + .../squareup/duktape/DuktapeException.java | 41 + .../dalvik/system/BaseDexClassLoader.java | 144 + .../main/java/dalvik/system/CloseGuard.java | 275 + .../java/dalvik/system/PathClassLoader.java | 64 + .../sqlite/RequerySQLiteOpenHelperFactory.kt | 8 + .../experimental/android/HandlerContext.kt | 5 + .../src/main/java/libcore/net/MimeUtils.java | 448 + .../src/main/java/libcore/net/UriCodec.java | 205 + .../main/java/libcore/util/EmptyArray.java | 34 + .../src/main/java/org/json/JSON.java | 116 + .../src/main/java/org/json/JSONArray.java | 629 ++ .../src/main/java/org/json/JSONException.java | 58 + .../src/main/java/org/json/JSONObject.java | 854 ++ .../src/main/java/org/json/JSONStringer.java | 433 + .../src/main/java/org/json/JSONTokener.java | 613 ++ .../src/main/java/org/json/package-info.java | 4 + .../AndroidSchedulers.kt | 16 + .../nulldev/androidcompat/AndroidCompat.kt | 17 + .../androidcompat/AndroidCompatInitializer.kt | 30 + .../androidcompat/AndroidCompatModule.kt | 39 + .../androidimpl/CustomContext.java | 738 ++ .../androidimpl/FakePackageManager.java | 849 ++ .../androidcompat/bytecode/ModApplier.kt | 22 + .../config/ApplicationInfoConfigModule.kt | 18 + .../androidcompat/config/FilesConfigModule.kt | 33 + .../config/SystemConfigModule.kt | 21 + .../androidcompat/db/ScrollableResultSet.kt | 841 ++ .../androidcompat/info/ApplicationInfoImpl.kt | 22 + .../nulldev/androidcompat/io/AndroidFiles.kt | 38 + .../io/sharedprefs/JsonSharedPreferences.java | 385 + .../androidcompat/pm/InstalledPackage.kt | 93 + .../androidcompat/pm/PackageController.kt | 87 + .../nulldev/androidcompat/pm/PackageUtil.kt | 27 + .../androidcompat/res/BuildConfigCompat.java | 27 + .../androidcompat/res/DrawableResource.kt | 7 + .../nulldev/androidcompat/res/RCompat.java | 78 + .../xyz/nulldev/androidcompat/res/Resource.kt | 27 + .../androidcompat/res/StringResource.kt | 26 + .../androidcompat/service/ServiceSupport.kt | 68 + .../androidcompat/util/KodeinGlobalHelper.kt | 67 + .../androidcompat/util/UriExtensions.kt | 28 + README.md | 4 +- build.gradle.kts | 80 + scripts/getAndroid.sh | 73 + server/build.gradle.kts | 15 +- .../main/kotlin/eu/kanade/tachiyomi/App.kt | 32 + .../kotlin/eu/kanade/tachiyomi/AppModule.kt | 66 + .../extension/api/ExtensionGithubService.kt | 4 +- .../kanade/tachiyomi/network/NetworkHelper.kt | 3 +- .../tachiyomi/source/online/HttpSource.kt | 3 +- .../main/kotlin/ir/armor/tachidesk/Main.kt | 29 + .../kotlin/ir/armor/tachidesk/util/APK.kt | 7 +- settings.gradle.kts | 5 +- 291 files changed, 68699 insertions(+), 16 deletions(-) create mode 100644 AndroidCompat/Config/build.gradle create mode 100644 AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt create mode 100644 AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt create mode 100644 AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt create mode 100644 AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ServerConfig.kt create mode 100644 AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt create mode 100644 AndroidCompat/Config/src/main/resources/META-INF/services/xyz.nulldev.ts.api.v2.java.model.ServerAPI create mode 100644 AndroidCompat/Config/src/main/resources/al.png create mode 100644 AndroidCompat/Config/src/main/resources/kitsu.png create mode 100644 AndroidCompat/Config/src/main/resources/mal.png create mode 100644 AndroidCompat/Config/src/main/resources/openapi.json create mode 100644 AndroidCompat/Config/src/main/resources/pref-schema.json create mode 100644 AndroidCompat/Config/src/main/resources/reference.conf create mode 100644 AndroidCompat/build.gradle.kts create mode 100644 AndroidCompat/lib/.gitignore create mode 100644 AndroidCompat/src/main/java/android/annotation/AnimRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/AnimatorRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/AnyRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/AppIdInt.java create mode 100644 AndroidCompat/src/main/java/android/annotation/ArrayRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/AttrRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/BinderThread.java create mode 100644 AndroidCompat/src/main/java/android/annotation/BoolRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/CallSuper.java create mode 100644 AndroidCompat/src/main/java/android/annotation/CheckResult.java create mode 100644 AndroidCompat/src/main/java/android/annotation/ColorInt.java create mode 100644 AndroidCompat/src/main/java/android/annotation/ColorRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/DimenRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/DrawableRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/FloatRange.java create mode 100644 AndroidCompat/src/main/java/android/annotation/FractionRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/IdRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/IntDef.java create mode 100644 AndroidCompat/src/main/java/android/annotation/IntRange.java create mode 100644 AndroidCompat/src/main/java/android/annotation/IntegerRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/InterpolatorRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/LayoutRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/MainThread.java create mode 100644 AndroidCompat/src/main/java/android/annotation/MenuRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/NonNull.java create mode 100644 AndroidCompat/src/main/java/android/annotation/Nullable.java create mode 100644 AndroidCompat/src/main/java/android/annotation/PluralsRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/RawRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/RequiresPermission.java create mode 100644 AndroidCompat/src/main/java/android/annotation/SdkConstant.java create mode 100644 AndroidCompat/src/main/java/android/annotation/Size.java create mode 100644 AndroidCompat/src/main/java/android/annotation/StringDef.java create mode 100644 AndroidCompat/src/main/java/android/annotation/StringRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/StyleRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/StyleableRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/SuppressLint.java create mode 100644 AndroidCompat/src/main/java/android/annotation/SystemApi.java create mode 100644 AndroidCompat/src/main/java/android/annotation/TargetApi.java create mode 100644 AndroidCompat/src/main/java/android/annotation/TestApi.java create mode 100644 AndroidCompat/src/main/java/android/annotation/TransitionRes.java create mode 100644 AndroidCompat/src/main/java/android/annotation/UiThread.java create mode 100644 AndroidCompat/src/main/java/android/annotation/UserIdInt.java create mode 100644 AndroidCompat/src/main/java/android/annotation/Widget.java create mode 100644 AndroidCompat/src/main/java/android/annotation/WorkerThread.java create mode 100644 AndroidCompat/src/main/java/android/annotation/XmlRes.java create mode 100644 AndroidCompat/src/main/java/android/app/Application.java create mode 100644 AndroidCompat/src/main/java/android/app/PackageDeleteObserver.java create mode 100644 AndroidCompat/src/main/java/android/app/PackageInstallObserver.java create mode 100644 AndroidCompat/src/main/java/android/app/Service.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java create mode 100644 AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java create mode 100644 AndroidCompat/src/main/java/android/content/ComponentCallbacks.java create mode 100644 AndroidCompat/src/main/java/android/content/ComponentCallbacks2.java create mode 100644 AndroidCompat/src/main/java/android/content/ComponentName.java create mode 100644 AndroidCompat/src/main/java/android/content/ContentValues.java create mode 100644 AndroidCompat/src/main/java/android/content/Context.java create mode 100644 AndroidCompat/src/main/java/android/content/ContextWrapper.java create mode 100644 AndroidCompat/src/main/java/android/content/Intent.java create mode 100644 AndroidCompat/src/main/java/android/content/SharedPreferences.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/ApplicationInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/FeatureInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/InstantAppInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/IntentFilterVerificationInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/KeySet.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/PackageInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/PackageItemInfo.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/PackageManager.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/PackageParser.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/Signature.java create mode 100644 AndroidCompat/src/main/java/android/content/pm/VersionedPackage.java create mode 100644 AndroidCompat/src/main/java/android/content/res/CompatibilityInfo.java create mode 100644 AndroidCompat/src/main/java/android/database/AbstractCursor.java create mode 100644 AndroidCompat/src/main/java/android/database/AbstractWindowedCursor.java create mode 100644 AndroidCompat/src/main/java/android/database/CharArrayBuffer.java create mode 100644 AndroidCompat/src/main/java/android/database/ContentObservable.java create mode 100644 AndroidCompat/src/main/java/android/database/CursorWindow.java create mode 100644 AndroidCompat/src/main/java/android/database/DataSetObservable.java create mode 100644 AndroidCompat/src/main/java/android/database/DatabaseUtils.java create mode 100644 AndroidCompat/src/main/java/android/database/DefaultDatabaseErrorHandler.java create mode 100644 AndroidCompat/src/main/java/android/database/Observable.java create mode 100644 AndroidCompat/src/main/java/android/database/SQLException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/DatabaseObjectNotClosedException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteAbortException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteAccessPermException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteBlobTooBigException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteClosable.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteConstraintException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursor.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursorDriver.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabase.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseConfiguration.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseCorruptException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseLockedException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatatypeMismatchException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDebug.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDirectCursorDriver.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDiskIOException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteDoneException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteFullException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteGlobal.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteMisuseException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteOpenHelper.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteOutOfMemoryException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteProgram.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteQuery.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteQueryBuilder.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatement.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatementInfo.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteTableLockedException.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SQLiteTransactionListener.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/SqliteWrapper.java create mode 100644 AndroidCompat/src/main/java/android/database/sqlite/package.html create mode 100644 AndroidCompat/src/main/java/android/net/ConnectivityManager.java create mode 100644 AndroidCompat/src/main/java/android/net/NetworkInfo.java create mode 100644 AndroidCompat/src/main/java/android/net/ParseException.java create mode 100644 AndroidCompat/src/main/java/android/net/Uri.java create mode 100644 AndroidCompat/src/main/java/android/net/WebAddress.java create mode 100644 AndroidCompat/src/main/java/android/os/BaseBundle.java create mode 100644 AndroidCompat/src/main/java/android/os/Build.java create mode 100644 AndroidCompat/src/main/java/android/os/Bundle.java create mode 100644 AndroidCompat/src/main/java/android/os/Environment.java create mode 100644 AndroidCompat/src/main/java/android/os/Parcelable.java create mode 100644 AndroidCompat/src/main/java/android/os/PersistableBundle.java create mode 100644 AndroidCompat/src/main/java/android/os/PowerManager.java create mode 100644 AndroidCompat/src/main/java/android/os/Process.java create mode 100644 AndroidCompat/src/main/java/android/os/SimpleClock.java create mode 100644 AndroidCompat/src/main/java/android/os/SystemClock.java create mode 100644 AndroidCompat/src/main/java/android/os/SystemProperties.java create mode 100644 AndroidCompat/src/main/java/android/os/UserHandle.java create mode 100644 AndroidCompat/src/main/java/android/os/ZygoteProcess.java create mode 100644 AndroidCompat/src/main/java/android/preference/PreferenceManager.kt create mode 100644 AndroidCompat/src/main/java/android/support/annotation/AnimRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/AnimatorRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/AnyRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/AnyThread.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/ArrayRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/AttrRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/BinderThread.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/BoolRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/CallSuper.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/CheckResult.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/ColorInt.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/ColorRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/DimenRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/Dimension.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/DrawableRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/FloatRange.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/FractionRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/IdRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/IntDef.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/IntRange.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/IntegerRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/InterpolatorRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/Keep.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/LayoutRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/MainThread.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/MenuRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/NonNull.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/Nullable.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/PluralsRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/Px.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/RawRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/RequiresApi.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/RequiresPermission.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/Size.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/StringDef.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/StringRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/StyleRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/StyleableRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/TransitionRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/UiThread.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/VisibleForTesting.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/WorkerThread.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/XmlRes.java create mode 100644 AndroidCompat/src/main/java/android/support/annotation/package-info.java create mode 100644 AndroidCompat/src/main/java/android/support/multidex/MultiDex.java create mode 100644 AndroidCompat/src/main/java/android/support/v4/content/ContextCompat.java create mode 100644 AndroidCompat/src/main/java/android/support/v4/os/EnvironmentCompat.java create mode 100644 AndroidCompat/src/main/java/android/support/v7/preference/PreferenceDataStore.java create mode 100644 AndroidCompat/src/main/java/android/support/v7/preference/PreferenceScreen.java create mode 100644 AndroidCompat/src/main/java/android/text/Html.java create mode 100644 AndroidCompat/src/main/java/android/text/TextUtils.java create mode 100644 AndroidCompat/src/main/java/android/text/format/Formatter.java create mode 100644 AndroidCompat/src/main/java/android/util/ArrayMap.java create mode 100644 AndroidCompat/src/main/java/android/util/Base64.java create mode 100644 AndroidCompat/src/main/java/android/util/ContainerHelpers.java create mode 100644 AndroidCompat/src/main/java/android/util/Log.java create mode 100644 AndroidCompat/src/main/java/android/util/MapCollections.java create mode 100644 AndroidCompat/src/main/java/android/view/DisplayAdjustments.java create mode 100644 AndroidCompat/src/main/java/android/webkit/MimeTypeMap.java create mode 100644 AndroidCompat/src/main/java/android/webkit/URLUtil.java create mode 100644 AndroidCompat/src/main/java/com/android/internal/util/ArrayUtils.java create mode 100644 AndroidCompat/src/main/java/com/android/internal/util/FastXmlSerializer.java create mode 100644 AndroidCompat/src/main/java/com/android/internal/util/Preconditions.java create mode 100644 AndroidCompat/src/main/java/com/android/internal/util/XmlUtils.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/package-info.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/BooleanAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/EnumAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/FloatAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/IntegerAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/LongAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preconditions.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preference.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/RxSharedPreferences.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringAdapter.java create mode 100644 AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringSetAdapter.java create mode 100644 AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/Connectivity.kt create mode 100644 AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/ReactiveNetwork.kt create mode 100644 AndroidCompat/src/main/java/com/squareup/duktape/Duktape.java create mode 100644 AndroidCompat/src/main/java/com/squareup/duktape/DuktapeException.java create mode 100644 AndroidCompat/src/main/java/dalvik/system/BaseDexClassLoader.java create mode 100644 AndroidCompat/src/main/java/dalvik/system/CloseGuard.java create mode 100644 AndroidCompat/src/main/java/dalvik/system/PathClassLoader.java create mode 100644 AndroidCompat/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.kt create mode 100644 AndroidCompat/src/main/java/kotlinx/coroutines/experimental/android/HandlerContext.kt create mode 100644 AndroidCompat/src/main/java/libcore/net/MimeUtils.java create mode 100644 AndroidCompat/src/main/java/libcore/net/UriCodec.java create mode 100644 AndroidCompat/src/main/java/libcore/util/EmptyArray.java create mode 100644 AndroidCompat/src/main/java/org/json/JSON.java create mode 100644 AndroidCompat/src/main/java/org/json/JSONArray.java create mode 100644 AndroidCompat/src/main/java/org/json/JSONException.java create mode 100644 AndroidCompat/src/main/java/org/json/JSONObject.java create mode 100644 AndroidCompat/src/main/java/org/json/JSONStringer.java create mode 100644 AndroidCompat/src/main/java/org/json/JSONTokener.java create mode 100644 AndroidCompat/src/main/java/org/json/package-info.java create mode 100644 AndroidCompat/src/main/java/rx.android.schedulers/AndroidSchedulers.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/FakePackageManager.java create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/bytecode/ModApplier.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/info/ApplicationInfoImpl.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/BuildConfigCompat.java create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/DrawableResource.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/RCompat.java create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/Resource.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/StringResource.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt create mode 100644 build.gradle.kts create mode 100755 scripts/getAndroid.sh create mode 100644 server/src/main/kotlin/eu/kanade/tachiyomi/App.kt create mode 100644 server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt diff --git a/AndroidCompat/Config/build.gradle b/AndroidCompat/Config/build.gradle new file mode 100644 index 00000000..de646f6d --- /dev/null +++ b/AndroidCompat/Config/build.gradle @@ -0,0 +1,4 @@ +dependencies { + // Config API +// implementation("com.typesafe:config:1.4.0") +} \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt new file mode 100644 index 00000000..cf1b9867 --- /dev/null +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt @@ -0,0 +1,12 @@ +package xyz.nulldev.ts.config + +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.singleton + +class ConfigKodeinModule { + fun create() = DI.Module("ConfigManager") { + //Config module + bind() with singleton { GlobalConfigManager } + } +} \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt new file mode 100644 index 00000000..f346fdce --- /dev/null +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt @@ -0,0 +1,74 @@ +package xyz.nulldev.ts.config + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import mu.KotlinLogging +import java.io.File + +/** + * Manages app config. + */ +open class ConfigManager { + private val generatedModules + = mutableMapOf, ConfigModule>() + val config by lazy { loadConfigs() } + + //Public read-only view of modules + val loadedModules: Map, ConfigModule> + get() = generatedModules + + open val configFolder: String + get() = System.getProperty("compat-configdirs") ?: "tachiserver-data/config" + + val logger = KotlinLogging.logger {} + + /** + * Get a config module + */ + inline fun module(): T + = loadedModules[T::class.java] as T + + /** + * Get a config module (Java API) + */ + fun module(type: Class): T + = loadedModules[type] as T + + /** + * Load configs + */ + fun loadConfigs(): Config { + val configs = mutableListOf() + + //Load reference config + configs += ConfigFactory.parseResources("reference.conf") + + //Load custom configs from dir + File(configFolder).listFiles()?.map { + ConfigFactory.parseFile(it) + }?.filterNotNull()?.forEach { + configs += it.withFallback(configs.last()) + } + + val config = configs.last().resolve() + + logger.debug { + "Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true)) + } + + return config + } + + fun registerModule(module: ConfigModule) { + generatedModules.put(module.javaClass, module) + } + + fun registerModules(vararg modules: ConfigModule) { + modules.forEach { + registerModule(it) + } + } +} + +object GlobalConfigManager : ConfigManager() diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt new file mode 100644 index 00000000..cc4c5107 --- /dev/null +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt @@ -0,0 +1,8 @@ +package xyz.nulldev.ts.config + +import com.typesafe.config.Config + +/** + * Abstract config module. + */ +abstract class ConfigModule(config: Config) diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ServerConfig.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ServerConfig.kt new file mode 100644 index 00000000..804f280c --- /dev/null +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ServerConfig.kt @@ -0,0 +1,35 @@ +package xyz.nulldev.ts.config + +import com.typesafe.config.Config +import java.io.File + +class ServerConfig(config: Config) : ConfigModule(config) { + val ip = config.getString("ip") + val port = config.getInt("port") + + val allowConfigChanges = config.getBoolean("allowConfigChanges") + val enableWebUi = config.getBoolean("enableWebUi") + val useOldWebUi = config.getBoolean("useOldWebUi") + val prettyPrintApi = config.getBoolean("prettyPrintApi") + // TODO Apply to operation IDs + val disabledApiEndpoints = config.getStringList("disabledApiEndpoints").map(String::toLowerCase) + val enabledApiEndpoints = config.getStringList("enabledApiEndpoints").map(String::toLowerCase) + val httpInitializedPrintMessage = config.getString("httpInitializedPrintMessage") + + val useExternalStaticFiles = config.getBoolean("useExternalStaticFiles") + val externalStaticFilesFolder = config.getString("externalStaticFilesFolder") + + val rootDir = registerFile(config.getString("rootDir")) + val patchesDir = registerFile(config.getString("patchesDir")) + + fun registerFile(file: String): File { + return File(file).apply { + mkdirs() + } + } + + companion object { + fun register(config: Config) + = ServerConfig(config.getConfig("ts.server")) + } +} \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt new file mode 100644 index 00000000..2f36be41 --- /dev/null +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt @@ -0,0 +1,6 @@ +package xyz.nulldev.ts.config.util + +import com.typesafe.config.Config + +operator fun Config.get(key: String) = getString(key) + ?: throw IllegalStateException("Could not find value for config entry: $key!") diff --git a/AndroidCompat/Config/src/main/resources/META-INF/services/xyz.nulldev.ts.api.v2.java.model.ServerAPI b/AndroidCompat/Config/src/main/resources/META-INF/services/xyz.nulldev.ts.api.v2.java.model.ServerAPI new file mode 100644 index 00000000..196fca85 --- /dev/null +++ b/AndroidCompat/Config/src/main/resources/META-INF/services/xyz.nulldev.ts.api.v2.java.model.ServerAPI @@ -0,0 +1 @@ +xyz.nulldev.ts.api.v2.java.impl.ServerAPIImpl \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/resources/al.png b/AndroidCompat/Config/src/main/resources/al.png new file mode 100644 index 0000000000000000000000000000000000000000..6529ad67852f4bb08605172d5bcba2c202149658 GIT binary patch literal 2444 zcmb7Gi$By^AO0~7GCQLP<+f3gc5-=TT+_Nn-XThj+cfbSVn{@@X2Q5EOZ1j-NhG-b-P(f0WAMAKg$Kfo^VIBBLFnsg?+gS1?z`H-F?vjkYoq|sTlwu1DjG8 z03g8(02XloU|S3Tnz3c=KK5XTLWt*C7che9vvWrZ81}}x`^N(SOm#QQz2hZ+0RSbj zhl}Gy;^Z>VJNwb)L(EmtZT$SH7apM_9%Ikcj&D;&*sk^JeX5OVdFNVE;=-q2w8iST zB_2t;`OzRP8>SiT&K$pKTbX@-vgT;O0q8viUl(Mwt?|kJfY5h_f_CEU)X<%R5<4kf zU?b?1N+*u4iB1dNej&!MycVrF95(m|>7C)DakcDY8L=?2N!C-7(upgaErn7MTF!!z zug@extiNr4RHQp(6jlEXLFp;vi!N7WHtmNiFF9Gio%U9`0??t;5JEJru9=~dD$0xz z?@<;f^E(*B1iBPawKi}r5VGYeSj88m z;MQn7Mla*yvsDK;dF-=J^GG}6$9=7UimeLiG7Z17r5to01pO?5LALG)3gS7DK9jM)1X{)5gnHlaNC9 z05uF>?r_%I^@&jxea^c|#$Qdzn4tC|eAKH?5tSj9%2kEGvI!$Y-TKdSPUqjWDw-NYYsLiNxh%5bM|cN6O~qwVk*K zGD>2Pde1<#-40*$Z*jgsd4A$w$e3n#Qq$Yx=2X{{iZXn7fb6hExGNNuVTP7Gxih~s ztMIKPx$`VcT^++$f^z1CeVm1JFv7h73Vlo3{frGgVr)p2^s*i9C+SrbKTh*r@j9K` zq^^dkso`=tRcoW(P6KK89vr~j4cyx$=Yk%eqfz}NSm*ByK$E6Yrwml$5u1C(S;gsu z@(kx1saC*#N}r&Qt%ldAcG7-W2<;tk0Ox1fKVJMq{3d%8S`Vf z_a|aQJ{t`#LHW1L75!+O*f^CsW^Vlj{KleIIVZ;P$0m4PXnI~>6nWbRT+!iKQUiJb zMcuUIVlcu2d}4l$1b?bVI@wtU5jNV1>q@>xKWZ3eK2D9XnJz>Af6erUod>@U$g@b% zUWJsthVK5Jq$_oSf9%+99BnbOYYX}tshZAPh(m6;fSTHt%TFIoiTH=oe>L_MPI#(zL$vCiK}TX8L#oU(4Uf8_#=^L>-@F5d$D#w?n=bAx|w za4)zaX8ur(t{QIUTXJ&=P2OIeco)j@?1LdJdM-ew09RdCQ*-X)`QI4 zu`ve;BTE_coCf~t)#Pe=h42lxV5|vu+ka1M40VpRA7Ak*l%sar|4CAVBy9gKzcTY? zf!pS1C_k@Qt*j|B*OOi7iK8Vd{uqE*FPS()DQGnM!9}VObm0@j){1-3nEDcQheZkw zMBM##J(9X=%6}+(8dtFiW3e$U@akk5XVgc-^yEWl9IX&y1@yV#Vk()Vf_3b2q({*n z{S4g$Y+P^dB8^>`1Sb{CYZHi|kV#*~)&-+TW@S>tP7#uN%!(_H!zq$u!K41+*q`dH z3kurU7NZW|KNyiVW&n#+|KKP!I)XaqD#3pDyuBys6gL9S>z1@$q)6H=YSqVQh0!aN zgh=Y>36T$t6X+XsRT2`V6rgbtG!C>W0U^qau93!d{f&IST;ONf(-t56N7g>N3i+hO zqbPFDmB?SuxiX;|YcA=$^g~{a$E-2`?9(a#$WQzjO1(Cr>`3x(8oONwCkn}e`9$5) zPT0R0xsaGVJuY{;XivG*Rb3B?1Ur?Q6;0=Gd%xO4gns?JNT`EnqWq@U1;u!O)p0ng zQ2fz0hbP(J#D9sI%V|GatxX_Luac9?Pt?6!&<&UMznuAQLyKM8?RI5- zC{x?oE#cuUAO7Q|-$SzA#M|t1*TGnj^oadYoht{~Bv;t(@uOD>hcaKdreK0*t=Xi$ z&YmO@trCk^e{BL&<`w7$1j=t2`bGWnVfV(iaER|OiPVo#^FhX5h>3_J8n1Y1H)^Fh z37Ha;^fzHPBhusdrr4%dUIS_UkU%T0^*j+#R2`ri);oxO*8YB`&zeQ*YwOYbt*YB? zcKGd*C398b&sOBYs%@vBynnr*JR2<7Ovv@=wMu}R`Ad=I%7~sGXe6Z1KBrmFLf{yGK+h;aCm6DRJ-yHk1>%UAZ6A#$-JLV8PBEt hgAas(!TpgPK=nRjT~GN*-0sK7!_~{>S0`NB{{VkVsD%Ik literal 0 HcmV?d00001 diff --git a/AndroidCompat/Config/src/main/resources/kitsu.png b/AndroidCompat/Config/src/main/resources/kitsu.png new file mode 100644 index 0000000000000000000000000000000000000000..bb9caec08af5c08c705f2b74332fe3ef93dfae34 GIT binary patch literal 7091 zcmbt(hc{f`7w<)gAQ4f9D2ezHLof(}=!_aQ(P#8FYM5xFMWV!LDOwOMdQBM2Frr28 zB$Cmi6MgjdCg1h`g7@xPXWg|wXYbG6XPQX10B~JXL)8!f$l(75 zH5Ccz7xcSLdXf1Usw)E(101U);fj;e6D0unl0bWALrIdad1;vW0Kkp5e}k;kv&fzV zvLV$>kw%`5NIx5I2f)k5#RDnq;efpRP#7XCDyDM9^)CRh^JuCnJ@togWVT=?Ym?PD z!&SVfKON|BJD*dBP~T`_uK{ADc^@d_If*kIOZNzF~W~>-v*d_Q{(ccM%aIbb6?wlYf?gMc200 zDBa9Rf!?qaA`m}D(%|UYZYrbOPP))amsiUwUfp-x>i~eZw{#*-uLo-cd9w$95ChHH z$bb$g^l-W72)+v2*kn0z`=L``BMmgCcCBbRl`=2d>>q!Gi>#9f>$?`T?mrIFN#9h? zs$AXQO{M`@LFtn{M0j!ig7(B%pX2e(&64Pz3U0c#F>gjz?s zr0nYhBN?^d0v~J0fSW9A1Xzc~iGV*Mkj$FS-nsC0W$jM;HTt4}CG&pvXwdn4-)q2- zlvqtcN9o`L==y%J_SIqd>QFNmph#`+7!_v{x@B&C=&U_@H9r?e2Arrn8ap(eTqe9L zWi&rT)V15w04sOW%--&3>2haGRZrb=l>!uJy1KOD4n4m3QUJub*go*9j21O!2Zw)- zzJ6qG+hhuW11s!oX8q)ge(1*{01>TIraqtl2?U&|EIG6Z!OHkzNZ{8Fuckq}i zVvOq>p>VmTRs0`LgUgwcCGueRvNw4H!ENUAyJ${49+dcnjF2j_TD&7`|$O@YFPKL>U6bFCGAa^{Eyoe42?eK1fN9NwZk{_irj#e>r?fc!O&F(LbUru z)Cdz{x;PQ8-LADS5$q1EkWJM{RI+~4s*v$A&n>!NHrTN2K{2@DqbmA>H5-I%gGx?e zGVDU6qdyNG$;N#$KT=y=r%<#mF$$LkZkun<~RED}>&z zx>yg|bjD!w*D5~ixE~~Z>jWkDu(<{>I2Q+BR%;FS$2;$hx}MTLGxt_t^Qc0GHWGa4qo=;U`6n6Wn$LH zNu!WbK|L$7Sq{VOMRJDyQVTf6=lPS>?qc#oK6TU6Rt1(O1q~xb3#O+blRcE2YbGgA za)-W5mSUvUwuZTqLN(4_?TQA!2n*!U09PRv()-P+#F8_9=OqVy!aVklA7YOwGm0}g zwd(Wz8HC$wPlb%-&F)yM3)jO}w+WSmsJP!!+x!Uy2FREl#J3BEOuVhVP-nR;Q-5K7 z+wAArZpt5m68IL3!XR%-d#Joss~d0DDeQo#iVOej>(wroW27F830=-{osh~ai8Gn-u9$G? zrhFe6_L$~I3i%It-4oEon3dm_0_T0dAR_#GN2$A8ea6h*Ycdrs%E&t+K1$W<1HGRd zn_Moj^SM@-3IBaFE1$sn@op*|8uID9Y%dQ15Clb(-Y5W1Z>>FYw&3kZpWw7TZHKO{ealO{lf`>}PQ!>5^?lr6(0;ZsYcgYU-Wgk$~au!q{4evV9lx~rU3 z{Pdc*I^Xl`(1@LQo*DY5@_jMF7sFatU8|Q%L!I2iBwPJEM+=eN){@8_3!+B#Qt3AZ znZ&3I<__6{$sL=K@3AYbcZ9^Nr_(AgbYgPKA=A$nPhXcrvF$|YktNc`;!j+fPC`pq z2{iMZ7EOn~^rmf!E3M;A*gpoJDs`DvP&SP|)O(o>R!7h7jzu&5llN9WL=@kBgxePC z17S#hQXn&I!x5c9cGu=J^-{M;4(*zrzup-D_xVAQbD}e>t^Ui*OR_w;ZAmMtzD<%v z-G3II2FvKRh@E@?SMBum&OP)#r=u!pdZ{;jX`FUm4V`K)N-`5NmQZnM^&ieNHTEVk zx;H&I_RoaCiTs6UrlwBD$Q?7f<=Zxm&O+a`KJoH8se!W0XZ9j?axr5+-ew1{oO6LX&<87(OjHnA8mho=og_DM-uIUim}mgc$YuejeUbs( zz?N#uK)-%>ZcimKJGj-~;EjfKb0t5-@=*K4<{Je5^j-M#ee>=_AUO>FZw&^}LVrJR*#isni@ z{hu1=f6y0iHn+UH*qSTQPISHQ6%uL8h?swTcv~@W^$jYQi=*LzX;2xmj~YKa<;m3L zhSt}m``SXvnOh(>?71h=?&KHmihr*RgWvV84Y-L!wUDy3XxG0q|1HM9&>IW*-8)@U zVmF1+ZLioW8a%#wuTD-`dIxHI>e?64IhgrD-?HA2t#=$RTX)H!ZdFh=PPtHs;nT#p^G zH4irUJxWzQ58=mW6Q<`(hhyzTPay%nfuT+C>JS^Q9wUMC>sGiqhayMh8G*0|d3*MI z#DK#Dq>HfpA9~*EUr<%h&lZ-Ql#qm=_m(nu>p+<9{Wh#*UafiT&*jtYQGJ5m=A$~W z#C50=2;(#g>rHsi-QzhHFOJ9>rWXph{KK^Y9})HLY|>r`P*(zpBXrk`f=27FBDFb zkV(Nj`!Zebz5@mf?*hFuf{Pd_%vEq&diKa7Xw;Sc&`WeuCPI9I64O2YaD0i{HmqC< zISiQ^-wlho>NV|Hns0OnJ&|4ZJl`r4GcHVYbe$9ipS*Br$@nxx*@hg3;gDWF^d4yY zycb$Ig?9`D*aMY7Xaip)CL;t}o*@!N_;a77u6TJ@)3ItlO!(9D zn}cpsye(r?p;M1^U*PaiLG-$P2|2|^F*os(Sj>Ax7m=;yN!R3?7T$=6kLw!svR{MU z)B$0VuWue8!wFSH4Og^HjwPo)70S!^6r6lj)x7jz>`POH@hG{o!78z3ITRu(s8|Qu%&ET?)TUia&y^>0qm+cJhl; zirwO7F}_0MIo$#wD*oC$o;-8q6IUtyqNn!ubFO%F2``a(VRax6$`Y6YS9T+XU4fie>{)N$$)zcMe=Xp5p?ai*mi_3KFre)?>DU*CcL&rw2^QFcvy=Sxm0`rQj=7?s8VoE~hjmhDIphn?P(I1-( zg`XqqSrAcKjT%BkZiIhAHlzQ3n7N5dg!{ejE%_jXho$Z12zWCDu&xj|3+QbB>W>QVSWEZm0<59`)-AuA5%ClK?rE#_$s8 zG>PUuQtxs{fU;8`dtPjsvjtATFnSZXx9yqcJ_tU(9 zjdAJtJ&w?_X;I{_pPV10ggyN|3xOdcie@5w^u6}&Z_hd2EPj%@nQ9wM_2??4i5`B+ ziy|GtSRh-$&-2R1%d0$GD7W=`E*s|BD5u3|wrn|?GOoae9LHRtB9hs!d;P$2_H2v? z*PH+-me3wsv!nKrjCAXWnV$HL9M1eBp)kZnX}mD)(KQ`OkXX}J7)FV@kpEMdoQOl< z9&9{H@K1--sJ~>!)Up3S%H@4o&KEOCi_9vf0nKN>yLT|zDlGrGc4sL%pgsHcKfULa zU%LvlfB&+iF~L!1=4HqHnuv45l@-gT_`m;;sGLdQ>#AW3B#WQ>)d7Fiz3VsCB&WE> z`JkPup8qaHF~p_Wssh)sCI+~BLeKxjZ25+t)OC*E$+63=qunsD#vUnvyi5dftMA4$ zjflQEq-9g{8Vo;4(Y)moGc8OC#6hK-ax#g=9S3zM3ZDA7tJ|S*Fy{pF^-Xh9(xpb4 zd2qUYhObCjo;p6_kgxgy)#_aOP$}&yrifxP^}E6HVmMu2R#UVOY<%}WEs3I^=vBk- zkiw??n}9Pqe7!;m1J_9rhz$51c01!$_QyCy_MpY@wM=s5S z-qQrS@#9IXauk1k#Q@A#LbPP;mLz06+qxRO6ke5tvouw8FQ}7E zDH@M()z8k%bX+NM`^$hypWb}DCaIgF!sO;u$c8JiHuY*SnW2aig3nWo=Y+|M_iuwa z3uMO*sz*im<8gmE6bt!h2Xw@dvKl$H;@a+91}(sNZnmso|Inf}A)LT9$D6eh^ulDC znj*VdcKtbSLWu^G$=tPcvw(IyuUWR&8W$oFLl_Kfn5P|$K;2b1+=32kPu5sr}D_Iv{lrHPYGG?Rfv~k=N-%i zw9oYwYyIa{$MRj&ZvX6)A1^ygT0XX zXqV=jjFii7&+O~gA!WmU;3v%Xu1Wic3#hG>P(@K#bX+)bkf{uNH>4n^$D0V#5@h_; zgx0<^_K`PWKW?n9;UT^Z|55$xK%S3&bQCt^Cy*wSNhRC@|Gm zX!qlZBXZa1<*(N{7B{^grC8r;$cN?ooN%lk!|!)KVC2aTW$N0bls%M2Dz2X<9q9Tg z^sH5xMNlN2bt*(pyhL>gA^-Xe}2#-G_CsQipmkLQne`PUPfD4lWyVZ@&Ut-I z?*H&FLq5nDVgLv^E=`;2=SN|x&*xt<7UW-?Lm2_noOGPMd&H81Hlfgn^E21(t`S-F z5CFA%k@sx`ymyJQD&+EhZ|ul|!2&$F=?%yZLNQ)sV+KF-8}utDg%%=)Ye5U#9&s$p zvkNF5_&q{@(X`-A233~|?0!18AdU^yL11t@3~B#NUm zaBJnoczA3l^zTcoY+$=&=x#l*{psraUwIXG-cNpigo`2RLchfIv z4(He3%mzGma=2YN_Jn@GJ!x{i5pg0KNei5srgavK@Z}iD$~7zsaF!n|4{geDB$~nF;m-?q*p!qqM&SL%uAGbcL;-Tg88(4DW961T> zb4c*0MCh|8D^**bn)fE%)CmH@YRh7mk~}2foHLQA@7Zan5!m8uF>&c{BwpdN+>S=g)i6m>Xm6% zDqx&v4=g%8ni4T4I$){y5=jnBCg^lkH+u6WjTYAY2K!%@&SP#ZS_3z)rWtheoP)P4 zM?>&I^K5uacHv_SAUYy$wWSEYYC$MtBNU=?v38scq%_lL3g@VqY?_i$rO6QY237Io{|dKdpFfw? ze^r{O5rd5%zcU8HF5adEFnr7ymK*ih8+EcstuW%&*`f@4o>-h@EcrKZY<$@~b+fp7 z5db_?s7wr9w!OoiWJx1%9`smCQo%gUvKyFK8nZb+H5lD*l_Q;FYd3qOS}&xaKSb%$ zx5mD#1VIjD%i8;^8Aat=jQ}SNBT6_->L#fX@K&@{2+vL&$&ynvk>!aC5(fI-^i|p$ z5J}&_zs7A z-p+&T5}T{?K`7$q-IM&y;%28+ZJyiOhQb(klec<;HlLJHvr5sur08y zQZ0w4QPBa3DpUbb+{>i^Kr*mEm88FK)W>l$qP}1JV zOjkx~e+|*3t!<5j@_u9WF*};1@Y-ZBB6U3eU!T0)3C~M#EkSW;4QSj3dflE}p5oHxr^gqTolNsol&Y$)f$G=|M# zju}gfMl(uI%_@gXUS03|KfJ%ZpXfq*ap#nH*HV~nNosb%`~ z%Day%lg!okGpmf5)u~Tw)2weyHfMI8GrO?c)Pp1zxUbO+uOg$|M{OM&~AJ# zZhr|8w5@9>03bp6(+B#4@!tEUQkb<%*p*=4ut-#h4*-n{xE-c{+b2xjNFSnq-0-}> ztrY-3@Dj|*(kbf3YW{Bggq2Ev?eeL?HxG4Oe4H|E)T+5UW5J zN`clBMSX&)`;oM#6}cWW?Zml4HHw=*|3;BYJ$63&>s+k#=+V?tqZZmR=*1}B@bH}c z87kpngt|Q{GC<%tW(lb5ctrGCZtE%9X{?-)U?q@vf@)>gSuA4z1kLV~xL)O77;f=~ z{DLhkSWZi{)HUU@K)HXR1y%XRX~r5}r`xm&*)k-fWN>%r)FaPVHaSA{r&RUVkFkwQ}TRbL?`$sgExS$GUA zL&5|}9WDvR9mBl$fXck|fd8W|ZInX&leF{kO`gMRn{spQ#Jb>;McuuZBJqa*zk zCLgXOOisCOdvIR*SB$qN&m%rA=97{hB8pY-BLJ54b5Jc%%n;P(P%kj6~Sq5MFOO%vn^MBK)?9+FesLZyOb+YMq^zYKg@-6!&%4j4i;n zfPu$0GdBh@>tdawbfOj-$1Z6X@0Uj6iJi^ZUy(bUImc?n6_( zMUFB7FBGGP0?&dAlf}ubu5i)W7@cGp1>RgeRFVJvxnC>-!X>mHD|QxR6T|<$^JMII zN8n!ixPIOF9J;f6%H5#lB3Z0!BfT4h8fTa$X%@=LM-~L$d;~E`eQRciy3@-(c|>fI zDOn8%s#)mHX&w>VmewLCwVDWsnSkWiN))6a`(evBOWCx9o7<4TmB?#+pyX(?lmn#| zGRNj^bA)S!hcycavN)1D4}uz|Lt`T}Rff)o(R!Nd2>chgX7?RgwPV&Y6XdHb%oB4e z9zqm3B|Sm5V#t%UNOIl?f@)B~L?3~pfWMHsS8fj!#c9bv^m$rU(zEJm5Z&RGl<_VC zM7qHB^v+{Uj;zG9D<_W|#L6wCez%2+0f%bt+6{{u!?7+GKwj{u1FB8O4KDROqg_`2 zdK10{I^%yrfTcv&+={VEBHN5v=4lA4S-zN41bbeN;wWuMVFq3bg9(qyOi;HSN)A$_ z%y0?Ct*&5lKBRdWnH77m+1vG>f+^)N4*ui41Lvl9Fr z$PNZ>BAqJro6&%Y1i{Z`BqUVQ4GU=;)?m}J*3V|AA3a~b7csR_Q67ir3(-nqg5s&p zW#3`n2I&R*>}gR8X1Lm&+KBo5Z4;KC4A-74)?D~sJaJI!p26#z2)@Z~`k%J|v%YLq I1NBV&Hw#>^I{*Lx literal 0 HcmV?d00001 diff --git a/AndroidCompat/Config/src/main/resources/openapi.json b/AndroidCompat/Config/src/main/resources/openapi.json new file mode 100644 index 00000000..da4503e9 --- /dev/null +++ b/AndroidCompat/Config/src/main/resources/openapi.json @@ -0,0 +1,4291 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "TachiWeb-Api", + "version": "3.2.8", + "description": "**Security:**\n\nAuthentication is currently not supported. Clients should use the *No Security* option until authentication is implemented.", + "contact": { + "url": "https://github.com/TachiWeb/TachiWeb-Server" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" + } + }, + "servers": [ + { + "url": "{protocol}://{host}:{port}", + "description": "", + "variables": { + "protocol": { + "default": "http", + "description": "" + }, + "host": { + "default": "localhost" + }, + "port": { + "default": "4567" + } + } + } + ], + "paths": { + "/api/v3/manga": { + "summary": "Path used to manage the list of manga.", + "description": "The REST endpoint/path used to list `manga` entities. This path contains a `GET` operation to perform the list task.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/manga" + } + } + } + }, + "description": "Successful response - returns an array of `manga` entities." + } + }, + "operationId": "getMangas", + "summary": "List all manga", + "description": "Gets a list of all `manga` entities.\n\nThis operation can be slow and return large amounts of data.\nIt is recommended to be used sparingly." + } + }, + "/api/v3/manga/{mangaId}": { + "summary": "Path used to get and update a single manga.", + "description": "The REST endpoint/path used to get and update single instances of a `manga`. This path contains `GET` and `PATCH` operations used to perform the get and update tasks respectively.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga" + } + } + }, + "description": "Successful response - returns a single `manga`." + }, + "404": { + "description": "The manga could not be found." + } + }, + "operationId": "getManga", + "summary": "Get a manga", + "description": "Gets the details of a single instance of a `manga`." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/cover": { + "summary": "Get the cover/thumbnail of a manga.", + "get": { + "parameters": [ + { + "name": "use-placeholder", + "description": "If the manga with the supplied `mangaId` exists but has no cover, supply a placeholder image in place of the `404 Not Found` response.\n\nIf the manga does not exist, a `404 Not Found` response will still be returned.\n\nDefaults to: `false`", + "schema": { + "type": "boolean" + }, + "in": "query" + } + ], + "responses": { + "200": { + "content": { + "image/jpeg": { + + }, + "image/png": { + + }, + "image/webp": { + + } + }, + "description": "The manga has a cover and it has been returned." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_MANGA", + "NO_COVER" + ] + } + } + }, + "description": "The manga with the supplied `mangaId` does not exist or has no cover.\n\nThe reason for the failure is supplied in the response body." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "An error occured while loading the cover.\n\nThe exact failure reason is provided in the response body." + } + }, + "operationId": "getMangaCover", + "summary": "Get a manga's cover/thumbnail." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/flags": { + "summary": "Path used to manage a single manga's flags.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga-flags" + } + } + }, + "description": "Successful response - returns a single manga's flags." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "getMangaFlags", + "summary": "Get a manga's flags", + "description": "Gets the details of a single instance of a manga's flags." + }, + "put": { + "requestBody": { + "description": "The new flags to replace the existing flags with.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga-flags" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga-flags" + } + } + }, + "description": "Successful response. Returns the new manga flags." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "setMangaFlags", + "summary": "Replace all of a manga's flags", + "description": "Replaces all a manga's flags with the supplied flags." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/favorite": { + "summary": "Path used to manage a single manga's favorite status.", + "description": "", + "put": { + "requestBody": { + "description": "The new favorite status of the manga.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "description": "Successful response.\n\nThe new favorite status of the manga is returned." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "setMangaFavorited", + "summary": "Set a manga's favorite status.", + "description": "" + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/viewer": { + "summary": "Path used to manage a single manga's viewer.", + "description": "", + "put": { + "requestBody": { + "description": "The new manga viewer.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga-viewer" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga-viewer" + } + } + }, + "description": "Successful response.\n\nThe new viewer of the manga is returned." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "setMangaViewer", + "summary": "Set a manga's viewer.", + "description": "" + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/categories": { + "summary": "Path used to manage the list of categories.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/category" + } + } + } + }, + "description": "Successful response - returns an array of `category` entities." + } + }, + "operationId": "getCategories", + "summary": "List all categories", + "description": "Gets a list of all `category` entities sorted ascending by the `order` field." + }, + "post": { + "requestBody": { + "description": "A new `category` to be created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mutate-category-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } + }, + "description": "Successful response.\n\nThe newly created category is returned." + }, + "409": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NAME_CONFLICT", + "ORDER_CONFLICT" + ] + } + } + }, + "description": "Category creation failed due to a conflict.\n\nThe exact reason for the failure is supplied in the response body." + } + }, + "operationId": "createCategory", + "summary": "Create a category", + "description": "Creates a new instance of a `category`." + } + }, + "/api/v3/categories/{categoryId}": { + "summary": "Path used to manage a single category.", + "description": "The REST endpoint/path used to get, update, and delete single instances of an `category`. This path contains `GET`, `PUT`, and `DELETE` operations used to perform the get, update, and delete tasks, respectively.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } + }, + "description": "Successful response - returns a single `category`." + }, + "404": { + "description": "No category with the supplied `id` could be found." + } + }, + "operationId": "getCategory", + "summary": "Get a category", + "description": "Gets the details of a single instance of a `category`." + }, + "put": { + "requestBody": { + "description": "Updated `category` information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mutate-category-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } + }, + "description": "Successful response.\n\nThe updated category is returned." + }, + "404": { + "description": "No category with the supplied `id` could be found." + }, + "409": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NAME_CONFLICT", + "ORDER_CONFLICT" + ], + "type": "string" + } + } + }, + "description": "Category edit failed due to a conflict.\n\nThe exact reason for the failure is supplied in the response body." + } + }, + "operationId": "editCategory", + "summary": "Update a category", + "description": "Updates an existing `category`." + }, + "delete": { + "responses": { + "204": { + "description": "Successful response." + }, + "404": { + "description": "No category with the supplied `id` could be found." + } + }, + "operationId": "deleteCategory", + "summary": "Delete a category", + "description": "Deletes an existing `category`." + }, + "parameters": [ + { + "name": "categoryId", + "description": "The category's `id`.", + "schema": { + "format": "int32", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/chapters": { + "summary": "Get a list of all chapter associated with this manga.", + "get": { + "parameters": [ + { + "name": "apply-filters", + "description": "Whether or not to apply the filters specified in the manga flags.\n\nDefaults to: `false`", + "schema": { + "type": "boolean" + }, + "in": "query", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chapter" + } + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "getMangaChapters", + "summary": "Get a list of all chapter associated with this manga sorted by the manga's flags.", + "description": "" + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/update": { + "summary": "Update a manga's info.", + "description": "Does not update a manga's chapters.", + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga" + } + } + }, + "description": "Metadata/info update successful.\n\nThe new manga metadata is returned." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The metadata/info update failed.\n\nThe exact reason is supplied in the responsed body." + } + }, + "operationId": "updateMangaInfo", + "summary": "Update a manga's metadata/info.", + "description": "It is recommended for UIs to call this when the initialized field of the manga is `false`.\n\nCalling this will update the initialized field of the manga to `true` if the request succeeds." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/chapters/update": { + "summary": "Update a manga's chapters.", + "description": "Does not update a manga's info.", + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chapter" + } + } + } + }, + "description": "Manga chapters update successful.\n\nThe new chapter list will be returned." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The manga chapter update failed.\n\nThe exact reason is supplied in the responsed body." + } + }, + "operationId": "updateMangaChapters", + "summary": "Update a manga's chapters.", + "description": "It is recommended for UIs to call this when the user transitions from the catalogue screen to the manga screen." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/chapters": { + "summary": "Path used to manage chapters.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chapter" + } + } + } + }, + "description": "Successful response." + } + }, + "operationId": "getChapters", + "summary": "List all chapters across all manga.", + "description": "This operation can be slow and return large amounts of data.\nIt is recommended to be used sparingly." + } + }, + "/api/v3/chapters/{chapterId}": { + "summary": "Path used to manage a single chapter.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chapter" + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + } + }, + "operationId": "getChapter", + "summary": "Get a single chapter by it's id." + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/chapters/{chapterId}/page-count": { + "summary": "Manage a chapter's page count.", + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "format": "int64", + "type": "integer" + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "Failed to calculate the number of pages in the chapter.\n\nThe exact reason is supplied in the responsed body." + } + }, + "operationId": "getChapterPageCount", + "summary": "Get a chapter's page count.", + "description": "This is POST request for technical reasons." + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/chapters/{chapterId}/reading-status": { + "summary": "Updates the time this chapter was last read", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chapter-reading-status" + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + } + }, + "operationId": "getChapterReadingStatus", + "summary": "Get the reading status of a chapter." + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chapter-reading-status" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/chapter-reading-status" + } + } + }, + "description": "Successful response.\n\nThe new reading status of the chapter is returned." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + } + }, + "operationId": "setChapterReadingStatus", + "summary": "Replace the reading status of a chapter.", + "description": "Will also update the tracking information associated with this manga." + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/chapters/{chapterId}/download": { + "summary": "Path used to manage the download associated with this chapter", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/download" + } + } + }, + "description": "Success response." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_CHAPTER", + "NO_DOWNLOAD" + ] + } + } + }, + "description": "No chapter exists with the supplied `chapterId` or this chapter is not associated with a download.\n\nThe specific error is supplied in the response body." + } + }, + "operationId": "getChapterDownload", + "summary": "Get the download associated with this chapter." + }, + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/download" + } + } + }, + "description": "Success response.\n\nThe newly created download is returned." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/download" + } + } + }, + "description": "Failed to begin downloading this chapter as this chapter is already associated with a download.\n\nThe associated download is supplied in the response body." + } + }, + "operationId": "beginChapterDownload", + "summary": "Begin downloading a chapter" + }, + "delete": { + "responses": { + "204": { + "description": "The download was successfully deleted." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_CHAPTER", + "NO_DOWNLOAD" + ] + } + } + }, + "description": "No chapter exists with the supplied `chapterId` or this chapter is not associated with a download.\n\nThe specific error is supplied in the response body." + }, + "423": { + "description": "Could not delete the download as the status of the download is not: `DOWNLOADED`." + } + }, + "operationId": "deleteChapterDownload", + "summary": "Delete the download associated with this chapter.", + "description": "Note that only fully downloaded downloads can be deleted." + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/downloader": { + "summary": "Path to manage the chapter downloader.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/downloader" + } + } + }, + "description": "Success response." + } + }, + "operationId": "getDownloader", + "summary": "Gets the chapter downloader.", + "description": "" + } + }, + "/api/v3/downloader/paused": { + "summary": "Path to manage the pause state of the chapter downloader.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "description": "Success response, returns whether or not the downloader is paused." + } + }, + "operationId": "isDownloaderPaused", + "summary": "Gets whether or not the downloader is paused." + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "description": "Success response. The new pause state of the downloader is returned." + } + }, + "operationId": "setDownloaderPaused", + "summary": "Updates the pause state of the downloader." + } + }, + "/api/v3/downloader/clear": { + "summary": "Path used to clear the downloader's queue.", + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/downloader" + } + } + }, + "description": "Success response, the new downloader is returned." + } + }, + "operationId": "clearDownloader", + "summary": "Clear the downloader's queue." + } + }, + "/api/v3/chapters/{chapterId}/page/{pageIndex}/image": { + "summary": "Manage the image associated with a chapter page.", + "get": { + "responses": { + "200": { + "content": { + "image/jpeg": { + + }, + "image/png": { + + }, + "image/webp": { + + }, + "image/gif": { + + } + }, + "description": "Success response. The page's image is returned." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_CHAPTER", + "NO_PAGE" + ] + } + } + }, + "description": "No chapter with the specified `chapterId` was found or no page was found at the supplied `pageIndex`.\n\nThe exact failure reason is supplied in the response body." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The server failed to fetch the page's image.\n\nThe exact failure reason is supplied in the response body." + } + }, + "operationId": "getChapterPageImage", + "summary": "Get the image associated with a chapter page.", + "description": "Note that the page image may be animated." + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + }, + { + "name": "pageIndex", + "description": "The index of the page (0-indexed).", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/tasks": { + "summary": "List all uncompleted tasks", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/task" + } + } + } + }, + "description": "Success response." + } + }, + "operationId": "getTasks", + "summary": "Get a list of all uncompleted tasks." + } + }, + "/api/v3/tasks/{taskUuid}": { + "summary": "Path used to manage a single task.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/task" + } + } + }, + "description": "Success response." + }, + "404": { + "description": "No task exists with the supplied `taskUuid`." + } + }, + "operationId": "getTask", + "summary": "Get a task." + }, + "parameters": [ + { + "name": "taskUuid", + "description": "The UUID of the task.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/sources": { + "summary": "Path used to manage the list of sources.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/source" + } + } + } + }, + "description": "Successful response - returns an array of `source` entities." + } + }, + "operationId": "getSources", + "summary": "List all sources.", + "description": "Gets a list of all `source` entities." + } + }, + "/api/v3/sources/{sourceId}": { + "summary": "Path used to manage a single source.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/source" + } + } + }, + "description": "Successful response - returns a single `source`." + }, + "404": { + "description": "No source exists with the provided `id`." + } + }, + "operationId": "getSource", + "summary": "Get a source", + "description": "Gets the details of a single instance of a `source`." + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/sources/{sourceId}/login": { + "summary": "Path used to login to a source.", + "post": { + "requestBody": { + "description": "The login credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/login-request" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "The login succeeded." + }, + "400": { + "content": { + "application/json": { + "schema": { + "enum": [ + "AUTH_UNSUPPORTED", + "ALREADY_LOGGED_IN" + ] + } + } + }, + "description": "The source does not require authentication or the user is already logged in.\n\nThe exact failure reason is provided in the response body." + }, + "401": { + "description": "The login failed (usually this means the credentials are invalid)." + }, + "404": { + "description": "No source exists with the provided `id`." + } + }, + "operationId": "loginToSource", + "summary": "Login to a source.", + "description": "" + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/sources/{sourceId}/filters": { + "summary": "Manage the filters for a source", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/catalogue-filters" + } + } + }, + "description": "Success response." + }, + "404": { + "description": "No source exists with the provided `id`." + } + }, + "operationId": "getSourceFilters", + "summary": "Get the filters for a source." + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/sources/{sourceId}/catalogue": { + "summary": "Manage a catalogue page.", + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/catalogue-page-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/catalogue-page" + } + } + }, + "description": "Success response." + }, + "404": { + "description": "No source exists with the provided `id`." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "Failed to get catalogue page.\n\nThe exact failure reason is supplied in the response body." + } + }, + "operationId": "getSourceCatalogue", + "summary": "Request a catalogue page." + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/sources/{sourceId}/latest-updates": { + "summary": "Manage a latest-updates catalogue page.", + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/latest-updates-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/catalogue-page" + } + } + }, + "description": "Success response." + }, + "404": { + "description": "No source exists with the provided `id`." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "Failed to get latest-updates catalogue page.\n\nThe exact failure reason is supplied in the response body." + } + }, + "operationId": "getSourceLatestUpdates", + "summary": "Request a latest-updates catalogue page." + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/preferences": { + "summary": "Path used to manage the list of preferences.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/preference" + } + } + } + }, + "description": "Successful response - returns an array of `preference` entities." + } + }, + "operationId": "getPreferences", + "summary": "List all preferences", + "description": "Gets a list of all `preference` entities." + } + }, + "/api/v3/preferences/schema": { + "summary": "Get the preference schema (used to render preferences).", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + + } + }, + "description": "Success response." + } + }, + "operationId": "getPreferencesSchema", + "summary": "Get the preference schema." + } + }, + "/api/v3/manga/recently-read": { + "get": { + "parameters": [ + { + "name": "range-size-ms", + "description": "How far back to look (in milliseconds) while finding the recently read manga.\n\nRecommended: `2592000000` ms = 1 month", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/recently-read" + } + } + } + }, + "description": "Successful response - returns an array of `recently-read` entities sorted by most to least recent." + } + }, + "operationId": "getRecentlyReadManga", + "summary": "List recently read manga", + "description": "Hardcoded to return a maximum of 25 entries. Includes the chapter that was last read in each manga." + } + }, + "/api/v3/chapters/{chapterId}/reading-status/last-read": { + "summary": "Manage information on when this chapter was last read", + "delete": { + "responses": { + "200": { + "description": "Success response." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + } + }, + "operationId": "deleteChapterLastRead", + "summary": "Deletes the information on when this chapter was last read" + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/categories/{categoryId}/manga": { + "summary": "Path to manage a category's manga.", + "patch": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mutate-category-manga-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + } + } + }, + "description": "Successful response - the specified manga were added/removed from the category, returns a list of the ids of the manga in the category." + }, + "400": { + "description": "The supplied mutation request contains a manga id that is in both the `add` list and the `remove` list." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_CATEGORY", + "NO_MANGA" + ] + } + } + }, + "description": "No category with the specified `categoryId` was found or a manga `id` specified in the request body array did not have an associated manga.\n\nNo manga were added/removed from the `category`.\n\nThe exact failure reason is supplied in the response body." + } + }, + "operationId": "editCategoryManga", + "summary": "Add/remove manga from a category." + }, + "parameters": [ + { + "name": "categoryId", + "description": "The category's `id`.", + "schema": { + "format": "int32", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/extensions/{extensionPackage}": { + "summary": "Path to manage a single extension", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/extension" + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No extension exists with the specified `extensionPackage`." + } + }, + "operationId": "getExtension", + "summary": "Get information about a specific extension" + }, + "delete": { + "responses": { + "200": { + "description": "The extension was successfully deleted." + }, + "404": { + "description": "No extension exists with the specified `extensionPackage`." + } + }, + "operationId": "deleteExtension", + "summary": "Delete an extension.", + "description": "Extensions that are present in the central repository will not be deleted. Instead, their `status`' will be changed to `AVAILABLE`." + }, + "parameters": [ + { + "name": "extensionPackage", + "description": "The `pkgName` of the extension.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/extensions/{extensionPackage}/install": { + "description": "", + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/extension" + } + } + }, + "description": "The extension was successfully installed/updated." + }, + "400": { + "description": "The extension's status is not `AVAILABLE` and it's `hasUpdate` field is `false`." + }, + "404": { + "description": "No extension exists with the specified `extensionPackage`." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The extension could not be installed/updated.\n\nThe exact failure reason is provided in the response body." + } + }, + "operationId": "installExtension", + "summary": "Installs or updates an extension from the central repository.", + "description": "The extension must either have a `status` of `AVAILABLE` or it's `hasUpdate` field is `true`." + }, + "parameters": [ + { + "name": "extensionPackage", + "description": "The `pkgName` of the extension.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/extensions/trust": { + "post": { + "requestBody": { + "description": "The signature hash.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful response. The provided signature hash is now trusted." + }, + "400": { + "description": "The provided signature hash is invalid." + } + }, + "operationId": "trustExtensionSignatureHash", + "summary": "Trust a signature hash.", + "description": "Multiple extensions may have the same signature hash.\n\nTrusting a signature hash will also cause future extensions that have the same signature hash to be trusted." + } + }, + "/api/v3/extensions/{extensionPackage}/icon": { + "get": { + "responses": { + "200": { + "content": { + "image/png": { + + }, + "image/webp": { + + } + }, + "description": "Successful response." + }, + "404": { + "description": "No extension exists with the specified `extensionPackage`." + } + }, + "operationId": "getExtensionIcon", + "summary": "Get the icon of an extension." + }, + "parameters": [ + { + "name": "extensionPackage", + "description": "The `pkgName` of the extension.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/extensions/reload-available": { + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/extension" + } + } + } + }, + "description": "Successful response. The new extension list is returned (including extensions of any `status`)." + } + }, + "operationId": "reloadAvailableExtensions", + "summary": "Fetch available extension list from central repository.", + "description": "Also checks for extension updates." + } + }, + "/api/v3/tracking/services": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking-service" + } + } + } + }, + "description": "Returns a list of all tracking services." + } + }, + "operationId": "getTrackingServices", + "summary": "List all tracking services." + } + }, + "/api/v3/tracking/services/{trackingServiceId}": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tracking-service" + } + } + }, + "description": "Successful response." + }, + "404": { + "description": "No tracking service with the supplied `trackingServiceId` could be found." + } + }, + "operationId": "getTrackingService", + "summary": "Get information about a single tracking service." + }, + "parameters": [ + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/tracking/services/{trackingServiceId}/logout": { + "post": { + "responses": { + "200": { + "description": "Successfully logged out of the tracking service." + }, + "400": { + "description": "The user is not logged into the specified tracking service (`loggedIn` field of the tracking service is `false`)." + }, + "404": { + "description": "No tracking service with the supplied `trackingServiceId` could be found." + } + }, + "operationId": "logoutOfTrackingService", + "summary": "Logout of a tracking service.", + "description": "Can only be performed if the user is logged into the tracking service (`loggedIn` field of the tracking service is `true`)." + }, + "parameters": [ + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/tracking/services/{trackingServiceId}/login": { + "post": { + "requestBody": { + "description": "The login credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/login-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successfully logged into the tracking service." + }, + "400": { + "content": { + "application/json": { + "schema": { + "enum": [ + "OAUTH_ONLY", + "ALREADY_LOGGED_IN" + ] + } + } + }, + "description": "The user is already logged into the specified tracking service (`loggedIn` field of the tracking service is `true`) or the tracking service is an OAuth based service.\n\nThe exact failure reason is provided in the response body." + }, + "404": { + "description": "No tracking service with the supplied `trackingServiceId` could be found." + } + }, + "operationId": "loginToTrackingService", + "summary": "Logout of a tracking service.", + "description": "Can only be performed if the user is logged out of the tracking service (`loggedIn` field of the tracking service is `false`) and the tracking service is not an OAuth based service." + }, + "parameters": [ + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/handle-url": { + "post": { + "requestBody": { + "description": "The entire URL (including the scheme, path and query parameters).", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/url-handle-result" + } + } + }, + "description": "Indicates that the URL has been processed." + }, + "400": { + "description": "Indicates that the provided URL is not valid." + } + }, + "operationId": "handleUrl", + "summary": "Handle an application URL", + "description": "This endpoint is used to handle `tachiyomi://` URLs.\n\nClients should intercept all URLs that have the `tachiyomi` scheme and POST these URLs to the server via this endpoint." + } + }, + "/api/v3/tracking/services/{trackingServiceId}/search": { + "post": { + "requestBody": { + "description": "The search query. Should usually be the title of the manga to search for.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking-search-result" + } + } + } + }, + "description": "Successful response - search results are returned." + }, + "400": { + "description": "The user is not logged into the specified tracking service (`loggedIn` field of the tracking service is `false`)." + }, + "404": { + "description": "No tracking service with the supplied `trackingServiceId` could be found." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The search failed.\n\nThe exact failure reason is provided in the response body." + } + }, + "operationId": "searchTrackingService", + "summary": "Search for manga in a tracking service", + "description": "Can only be performed if the user is logged into the tracking service (`loggedIn` field of the tracking service is `true`)." + }, + "parameters": [ + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/tracks": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/track" + } + } + } + }, + "description": "Returns a list of all tracking information associated with this manga." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + } + }, + "operationId": "getMangaTracks", + "summary": "List all tracking information associated with this manga." + }, + "post": { + "requestBody": { + "description": "A tracking search result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tracking-search-result" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/track" + } + } + }, + "description": "Tracking information was bound successfully. The new tracking information is returned." + }, + "404": { + "description": "No manga with the supplied `mangaId` exists." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "An error occurred while binding the tracking information.\n\nThe exact failure reason is provided in the repsonse body." + } + }, + "operationId": "bindMangaTrack", + "summary": "Bind a manga to tracking information" + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/tracks/{trackingServiceId}": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/track" + } + } + }, + "description": "Returns tracking information on the specified tracking service associated with this manga." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_MANGA", + "NO_TRACKING_SERVICE", + "NO_TRACK" + ] + } + } + }, + "description": "No manga with the supplied `mangaId` exists, no tracking service is associated with the specified `trackingServiceId` or no track associated with the tracking service with the specified `trackingServiceId` is bound to this manga.\n\nThe exact failure reason is provided in the response body." + } + }, + "operationId": "getMangaTrack", + "summary": "Get tracking information on the specified tracking service associated with this manga." + }, + "put": { + "requestBody": { + "description": "The new tracking information. It will overwrite the old tracking information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mutate-track-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/track" + } + } + }, + "description": "Tracking information changed successfully on the specified service, the updated tracking information is returned." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_MANGA", + "NO_TRACKING_SERVICE", + "NO_TRACK" + ] + } + } + }, + "description": "No manga with the supplied `mangaId` exists, no tracking service is associated with the specified `trackingServiceId` or no track associated with the tracking service with the specified `trackingServiceId` is bound to this manga.\n\nThe exact failure reason is provided in the response body." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "An error occurred while changing the tracking information.\n\nThe exact failure reason is provided in the repsonse body." + } + }, + "operationId": "editMangaTrack", + "summary": "Change the tracking information manually." + }, + "delete": { + "responses": { + "200": { + "description": "Manga successfully unbound from it's tracking information on the specified tracking service." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_MANGA", + "NO_TRACKING_SERVICE", + "NO_TRACK" + ] + } + } + }, + "description": "No manga with the supplied `mangaId` exists, no tracking service is associated with the specified `trackingServiceId` or no track associated with the tracking service with the specified `trackingServiceId` is bound to this manga.\n\nThe exact failure reason is provided in the response body." + } + }, + "operationId": "unbindMangaTrack", + "summary": "Unbind a manga from it's tracking information." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + }, + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/manga/{mangaId}/tracks/{trackingServiceId}/update": { + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/track" + } + } + }, + "description": "Tracking information refreshed successfully, the new information is returned." + }, + "404": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NO_MANGA", + "NO_TRACKING_SERVICE", + "NO_TRACK" + ] + } + } + }, + "description": "No manga with the supplied `mangaId` exists, no tracking service is associated with the specified `trackingServiceId` or no track associated with the tracking service with the specified `trackingServiceId` is bound to this manga.\n\nThe exact failure reason is provided in the response body." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "An error occurred while updating the tracking information.\n\nThe exact failure reason is provided in the repsonse body." + } + }, + "operationId": "updateMangaTrack", + "summary": "Re-fetch tracking information from the specified tracking service associated with this manga." + }, + "parameters": [ + { + "name": "mangaId", + "description": "The `manga`'s `id`.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + }, + { + "name": "trackingServiceId", + "description": "The `id` of the tracking service.", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/server/stop": { + "post": { + "responses": { + "200": { + "description": "Successful response, indicates that the server shutdown process has started." + } + }, + "operationId": "stopServer", + "summary": "Stop/shutdown the server." + } + }, + "/api/v3": { + "summary": "The API root.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + + } + }, + "description": "An OpenAPI specification in JSON format." + } + }, + "security": [ + { + + } + ], + "operationId": "getApiSpec", + "summary": "Returns the OpenAPI specification for this API" + } + }, + "/api/v3/sources/{sourceId}/logout": { + "summary": "Path used to logout of a source.", + "post": { + "responses": { + "204": { + "description": "The logout succeeded." + }, + "400": { + "content": { + "application/json": { + "schema": { + "enum": [ + "AUTH_UNSUPPORTED", + "NOT_LOGGED_IN" + ] + } + } + }, + "description": "The source does not require authentication or the user is not logged in.\n\nThe exact failure reason is provided in the response body." + }, + "404": { + "description": "No source exists with the provided `id`." + } + }, + "operationId": "logoutOfSource", + "summary": "Logout of a source", + "description": "" + }, + "parameters": [ + { + "name": "sourceId", + "description": "The `id` of the source.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/backups/backup": { + "summary": "Create a backup", + "post": { + "responses": { + "200": { + "content": { + "application/octet-stream": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "The backup was created successfully." + } + }, + "operationId": "backupAll", + "summary": "Create a backup" + } + }, + "/api/v3/backups/restore": { + "summary": "Restore a backup.", + "post": { + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "Restore started. The UUID of the task where the restore is running in is supplied in the response body.\n\nThe data field of the task will be of the `restore-task-data` datatype once the restore is completed." + } + }, + "operationId": "restoreAll", + "summary": "Restore a backup." + } + }, + "/api/v3/sync": { + "summary": "Sync endpoint", + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sync-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sync-response" + } + } + }, + "description": "The sync completed successfully." + }, + "426": { + "content": { + "application/json": { + "schema": { + "type": "integer" + } + } + }, + "description": "The client's sync specification version does not match the server's sync specification version.\n\nThe server's sync specification version is returned." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "An error occured during the syncing process and the sync was not performed.\n\nThe exact failure reason is provided in the repsonse body." + } + }, + "security": [ + { + "auth-token-header": [ + ] + }, + { + "auth-token-cookie": [ + ] + } + ], + "operationId": "sync", + "summary": "Sync endpoint." + } + }, + "/api/v3/preferences/{preferenceKey}": { + "summary": "Path used to manage a single preference.", + "description": "", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/preference" + } + } + }, + "description": "Successful response - returns a single `preference`." + }, + "404": { + "description": "No preference exists with the supplied `preferenceKey`." + } + }, + "operationId": "getPreference", + "summary": "Get a preference", + "description": "Gets the details of a single instance of a `preference`." + }, + "put": { + "requestBody": { + "description": "Updated `preference` value", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "$ref": "#/components/schemas/mutate-preference-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful response." + }, + "404": { + "description": "No preference exists with the supplied `preferenceKey`." + } + }, + "operationId": "setPreference", + "summary": "Update a preference", + "description": "Updates an existing `preference`." + }, + "parameters": [ + { + "name": "preferenceKey", + "description": "The key of the preference.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/extensions": { + "summary": "Path to manage extensions.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/extension" + } + } + } + }, + "description": "Successful response." + } + }, + "operationId": "getExtensions", + "summary": "List all extensions.", + "description": "Includes all extensions, regardless of their `status`." + }, + "post": { + "requestBody": { + "description": "The extension APK.", + "content": { + "application/vnd.android.package-archive": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/extension" + } + } + }, + "description": "Successful response. The extension was successfully imported." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "The request body did not contain a valid extension or the extension could not be imported.\n\nThe exact failure reason is supplied in the response body." + } + }, + "operationId": "createExtensionFromAPK", + "summary": "Imports an extension from an APK file." + } + }, + "/api/v3/manga/library": { + "get": { + "parameters": [ + { + "name": "include-total-downloaded", + "description": "Set to `true` to include the `totalDownloaded` field in the response. Defaults to `false`.\n\n**This will incur a performance penalty.**", + "schema": { + "type": "boolean" + }, + "in": "query", + "required": false + }, + { + "name": "include-last-read-index", + "description": "Set to `true` to include the `lastReadIndex` field in the response. Defaults to `false`.\n\n**This will incur a performance penalty.**", + "schema": { + "type": "boolean" + }, + "in": "query", + "required": false + }, + { + "name": "include-total-chapters-index", + "description": "Set to `true` to include the `totalChaptersIndex` field in the response. Defaults to `false`.\n\n**This will incur a performance penalty.**", + "schema": { + "type": "boolean" + }, + "in": "query", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/library-manga" + } + } + } + }, + "description": "Successful response." + } + }, + "operationId": "getLibraryMangas", + "summary": "Get all favorited manga along with some extra information about each manga." + } + }, + "/api/v3/chapters/{chapterId}/bookmark": { + "summary": "Path used to manage a single chapter's bookmarked status.", + "description": "", + "put": { + "requestBody": { + "description": "The new bookmarked status of the chapter.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + }, + "description": "Successful response.\n\nThe new bookmarked status of the chapter is returned." + }, + "404": { + "description": "No chapter exists with the supplied `chapterId`." + } + }, + "operationId": "setChapterBookmarked", + "summary": "Set a chapter's bookmarked status.", + "description": "" + }, + "parameters": [ + { + "name": "chapterId", + "description": "The chapter id.", + "schema": { + "format": "int64", + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/auth/validate": { + "get": { + "responses": { + "200": { + "description": "Successful response - the client's authentication is valid." + }, + "401": { + "description": "The client's authentication is invalid." + } + }, + "operationId": "getAuthValid", + "summary": "Validate that the client is authenticated" + } + }, + "/api/v3/auth/tokens": { + "summary": "Endpoint for managing auth tokens", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/auth-token" + } + } + } + }, + "description": "Successful response.\n\nThe auth tokens are returned (without their secrets)." + } + }, + "operationId": "getAuthTokens", + "summary": "List all auth tokens created by this user." + }, + "post": { + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create-auth-token-request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/auth-token" + } + } + }, + "description": "Successful response.\n\nThe new authentication token is returned (including the secret)." + } + }, + "operationId": "createAuthToken", + "summary": "Create an auth token." + } + }, + "/api/v3/auth/tokens/{tokenId}": { + "summary": "Endpoint used to manage a single auth token.", + "delete": { + "responses": { + "200": { + "description": "Successful response - the token was successfully deleted." + }, + "404": { + "description": "No auth token exists with the specified identifier." + } + }, + "operationId": "deleteAuthToken", + "summary": "Delete the auth token." + }, + "parameters": [ + { + "name": "tokenId", + "description": "The auth token's identifier.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/api/v3/categories/batch": { + "summary": "Endpoint to perform batch operations on categories", + "put": { + "requestBody": { + "description": "Updated `category` information.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/batch-mutate-category-request" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/category" + } + } + } + }, + "description": "Successful response - returns the edited `categories`." + }, + "404": { + "content": { + "application/json": { + "schema": { + "format": "int32", + "type": "integer" + } + } + }, + "description": "No category with the supplied `id` could be found.\n\nIf any of the categories being edited cannot be found in the database by their ids, none of the categories will be edited.\n\nWill return the id of the first category edit request which could not be found in the database." + }, + "409": { + "content": { + "application/json": { + "schema": { + "enum": [ + "NAME_CONFLICT", + "ORDER_CONFLICT" + ], + "type": "string" + } + } + }, + "description": "Category edit failed due to a conflict.\n\nThe exact reason for the failure is supplied in the response body.\n\nThe category that the edit failed on is **not** provided.\n\nIf an error occurs while performing any of the requested edits, none of the categories will be edited.\n" + } + }, + "operationId": "editCategories", + "summary": "Update categories", + "description": "Updates existing `categories`." + } + } + }, + "components": { + "schemas": { + "manga": { + "title": "Root Type for manga", + "description": "A single manga.", + "required": [ + "id", + "favorite", + "sourceId", + "lastUpdate", + "status", + "title", + "viewer", + "flags", + "categories", + "initialized", + "tracks" + ], + "type": "object", + "properties": { + "id": { + "format": "int64", + "description": "The unique identifier for this manga.", + "type": "integer" + }, + "sourceId": { + "description": "The id of the source this manga belongs to.", + "type": "string" + }, + "favorite": { + "description": "Whether or not the manga is favorited.", + "type": "boolean" + }, + "lastUpdate": { + "format": "int64", + "description": "The time this manga was last updated in milliseconds since epoch.", + "type": "integer" + }, + "viewer": { + "$ref": "#/components/schemas/manga-viewer", + "description": "The viewer for this manga." + }, + "flags": { + "$ref": "#/components/schemas/manga-flags", + "description": "The manga's flags.", + "properties": { + "sortDirection": { + "type": "string" + }, + "displayMode": { + "type": "string" + }, + "readFilter": { + "type": "string" + }, + "downloadedFilter": { + "type": "string" + }, + "sortType": { + "type": "string" + } + } + }, + "url": { + "description": "The URL of this manga on the source. Will not be present for mangas in the local source.", + "type": "string" + }, + "title": { + "description": "The title of this manga.", + "type": "string" + }, + "artist": { + "description": "The artist of the manga.", + "type": "string" + }, + "author": { + "description": "The author of the manga.", + "type": "string" + }, + "description": { + "description": "The description of the manga. Will be rather large and may contain multiple lines/paragraphs.", + "type": "string" + }, + "genre": { + "description": "The genre(s) of a manga. If the manga has multiple genres, the genres are often (but not always) split by comma.", + "type": "string" + }, + "status": { + "description": "The status of this manga.", + "enum": [ + "UNKNOWN", + "ONGOING", + "COMPLETED", + "LICENSED" + ] + }, + "categories": { + "description": "The `id`s of the categories that this manga belong to.", + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + }, + "initialized": { + "description": "Whether or not this manga has had it's metadata (not chapters) updated for the first time yet.", + "type": "boolean" + }, + "tracks": { + "description": "An array of the tracking information associated with this manga.\n\nA single manga may be tracked on multiple services which is why this field is an array.", + "type": "array", + "items": { + "$ref": "#/components/schemas/track" + } + } + }, + "example": { + "id": 1, + "sourceId": 123, + "favorite": true, + "lastUpdate": 1544920787968, + "viewer": "DEFAULT", + "flags": { + "sortDirection": "DESCENDING", + "displayMode": "NAME", + "readFilter": "ALL", + "downloadedFilter": "ALL", + "sortType": "SOURCE" + }, + "url": "https://mangadex.org/title/17709/kumo-desu-ga-nani-ka", + "title": "Kumo Desu ga, Nani ka?", + "artist": "Kakashi Asahiro", + "author": "Baba Okina", + "description": "When a mysterious explosion killed an entire class full of high school students, the souls of everyone in class were transported into a fantasy world and reincarnated. While some students were reincarnated as princes or prodigies, others were not as blessed.\n\nOur heroine, who was the lowest in the class, discovered that she was reincarnated as a spider! Now at the bottom of the food chain, she needs to adapt to the current situation with willpower in order to live. Stuck in a dangerous labyrinth filled with monsters, it's eat or be eaten!\n\nThis is the story of a spider doing whatever she can in order to survive!", + "genre": "Action, Adventure, Comedy, Drama, Fantasy, Isekai", + "status": "ONGOING", + "categories": [ + 2, + 4 + ], + "initialized": true + } + }, + "manga-viewer": { + "description": "An enum representing a manga's viewer:\n\n- `DEFAULT`: The user's default viewer (stored in the user preferences)\n- `LEFT_TO_RIGHT`: A paged left-to-right viewer.\n- `RIGHT_TO_LEFT`: A paged right-to-left viewer.\n- `VERTICAL`: A paged top-to-bottom viewer.\n- `WEBTOON`: A continous top-to-bottom viewer.", + "enum": [ + "DEFAULT", + "LEFT_TO_RIGHT", + "RIGHT_TO_LEFT", + "VERTICAL", + "WEBTOON" + ] + }, + "mutate-category-request": { + "title": "Root Type for category", + "description": "A request to create/edit a category.", + "required": [ + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the category.\n\nMust be unique across all categories (case-insensitive, two categories are considered the same if their only difference is different casing).", + "type": "string" + }, + "order": { + "format": "int32", + "description": "Categories are sorted ascending by this number.\n\nMust be unique across all categories.\n\nDefaults to the `maximum order of all existing categories + 1`.", + "type": "integer" + } + }, + "example": { + "name": "Dropped", + "order": 1 + } + }, + "chapter": { + "title": "Root Type for chapter", + "description": "A single chapter of a manga.", + "required": [ + "bookmarked", + "dateFetch", + "id", + "mangaId", + "name", + "readingStatus" + ], + "type": "object", + "properties": { + "url": { + "description": "The url of this chapter on the source website.\n\nWill be `null` if this chapter belongs to a manga in the local source.", + "type": "string" + }, + "name": { + "description": "The name of this chapter.", + "type": "string" + }, + "dateUpload": { + "format": "int64", + "description": "The date in milliseconds since epoch representing when this chapter was uploaded to the source.", + "type": "integer" + }, + "chapterNumber": { + "format": "float", + "description": "The chapter number of this chapter (usually parsed from the chapter name).\n\nWill be `null` if unknown.", + "type": "number" + }, + "scanlator": { + "description": "The name of the scanlator for this chapter.", + "type": "string" + }, + "id": { + "format": "int64", + "description": "A unique identifier for this chapter. Will be unique across all chapters and all manga.", + "type": "integer" + }, + "mangaId": { + "format": "int64", + "description": "The id of the `manga` this chapter belongs to.", + "type": "integer" + }, + "dateFetch": { + "format": "int64", + "description": "The date in milliseconds since epoch representing when the metadata for this chapter was downloaded from the source URL.", + "type": "integer" + }, + "sourceOrder": { + "format": "int64", + "description": "The index that this chapter appears in on the source website.", + "type": "integer" + }, + "readingStatus": { + "$ref": "#/components/schemas/chapter-reading-status", + "description": "The reading status of this chapter." + }, + "bookmarked": { + "description": "Whether or not the chapter is bookmarked.", + "type": "boolean" + } + }, + "example": { + "url": "https://mangadex.org/chapter/415934", + "name": "Ch. 29.2", + "dateUpload": 1544928692640, + "chapterNumber": 29.2, + "scanlator": "aumakua", + "id": 35, + "mangaId": 1, + "bookmark": false, + "dateFetch": 1544928692640, + "sourceOrder": 0, + "readingStatus": { + "read": false, + "lastPageRead": 0 + } + } + }, + "chapter-reading-status": { + "title": "Root Type for chapter-reading-status", + "description": "The reading status of a chapter.", + "required": [ + "read", + "lastPageRead" + ], + "type": "object", + "properties": { + "read": { + "description": "Whether or not this chapter is fully read.", + "type": "boolean" + }, + "lastPageRead": { + "format": "int64", + "description": "The last page read in this chapter.", + "type": "integer" + }, + "lastRead": { + "format": "int64", + "description": "The time this chapter was last read in milliseconds since epoch. Not present if unknown.", + "type": "integer" + } + }, + "example": { + "read": false, + "lastPageRead": 0, + "lastRead": 1551218464314 + } + }, + "download": { + "title": "Root Type for download", + "description": "A chapter download. May represent a complete or partial download.", + "required": [ + "mangaId", + "chapterId", + "chapterName", + "downloadedPages", + "progress", + "mangaTitle", + "status" + ], + "type": "object", + "properties": { + "mangaTitle": { + "description": "The title of the manga of the chapter associated with this download.", + "type": "string" + }, + "chapterName": { + "description": "The name of the chapter associated with this download.\n\nDepends on the `displayMode` field of the manga the chapter belongs to.", + "type": "string" + }, + "progress": { + "format": "float", + "description": "The progress of this download between (0 and 1).\n\nNote that this number is not equal to the `downloadedPages` divided by `totalPages` since this number also includes the download progress on any currently downloading pages.", + "type": "number" + }, + "downloadedPages": { + "format": "int64", + "description": "The number of pages fully downloaded so far.", + "type": "integer" + }, + "totalPages": { + "format": "int64", + "description": "The total number of pages in the chapter associated with this download.\n\nWill be `null` if currently unknown.", + "type": "integer" + }, + "mangaId": { + "format": "int64", + "description": "The id of the manga of the chapter associated with this download.", + "type": "integer" + }, + "chapterId": { + "format": "int64", + "description": "The id of the chapter associated with this download.", + "type": "integer" + }, + "status": { + "description": "The status of this download.", + "enum": [ + "QUEUED", + "DOWNLOADING", + "DOWNLOADED", + "ERROR" + ] + } + }, + "example": { + "mangaTitle": "Kumo Desu ga, Nani ka?", + "chapterName": "Ch. 29.2", + "mangaId": 1, + "chapterId": 2, + "progress": 0.51, + "downloadedPages": 10, + "totalPages": 20, + "status": "DOWNLOADING" + } + }, + "downloader": { + "description": "The chapter downloader on the server.", + "required": [ + "paused", + "downloads" + ], + "type": "object", + "properties": { + "paused": { + "description": "Whether or not the downloader is paused.", + "type": "boolean" + }, + "downloads": { + "description": "A list of all currently **unfinished** downloads.\n\nIncludes only downloads in the `QUEUED`, `DOWNLOADING` and `ERROR` states.", + "type": "array", + "items": { + "$ref": "#/components/schemas/download" + } + } + } + }, + "task": { + "description": "A long-running task.", + "required": [ + "type", + "completed", + "startedAt", + "uuid" + ], + "type": "object", + "properties": { + "type": { + "description": "The type of the task.", + "enum": [ + "RESTORE_BACKUP", + "UPDATE_LIBRARY" + ] + }, + "completed": { + "description": "Whether or not the task is completed.", + "type": "boolean" + }, + "progress": { + "format": "float", + "description": "The progress on the task (between 0 and 1).\n\nCan be `null` if unknown.", + "type": "number" + }, + "startedAt": { + "format": "int64", + "description": "The time when this task was started in milliseconds since epoch.", + "type": "integer" + }, + "progressText": { + "description": "The progress of the current task in textual form.\n\nMay represent the currently processing subtask in this task.", + "type": "string" + }, + "data": { + "description": "The data associated with the task.\n\nUsually only existant when the task is completed.", + "type": "string" + }, + "uuid": { + "description": "The UUID of the task.\n\nNote that the value of this field is opaque, clients should not attempt to parse it. It will always be URL safe.", + "type": "string" + } + }, + "example": { + "type": "RESTORE_BACKUP", + "completed": true, + "progress": 1, + "startedAt": 1551594603012, + "progressText": "Restored 10082 manga", + "data": "[100, 2, 4, 5]", + "uuid": "01ARZ3NDEKTSV4RRFFQ69G5FAV" + } + }, + "restore-task-data": { + "title": "Root Type for restore-task-data", + "description": "The data of a restore task.", + "required": [ + "errors", + "log", + "result" + ], + "type": "object", + "properties": { + "result": { + "description": "The resulting status of the restore.", + "enum": [ + "SUCCESSFUL", + "SUCCESSFUL_WITH_ERRORS", + "FAILED" + ] + }, + "errors": { + "description": "A list of errors that occured during the restore process.", + "type": "array", + "items": { + "type": "string" + } + }, + "log": { + "description": "A log of the restore process.", + "type": "string" + } + }, + "example": { + "result": "SUCCESSFUL_WITH_ERRORS", + "errors": [ + "An error", + "Another error" + ], + "log": "A very long log..." + } + }, + "source": { + "title": "Root Type for source", + "description": "A manga source.", + "required": [ + "requiresLogin", + "id", + "name", + "supportsLatest" + ], + "type": "object", + "properties": { + "id": { + "description": "The id of the source, unique across all sources.", + "type": "string" + }, + "name": { + "description": "The name of this source.", + "type": "string" + }, + "lang": { + "description": "The ISO 639-1 compliant language code of the source (two letters in lower case).", + "type": "string" + }, + "langName": { + "description": "The full name of the language of this source in English.", + "type": "string" + }, + "langDisplayName": { + "description": "The full name of the language of this source in the language itself.", + "type": "string" + }, + "supportsLatest": { + "description": "Whether or not this source supports the latest updates screen.", + "type": "boolean" + }, + "requiresLogin": { + "description": "Whether or not the source requires login.", + "type": "boolean" + }, + "loggedIn": { + "description": "Whether or not the user is logged into the source.\n\nWill be `null` if the source does not require login.", + "type": "boolean" + } + }, + "example": { + "id": "3", + "name": "MangaDex", + "lang": "fr", + "langName": "French", + "langDisplayName": "Français", + "supportsLatest": true, + "requiresLogin": true, + "loggedIn": false + } + }, + "catalogue-page": { + "description": "A catalogue/latest-updates page.", + "required": [ + "mangas", + "hasNextPage" + ], + "type": "object", + "properties": { + "mangas": { + "description": "The mangas on the current page of the catalogue.", + "type": "array", + "items": { + "$ref": "#/components/schemas/manga" + } + }, + "hasNextPage": { + "description": "Whether or not there is another page after this catalogue page.", + "type": "boolean" + } + } + }, + "catalogue-page-request": { + "description": "A request for a catalogue page.", + "required": [ + "page" + ], + "type": "object", + "properties": { + "page": { + "description": "The 1-indexed page number of the catalogue to obtain.", + "type": "integer" + }, + "query": { + "description": "The search string.", + "type": "string" + }, + "filters": { + "$ref": "#/components/schemas/catalogue-filters", + "description": "The filters to apply." + } + } + }, + "latest-updates-request": { + "description": "A request for the latest updates catalogue page.", + "required": [ + "page" + ], + "type": "object", + "properties": { + "page": { + "description": "The page number of the catalogue to obtain.", + "type": "integer" + } + }, + "example": { + "page": 86 + } + }, + "category": { + "title": "Root Type for category", + "description": "A manga category.", + "required": [ + "id", + "name", + "manga", + "order" + ], + "type": "object", + "properties": { + "id": { + "format": "int32", + "description": "The id of the category.", + "type": "integer" + }, + "name": { + "description": "The name of the category.", + "type": "string" + }, + "order": { + "format": "int32", + "description": "Categories are sorted ascending by this number.\n\nMust be unique across all categories.\n\n", + "type": "integer" + }, + "manga": { + "description": "An array of the `id`s of the manga present in this category.", + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + } + }, + "example": { + "id": 321, + "name": "Dropped", + "order": 1, + "manga": [ + 2, + 5, + 19, + 26 + ] + } + }, + "recently-read": { + "description": "A recently read manga + chapter combination.", + "required": [ + "manga", + "chapter" + ], + "type": "object", + "properties": { + "manga": { + "$ref": "#/components/schemas/manga", + "description": "The manga that was recently read." + }, + "chapter": { + "$ref": "#/components/schemas/chapter", + "description": "The chapter that was recently read in the manga." + } + } + }, + "manga-flags": { + "title": "Root Type for manga-flags", + "description": "Flags specific to manga objects. Often describe sorting and filtering.", + "required": [ + "displayMode", + "downloadedFilter", + "readFilter", + "sortDirection", + "sortType", + "bookmarkedFilter" + ], + "type": "object", + "properties": { + "sortDirection": { + "$ref": "#/components/schemas/sort-direction", + "description": "The sort direction of a manga's chapters." + }, + "displayMode": { + "description": "Option to display chapters by their name or chapter number.", + "enum": [ + "NAME", + "NUMBER" + ] + }, + "readFilter": { + "description": "Whether or not to show only read/unread chapters.", + "enum": [ + "SHOW_READ", + "SHOW_UNREAD", + "SHOW_ALL" + ] + }, + "downloadedFilter": { + "description": "Whether or not to show only downloaded/not downloaded chapters.", + "enum": [ + "SHOW_DOWNLOADED", + "SHOW_NOT_DOWNLOADED", + "SHOW_ALL" + ] + }, + "sortType": { + "description": "The method used to sort chapters:\n- by the order they appear in the **source** website\n- by their chapter **number**", + "enum": [ + "SOURCE", + "NUMBER" + ] + }, + "bookmarkedFilter": { + "description": "Whether or not to show only read/non-bookmarked chapters.", + "enum": [ + "SHOW_BOOKMARKED", + "SHOW_NOT_BOOKMARKED", + "SHOW_ALL" + ] + } + }, + "example": { + "sortDirection": "DESCENDING", + "displayMode": "NAME", + "readFilter": "ALL", + "downloadedFilter": "ALL", + "sortType": "SOURCE" + } + }, + "mutate-category-manga-request": { + "description": "A request to add/remove manga from a category.", + "type": "object", + "properties": { + "add": { + "description": "A list of the `id`s of the manga to add to this category.\n\n`id`s of manga already present in the category will be ignored.", + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + }, + "remove": { + "description": "A list of the `id`s of the manga to remove from this category.\n\n`id`s of manga not present in the category will be ignored.", + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + } + } + }, + "extension": { + "description": "A binary package of sources that can be downloaded from a central repository or uploaded manually by the user.", + "required": [ + "pkgName", + "name", + "status", + "versionName", + "versionCode", + "lang" + ], + "type": "object", + "properties": { + "pkgName": { + "description": "The package name of the extension. Unique across all extensions.", + "type": "string" + }, + "name": { + "description": "The display name of the extension.", + "type": "string" + }, + "status": { + "description": "The status of the extension.\n\n- `INSTALLED`: Extension is installed and trusted\n- `UNTRUSTED`: Extension is installed but not trusted. Sources inside this extension cannot be used until the extension is trusted.\n- `AVAILABLE`: Extension is available in the central repository and is not downloaded/installed", + "enum": [ + "INSTALLED", + "UNTRUSTED", + "AVAILABLE" + ] + }, + "versionName": { + "description": "Version name of the extension.", + "type": "string" + }, + "versionCode": { + "description": "Computer-readable version code of extension.", + "type": "integer" + }, + "signatureHash": { + "description": "Signature hash of extension. Only present when extension status is `UNTRUSTED`.\n\nMultiple extensions may have the same signature hash.\n\nThe signature hash is used to trust extensions.", + "type": "string" + }, + "lang": { + "description": "Language of extension represented in ISO 639-1 format. Can also be set to `all` when the extension includes sources of multiple languages.", + "type": "string" + }, + "sources": { + "description": "A list of the `id`s of all the sources included in the extension. Extensions may include multiple sources. Only present when the extension status is `INSTALLED`.", + "type": "array", + "items": { + "type": "string" + } + }, + "hasUpdate": { + "description": "Whether or not an update is available for this extension in the central repository. Only present when the extension status is `UNTRUSTED` or `INSTALLED`.", + "type": "boolean" + } + }, + "example": { + "pkgName": "eu.kanade.tachiyomi.extension.en.dynasty", + "name": "Dynasty", + "status": "INSTALLED", + "versionName": "1.2.6", + "versionCode": 6, + "lang": "en", + "sources": [ + "738706855355689486" + ], + "hasUpdate": true + } + }, + "error": { + "description": "An error.", + "required": [ + "type", + "message" + ], + "type": "object", + "properties": { + "type": { + "description": "The type of the error, not human readable.\n\nWill be `UNKNOWN` if the type is unknown.", + "type": "string" + }, + "stackTrace": { + "description": "Stack trace of the error to be used for debugging purposes.\n\nMay not be present if no stack trace could be generated.", + "type": "string" + }, + "message": { + "description": "A human readable message representing this error.", + "type": "string" + } + }, + "example": { + "type": "UNKNOWN", + "stackTrace": "java.lang.IllegalStateException: Not enough cheese!\r\n\tat Line_3.(Unknown Source)\r\n\tat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)\r\n\tat sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.ja\r\n\tat java.lang.reflect.Constructor.newInstance(Constructor.java:423)\r\n\tat org.jetbrains.kotlin.cli.common.repl.GenericReplEvaluator$eval$1$scriptInstance$1.invoke(Gener\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.configuration.SnippetExecutionInterceptor$Plain.execute(Snip\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter$eval$evalRes$1.invoke(ReplInterpreter.kt:101\r\n\tat org.jetbrains.kotlin.cli.common.repl.GenericReplEvaluator.eval(GenericReplEvaluator.kt:94)\r\n\tat org.jetbrains.kotlin.cli.common.repl.GenericReplCompilingEvaluator.eval(GenericReplCompilingEv\r\n\tat org.jetbrains.kotlin.cli.common.repl.GenericReplCompilingEvaluator.compileAndEval(GenericReplC\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter.eval(ReplInterpreter.kt:96)\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal.eval(ReplFromTerminal.kt:115)\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal.one(ReplFromTerminal.kt:106)\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal.doRun(ReplFromTerminal.kt:71)\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal.access$doRun(ReplFromTerminal.kt:38)\r\n\tat org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal$Companion.run(ReplFromTerminal.kt:171)\r\n\tat org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:104)\r\n\tat org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:57)\r\n\tat org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:96)\r\n\tat org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:52)\r\n\tat org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:93)\r\n\tat org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:71)\r\n\tat org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:39)\r\n\tat org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:204)\r\n\tat org.jetbrains.kotlin.cli.common.CLITool$Companion.doMain(CLITool.kt:196)\r\n\tat org.jetbrains.kotlin.cli.jvm.K2JVMCompiler$Companion.main(K2JVMCompiler.kt:348)\r\n\tat org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.main(K2JVMCompiler.kt)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.jetbrains.kotlin.preloading.Preloader.run(Preloader.java:81)\r\n\tat org.jetbrains.kotlin.preloading.Preloader.main(Preloader.java:43)", + "message": "The operation failed" + } + }, + "login-request": { + "title": "Root Type for source-login-request", + "description": "A login request.", + "required": [ + "password", + "username" + ], + "type": "object", + "properties": { + "username": { + "description": "The username to login with.", + "type": "string" + }, + "password": { + "format": "password", + "description": "The password to login with.", + "type": "string" + } + }, + "example": { + "username": "nulldev", + "password": "hunter7" + } + }, + "tracking-service": { + "description": "A tracking service.", + "required": [ + "id", + "possibleStatuses", + "possibleScores", + "loggedIn", + "name", + "themeColor" + ], + "type": "object", + "properties": { + "id": { + "description": "The `id` of the tracking service", + "type": "integer" + }, + "possibleStatuses": { + "description": "The list of the possible statuses a user can assign to a manga.", + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking-status" + } + }, + "possibleScores": { + "description": "The list of possible scores a user can assign to a manga.", + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking-score" + } + }, + "loggedIn": { + "description": "Whether or not the user is logged into this tracking service.", + "type": "boolean" + }, + "name": { + "description": "The display name of this tracking service", + "type": "string" + }, + "themeColor": { + "description": "The background color of this tracking service's logo.\n\nFormatted in hexidecimal format: `#RRGGBB`", + "type": "string" + }, + "username": { + "description": "The username the user is logged in as in this tracking service. Only present if `loggedIn` is `true`.", + "type": "string" + }, + "oauthUrl": { + "description": "Some sources use OAuth authentication. The presence of this field indicates the client uses OAuth authentication.\n\nTo log a user into an OAuth based source, the client must:\n1. Open the `oauthUrl` in a browser window. The client should **not** show the normal username/password dialog.\n2. When authentication is complete, the browser will redirect the user to a URL with the `tachiyomi` scheme (e.g. `tachiyomi://anilist-auth`).\n3. The client should intercept URLs with the `tachiyomi` scheme and use the `/api/v3/handle-url` API to transmit the URL back to the server.", + "type": "string" + } + }, + "example": { + "id": 0, + "possibleStatuses": [ + { + "id": 0, + "displayName": "Plan to read" + }, + { + "id": 1, + "displayName": "Reading" + }, + { + "id": 2, + "displayName": "Completed" + } + ], + "possibleScores": [ + { + "id": 0, + "score": 1 + }, + { + "id": 1, + "score": 2 + }, + { + "id": 2, + "score": 3 + }, + { + "id": 3, + "score": 4 + }, + { + "id": 4, + "score": 5 + } + ], + "loggedIn": true, + "name": "MyAnimeList", + "themeColor": "#2E51A2", + "username": "nulldev" + } + }, + "tracking-status": { + "description": "The status of a manga in a tracking service.", + "required": [ + "id", + "displayName" + ], + "type": "object", + "properties": { + "id": { + "description": "The `id` of the status, unique to a single tracking service.\n\n**WARNING:** Different tracking services may use the same `id` to represent different statuses.", + "type": "integer" + }, + "displayName": { + "description": "The display name of this tracking status.", + "type": "string" + } + }, + "example": { + "id": 5, + "displayName": "Plan to read" + } + }, + "tracking-score": { + "description": "A score the user can assign to a manga in a tracking service.", + "required": [ + "id", + "score" + ], + "type": "object", + "properties": { + "id": { + "description": "The `id` of the score, unique to a single tracking service.\n\n**WARNING:** Different tracking services may use the same `id` to represent different scores.", + "type": "integer" + }, + "score": { + "format": "float", + "description": "How much *stars* or *points* this score actually represents. This is the value that should be used when displaying a score in the UI.\n\nWhen displaying a score chooser, the client should sort the scores by this value. A higher score represents a more *positive* rating.", + "type": "number" + } + }, + "example": { + "id": 2, + "score": 1.5 + } + }, + "url-handle-result": { + "description": "The result of handling a URL with a `tachiyomi` scheme.\n\nThe `data` field will hold:\n- A JSON object of type `tracking-oauth-login` if the value of the `type` field is `TRACKING_LOGIN`", + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "description": "The type of the URL.", + "enum": [ + "TRACKING_LOGIN" + ] + }, + "data": { + "description": "Additional information about the URL.", + "type": "string" + } + } + }, + "tracking-oauth-login": { + "description": "The result of an OAuth login into a tracking service.", + "required": [ + "status", + "service" + ], + "type": "object", + "properties": { + "service": { + "description": "The `id` of the service that was logged into.", + "type": "integer" + }, + "status": { + "description": "The status of this login.", + "enum": [ + "SUCCESSFUL", + "FAILED_UNKNOWN", + "FAILED_ALREADY_LOGGED_IN" + ] + } + } + }, + "tracking-search-result": { + "description": "A single manga result when the user searches for manga in a tracking service. The client should never modify this object as it may need to be passed back to the server.", + "required": [ + "service", + "title" + ], + "type": "object", + "properties": { + "service": { + "description": "The `id` of the service providing this search result.", + "type": "integer" + }, + "extra": { + "description": "Information used by the server when this object is passed back to the server. Assume the values of this field to be opaque (the client should not attempt to parse it).\n\n*Notes for server used only (ignore if you are building a client):*\n\nContains the following `TrackSearch.kt` fields:\n- `media_id` - id of manga on tracker\n- `library_id` - id of manga in user library in tracker", + "type": "string" + }, + "title": { + "description": "The title of the manga represented by this search result.", + "type": "string" + }, + "chapters": { + "description": "The total amount of chapters in this manga.", + "type": "integer" + }, + "trackingUrl": { + "description": "The url of this manga on the tracking service.", + "type": "string" + }, + "coverUrl": { + "description": "The image URL of the cover of this manga on the tracking service.", + "type": "string" + }, + "summary": { + "description": "A description of this manga on the tracking service.", + "type": "string" + }, + "status": { + "description": "The publishing status of this manga (e.g. `Publishing` or `Completed`).", + "type": "string" + }, + "type": { + "description": "The type of this manga. (e.g. `Manga` or `Webtoon`)", + "type": "string" + }, + "startDate": { + "description": "The date this manga started publishing. Clients should not attempt to parse this date, it should be displayed directly in the UI.", + "type": "string" + } + }, + "example": { + "service": 1, + "extra": "{media_id: 1923, library_id: 8}", + "title": "Danberu nan kiro moteru?", + "chapters": 58, + "trackingUrl": "https://kitsu.io/manga/dumbbell-nan-kilo-moteru", + "coverUrl": "https://media.kitsu.io/manga/poster_images/38750/large.jpg", + "summary": "Sakura Hibiki is your average high school girl, with a voracious appetite. Noticing her clothes tightening in lieu of her slowly expanding waistline she decides to look into enrolling in the nearby gym. There she runs into a girl from her grade named Souryuuin Akemi.\r\n\r\nAkemi, who has a muscle fetish tries to get Hibiki to enroll in the gym despite its high ratio of macho men. Thankfully a beautiful trainer, Machio, appears and unknowingly convinces her to enroll and start her quest to a great body.\r\n\r\n(Source: MU)", + "status": "Publishing", + "type": "Manga", + "startDate": "Aug 5, 2016" + } + }, + "track": { + "description": "Tracking information associated with a manga.", + "required": [ + "lastChapterRead", + "totalChapters", + "status", + "service" + ], + "type": "object", + "properties": { + "lastChapterRead": { + "description": "The last read chapter. `0` indicates that the user has not read any chapters yet.", + "type": "integer" + }, + "totalChapters": { + "description": "The total number of chapters in this manga. `0` or any negative number indicates that it is unknown.", + "type": "integer" + }, + "score": { + "description": "The id of the score the user has assigned to this manga.\n\nThe possible scores are provided in the `possibleScores` field of the tracking service.\n\nWhen not present, indicates the user has not yet assigned a score to this manga.", + "type": "integer" + }, + "displayScore": { + "description": "The string the score the user assigned to this manga as should be displayed as on the UI.\n\nFor example, some sources display scores as happy/sad.\n\nWhen not present, indicates the user has not yet assigned a score to this manga.", + "type": "string" + }, + "status": { + "description": "The id of the status the user has assigned to this manga.\n\nThe possible statuses are available in the `possibleStatuses` field in the tracking service.", + "type": "integer" + }, + "trackingUrl": { + "description": "The url of this manga on the tracking service.", + "type": "string" + }, + "service": { + "description": "The `id` of the tracking service that this track is connected to.", + "type": "integer" + } + }, + "example": { + "lastChapterRead": 3, + "totalChapters": 58, + "score": 2, + "displayScore": "😊", + "status": 0, + "trackingUrl": "https://anilist.co/manga/97626/Dumbbell-Nan-Kilo-Moteru" + } + }, + "mutate-track-request": { + "description": "A request to change tracking data.", + "required": [ + "lastChapterRead", + "status" + ], + "type": "object", + "properties": { + "lastChapterRead": { + "description": "The last chapter read.\n\n`0` indicates that the user has not read any chapters yet.", + "type": "integer" + }, + "score": { + "description": "The `id` of the score the user wishes to assign to this manga.\n\nThe possible scores are provided in the `possibleScores` field of the tracking service.\n\nLeave as `null` to assign no score to this manga.", + "type": "integer" + }, + "status": { + "description": "The `id` of the status the user wishes to assign to this manga.\n\nThe possible statuses are available in the `possibleStatuses` field in the tracking service.", + "type": "integer" + } + }, + "example": { + "lastChapterRead": 5, + "score": 3, + "status": 0 + } + }, + "catalogue-filters": { + "format": "binary", + "description": "The filters for a source.\n\nThere are no OpenAPI docs for this data type as it is too difficult to express.", + "type": "string", + "example": "[ // List of filters (MUST be rendered in order on the page)\n\t{\n\t\t\"_cmaps\": { // Data on what Java types each JSON field is for internal use. \n // DO NOT USE THIS as it can be removed/changed at any time\n // without warning.\n // This data MUST NOT be modified and MUST be retained when calling the search API with the filters JSON.\n\t\t\t\"name\": \"java.lang.String\" // Example: The 'name' field is a Java String type\n\t\t},\n\t\t\"name\": \"Lorem ipsum\", // The name of the header\n\t\t\"_type\": \"HEADER\" // The type of the filter\n // The HEADER filter is a single line of non-editable text used to provide information to the user\n\t},\n\t{\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\"\n\t\t},\n\t\t\"name\": \"\", // Should be ignored in separator filters\n\t\t\"_type\": \"SEPARATOR\" // The SEPARATOR filter is a horizontal line used to divide two sections of filters\n\t},\n\t{\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\",\n\t\t\t\"state\": \"java.lang.Boolean\"\n\t\t},\n\t\t\"name\": \"Show R-18\", // The name of the filter\n\t\t\"state\": true, // The initial state of the filter\n\t\t\"_type\": \"CHECKBOX\" // The CHECKBOX filter is a two-state, toggleable filter\n\t},\n\t{\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\",\n\t\t\t\"state\": \"java.lang.String\"\n\t\t},\n\t\t\"name\": \"Author\", // The name of the filter\n\t\t\"state\": \"\", // The initial state of the filter\n\t\t\"_type\": \"TEXT\" // The TEXT filter is a single-line, editable text filter\n\t},\n\t{\n\t\t\"values\": [ // All possible values of the filter\n\t\t\t\"All\",\n\t\t\t\"Japanese Manga\",\n\t\t\t\"Korean Manhwa\",\n\t\t\t\"Chinese Manhua\"\n\t\t],\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\",\n\t\t\t\"state\": \"java.lang.Integer\"\n\t\t},\n\t\t\"name\": \"Type\", // The name of the filter\n\t\t\"state\": 0, // The initial state of the filter\n\t\t\"_type\": \"SELECT\" // The SELECT filter is a drop-down, single-selection filter\n // One (no more, no less) item can be selected at a time\n // It's state is the index of the selected item (0-indexed)\n\t},\n\t{\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\",\n\t\t\t\"state\": \"java.lang.Integer\"\n\t\t},\n\t\t\"name\": \"Completed\", // The name of the filter\n\t\t\"state\": 0, // The initial state of the filter\n\t\t\"_type\": \"TRISTATE\" // The TRISTATE filter is a triple-state filter\n // Users can cycle through the three states:\n // +-------+---------+\n // | Index | State |\n // +-------+---------+\n // | 0 | IGNORE |\n // | 1 | INCLUDE |\n // | 2 | EXCLUDE |\n // +-------+---------+\n\t},\n\t{\n\t\t\"state\": [\n\t\t\t{\n\t\t\t\t\"_cmaps\": {\n\t\t\t\t\t\"name\": \"java.lang.String\",\n\t\t\t\t\t\"state\": \"java.lang.Integer\"\n\t\t\t\t},\n\t\t\t\t\"name\": \"Action\",\n\t\t\t\t\"state\": 0,\n\t\t\t\t\"_type\": \"TRISTATE\"\n\t\t\t}\n\t\t],\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\"\n\t\t},\n\t\t\"name\": \"Genres\", // The name of the group\n\t\t\"_type\": \"GROUP\" // The GROUP filter is used to group multiple filters together\n // Although nested groups are currently not used, they may be supported in the future\n\t},\n\t{\n\t\t\"values\": [ // All the different attributes we can sort by\n\t\t\t\"Series name\",\n\t\t\t\"Rating\",\n\t\t\t\"Views\",\n\t\t\t\"Total chapters\",\n\t\t\t\"Last chapter\"\n\t\t],\n\t\t\"state\": { // The initial state of the filter\n\t\t\t\"index\": 2, // The current attribute we are sorting by\n\t\t\t\"ascending\": false // Are we sorting the attribute ascending or descending?\n\t\t},\n\t\t\"_cmaps\": {\n\t\t\t\"name\": \"java.lang.String\"\n\t\t},\n\t\t\"name\": \"Order by\", // The name of the filters\n\t\t\"_type\": \"SORT\" // The SORT filters allows the user to sort by one (no more, no less) specific attribute\n // The attribute can be sorted ascending or descending\n\t}\n]" + }, + "preference": { + "title": "Root Type for preference", + "description": "A single preference.", + "required": [ + "key", + "value" + ], + "type": "object", + "properties": { + "key": { + "description": "A unique key for the preference.", + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "example": { + "key": "pref_reader_theme_key", + "value": "white" + } + }, + "mutate-preference-request": { + "description": "A request to mutate a preference.", + "required": [ + ], + "type": "object", + "properties": { + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "The new value of the preference." + } + } + }, + "library-manga": { + "description": "A wrapper around `manga` containing more useful information.", + "required": [ + "totalUnread", + "manga" + ], + "type": "object", + "properties": { + "manga": { + "$ref": "#/components/schemas/manga" + }, + "totalUnread": { + "description": "The amount of unread chapters in this manga.", + "type": "integer" + }, + "totalDownloaded": { + "description": "The amount of downloaded chapters in this manga.\n\nWill only be present in some cases. Refer to the specific endpoint for more information.", + "type": "integer" + }, + "lastReadIndex": { + "description": "The index this manga would have if it was in a list of all the library manga sorted by the time the manga was last read (most recent to least recent).\n\nWill only be present in some cases. Refer to the specific endpoint for more information. May also not be present if the manga was never read.", + "type": "integer" + }, + "totalChaptersIndex": { + "description": "The index this manga would have if it was in a list of all the library manga sorted by the total amount of chapters the manga had (least chapters to most chapters).\n\nWill only be present in some cases. Refer to the specific endpoint for more information. Can also be `null` if the amount of chapters a manga has is unknown.", + "type": "integer" + } + } + }, + "sort-direction": { + "description": "An enum representing sort direction.", + "enum": [ + "ASC", + "DESC" + ] + }, + "auth-token": { + "description": "An authentication token.", + "required": [ + "id", + "deviceName", + "deviceId", + "platform" + ], + "type": "object", + "properties": { + "id": { + "description": "The identifier of the auth token. \nNo two auth tokens will have the same authentication (even across multiple users).\n\nIdentifiers follow the ULID specification: https://github.com/ulid/spec.\nThis means that you can extract the time this token was generated out of the identifer as ULIDs contain a timestamp.", + "type": "string" + }, + "secret": { + "description": "The authentication secret.\n\nWill only be present when the auth token is generated (and not when listing auth tokens).\n\nThis value is opaque, clients should not attempt to parse this value.", + "type": "string" + }, + "deviceName": { + "description": "The name of the device that generated this auth token.", + "type": "string" + }, + "deviceId": { + "description": "A unique identifier for this device.\n\nCan be opaque, the server will not parse this value.", + "type": "string" + }, + "platform": { + "description": "The platform of the device that is generating this auth token.", + "enum": [ + "ANDROID", + "IOS", + "WEB", + "WINDOWS", + "LINUX", + "MAC_OS" + ] + } + }, + "example": { + "deviceId": "ba050454-c7aa-491a-8bf6-cddef657c03f", + "deviceName": "Joe Schmoe's Samsung Galaxy S10", + "id": "0123456789ABCDEFGHJKMNPQRSTVWXYZ", + "platform": "ANDROID", + "secret": "96ca77e9412cc7c5a6bef55133ed4345e03fb3f1a13933a0e5b61804595ff61b8dba7d15418d26b897aecc54d02b99c039f985749a929f0d84cef82fe5696c15" + } + }, + "sync-request": { + "description": "A sync request.", + "required": [ + "version", + "events" + ], + "type": "object", + "properties": { + "version": { + "description": "The sync specification version the client implements.\n\nNote that this number is different from the API version.", + "type": "integer" + }, + "events": { + "description": "The sync events, in order from most recent to least recent.\n\nThe ordering of the events is used to order events that occur in the same millisecond (should rarely be needed).", + "type": "array", + "items": { + "$ref": "#/components/schemas/sync-event" + } + } + }, + "example": { + "version": 1, + "events": [ + { + "t": "EDIT_CATEGORY", + "r": 1554194924789, + "name": "completed", + "order": 2 + }, + { + "t": "EDIT_CATEGORY", + "r": 1554194924789, + "name": "dropped", + "order": 1 + }, + { + "t": "CREATE_CATEGORY", + "r": 1554194103643, + "name": "dropped", + "order": 2, + "updateInterval": 1176 + } + ] + } + }, + "sync-event": { + "description": "A sync event.\n\nSync events may have more fields that just the ones specified here.", + "required": [ + "r", + "t" + ], + "type": "object", + "properties": { + "t": { + "description": "The type of this event.", + "type": "string" + }, + "r": { + "format": "int64", + "description": "The time in milliseconds since epoch this sync event was recorded.", + "type": "integer" + } + }, + "example": { + "t": "CREATE_CATEGORY", + "r": 1554194103643, + "name": "dropped", + "order": 2, + "updateInterval": 1176 + } + }, + "sync-response": { + "description": "A sync response.", + "required": [ + "events" + ], + "type": "object", + "properties": { + "events": { + "description": "The events the client should apply.", + "type": "array", + "items": { + "$ref": "#/components/schemas/sync-event" + } + } + }, + "example": { + "events": [ + { + "t": "CREATE_MANGA", + "r": 1554195011312, + "sourceId": 1207429308912, + "key": "/title/17709/kumo-desu-ga-nani-ka", + "title": "Kumo Desu ga, Nani ka?", + "cover": "https://mangadex.org/images/manga/17709.jpg?1547694253" + } + ] + } + }, + "create-auth-token-request": { + "description": "A request to create an authentication token.", + "required": [ + "deviceId", + "deviceName", + "platform" + ], + "type": "object", + "properties": { + "deviceId": { + "description": "A unique identifier for this device.\n\nCan be opaque, the server will not parse this value.", + "type": "string" + }, + "deviceName": { + "description": "The name of the device that generated this auth token.", + "type": "string" + }, + "platform": { + "description": "The platform of the device that is generating this auth token.", + "enum": [ + "ANDROID", + "IOS", + "WEB", + "WINDOWS", + "LINUX", + "MAC_OS" + ] + } + }, + "example": { + "deviceId": "ba050454-c7aa-491a-8bf6-cddef657c03f", + "deviceName": "Joe Schmoe's Samsung Galaxy S10", + "platform": "ANDROID" + } + }, + "batch-mutate-category-request": { + "description": "A request to create/edit a category, used in the category batch endpoints.", + "required": [ + "id" + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the category.\n\nMust be unique across all categories (case-insensitive, two categories are considered the same if their only difference is different casing).", + "type": "string" + }, + "order": { + "format": "int32", + "description": "Categories are sorted ascending by this number.\n\nMust be unique across all categories.\n\nDefaults to the `maximum order of all existing categories + 1`.", + "type": "integer" + }, + "id": { + "format": "int32", + "description": "The id of the category to edit.", + "type": "integer" + } + }, + "example": { + "id": 1, + "name": "Dropped", + "order": 1 + } + } + }, + "securitySchemes": { + "account": { + "scheme": "basic", + "type": "http", + "description": "Username/password authentication using HTTP BASIC." + }, + "account-cookie": { + "type": "apiKey", + "description": "Provide the credentials in the following format: `{USERNAME} {BASE64 ENCODED PASSWORD}`.", + "name": "TW-Auth-Account", + "in": "cookie" + }, + "auth-token-header": { + "type": "apiKey", + "description": "Authenticate using an auth token secret obtained from `POST /api/v3/auth/tokens` in a request header.", + "name": "TW-Auth-Token", + "in": "header" + }, + "auth-token-cookie": { + "type": "apiKey", + "description": "Authenticate using an auth token secret obtained from `POST /api/v3/auth/tokens` in a cookie.", + "name": "TW-Auth-Token", + "in": "cookie" + } + } + }, + "security": [ + { + "account": [ + ] + }, + { + "account-cookie": [ + ] + }, + { + "auth-token-header": [ + ] + }, + { + "auth-token-cookie": [ + ] + } + ] +} \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/resources/pref-schema.json b/AndroidCompat/Config/src/main/resources/pref-schema.json new file mode 100644 index 00000000..8429ba4f --- /dev/null +++ b/AndroidCompat/Config/src/main/resources/pref-schema.json @@ -0,0 +1,22 @@ +[ + { + "label": "Sync", + "icon": "import_export", + "type": "nested", + "prefs": [] + }, + { + "label": "Server", + "icon": "dns", + "type": "nested", + "prefs": [ + { + "label": "Password authentication", + "type": "text-password", + "default": "", + "key": "pref_ts_server_password", + "hint": "Enter a password" + } + ] + } +] \ No newline at end of file diff --git a/AndroidCompat/Config/src/main/resources/reference.conf b/AndroidCompat/Config/src/main/resources/reference.conf new file mode 100644 index 00000000..19908015 --- /dev/null +++ b/AndroidCompat/Config/src/main/resources/reference.conf @@ -0,0 +1,87 @@ +# Server ip and port bindings +ts.server.ip = 0.0.0.0 +ts.server.port = 4567 + +# Allow/disallow preference changes (useful for demos) +ts.server.allowConfigChanges = true + +# Enable the WebUI? Note: The API and multi-user sync server ui will remain available even if the WebUI is disabled +ts.server.enableWebUi = true + +# 'true' to use the old, buggy/memory-leaking WebUI +ts.server.useOldWebUi = false + +# 'true' to pretty print all JSON API responses +ts.server.prettyPrintApi = false + +# List of blacklisted/whitelisted API endpoints/operation IDs +ts.server.disabledApiEndpoints = [] +ts.server.enabledApiEndpoints = [] + +# Message to print in the console when the API has finished booting +ts.server.httpInitializedPrintMessage = "" + +# Use external folder for static files +ts.server.useExternalStaticFiles = false +ts.server.externalStaticFilesFolder = "" + +# Root storage dir +ts.server.rootDir = tachiserver-data +# Dir to store JVM patches +ts.server.patchesDir = ${ts.server.rootDir}/patches + +# Storage dir for the emulated Android app +android.files.rootDir = ${ts.server.rootDir}/android-compat/appdata +# External storage dir for the emulated Android app's +android.files.externalStorageDir = ${ts.server.rootDir}/android-compat/extappdata + +# Internal Android directories +android.files.dataDir = ${android.files.rootDir}/data +android.files.filesDir = ${android.files.rootDir}/files +android.files.cacheDir = ${android.files.rootDir}/cache +android.files.codeCacheDir = ${android.files.rootDir}/code_cache +android.files.noBackupFilesDir = ${android.files.rootDir}/no_backup +android.files.databasesDir = ${android.files.rootDir}/databases +android.files.prefsDir = ${android.files.rootDir}/shared_prefs + +# External Android directories +android.files.externalFilesDirs = [${android.files.externalStorageDir}/files] +android.files.obbDirs = [${android.files.externalStorageDir}/obb] +android.files.externalCacheDirs = [${android.files.externalStorageDir}/cache] +android.files.externalMediaDirs = [${android.files.externalStorageDir}/media] +android.files.downloadCacheDir = ${android.files.externalStorageDir}/downloadCache + +android.files.packageDir = ${ts.server.rootDir}/android-compat/packages + +# Emulated Android app package name +android.app.packageName = eu.kanade.tachiyomi +# Debug mode for the emulated Android app +android.app.debug = true + +# Whether or not the emulated Android system is debuggable +android.system.isDebuggable = true + +# Is the multi-user sync server enabled? Does not affect the single-user sync server included in the API. +ts.syncd.enable = false + +# The URL of this server (displayed in the sync server web ui) +ts.syncd.baseUrl = "http://example.com" + +# 'true' to disable the API and only enable the multi-user sync server +ts.syncd.syncOnlyMode = false + +# The root directory to store synchronized data +ts.syncd.rootDir = ${ts.server.rootDir}/sync/accounts + +# Location to store config files for the sandbox +ts.syncd.sandboxedConfig = ${ts.server.rootDir}/sync/sandboxed_config.config + +# Recaptcha stuff for signup/login +ts.syncd.recaptcha.siteKey = "" +ts.syncd.recaptcha.secret = "" + +# Sync server display name +ts.syncd.name = "Tachiyomi sync server" + +# Header used to forward the IP to the multi-user sync server if the server is behind a reverse proxy +ts.syncd.ipHeader = "" diff --git a/AndroidCompat/build.gradle.kts b/AndroidCompat/build.gradle.kts new file mode 100644 index 00000000..d728678d --- /dev/null +++ b/AndroidCompat/build.gradle.kts @@ -0,0 +1,72 @@ + +plugins { + application +} + + +repositories { + mavenCentral() + jcenter() + maven { + url = uri("https://jitpack.io") + } + + maven { + url = uri("https://maven.google.com") + } +} + +dependencies { + // Android stub library +// compileOnly( fileTree(File(rootProject.rootDir, "libs/android"), include: "*.jar") + implementation(fileTree("lib/")) + implementation(fileTree("${rootProject.rootDir}/server/lib/dex2jar/")) + + + // Android JAR libs +// compileOnly( fileTree(dir: new File(rootProject.rootDir, "libs/other"), include: "*.jar") + + // JSON + compileOnly( "com.google.code.gson:gson:2.8.6") + + // Javassist + compileOnly( "org.javassist:javassist:3.27.0-GA") + + // Coroutines + val kotlinx_coroutines_version = "1.4.2" + compileOnly( "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version") + compileOnly( "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlinx_coroutines_version") + + // XML + compileOnly( group= "xmlpull", name= "xmlpull", version= "1.1.3.1") + + // Config API + implementation( project(":AndroidCompat:Config")) + + // dex2jar +// compileOnly( "dex2jar:dex-translator") + + // APK parser + compileOnly("net.dongliu:apk-parser:2.6.10") + + // APK sig verifier + compileOnly("com.android.tools.build:apksig:4.2.0-alpha13") + + // AndroidX annotations + compileOnly( "androidx.annotation:annotation:1.2.0-alpha01") + +// compileOnly("io.reactivex:rxjava:1.3.8") +} + +//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar') +// +//// Copy JVM core patches +//task copyJVMPatches(type: Copy) { +// from fatJarTask.outputs.files +// into 'src/main/resources/patches' +//} +// +//compileOnly(Java.dependsOn gradle.includedBuild('dex2jar').task(':dex-translator:assemble') +//compileOnly(Java.dependsOn copyJVMPatches +//copyJVMPatches.dependsOn fatJarTask +// diff --git a/AndroidCompat/lib/.gitignore b/AndroidCompat/lib/.gitignore new file mode 100644 index 00000000..545576c5 --- /dev/null +++ b/AndroidCompat/lib/.gitignore @@ -0,0 +1 @@ +android.jar \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/AnimRes.java b/AndroidCompat/src/main/java/android/annotation/AnimRes.java new file mode 100644 index 00000000..acd112b7 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/AnimRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AnimRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/AnimatorRes.java b/AndroidCompat/src/main/java/android/annotation/AnimatorRes.java new file mode 100644 index 00000000..2da04a95 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/AnimatorRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AnimatorRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/AnyRes.java b/AndroidCompat/src/main/java/android/annotation/AnyRes.java new file mode 100644 index 00000000..d44108a6 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/AnyRes.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a resource reference of any type. If the specific type is known, use + * one of the more specific annotations instead, such as {@link StringRes} or + * {@link DrawableRes}. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AnyRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/AppIdInt.java b/AndroidCompat/src/main/java/android/annotation/AppIdInt.java new file mode 100644 index 00000000..dfc89c0c --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/AppIdInt.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element is a multi-user application ID. This is + * not the same as a UID. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AppIdInt { +} diff --git a/AndroidCompat/src/main/java/android/annotation/ArrayRes.java b/AndroidCompat/src/main/java/android/annotation/ArrayRes.java new file mode 100644 index 00000000..3724da0c --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/ArrayRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface ArrayRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/AttrRes.java b/AndroidCompat/src/main/java/android/annotation/AttrRes.java new file mode 100644 index 00000000..45e3ee0b --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/AttrRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an attribute reference (e.g. {@link android.R.attr#action}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AttrRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/BinderThread.java b/AndroidCompat/src/main/java/android/annotation/BinderThread.java new file mode 100644 index 00000000..ceec2b3f --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/BinderThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on the binder thread. + * If the annotated element is a class, then all methods in the class should be called + * on the binder thread. + *

+ * Example: + *


+ *  @BinderThread
+ *  public BeamShareData createBeamShareData() { ... }
+ * 
+ * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface BinderThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/BoolRes.java b/AndroidCompat/src/main/java/android/annotation/BoolRes.java new file mode 100644 index 00000000..f5409ea0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/BoolRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a boolean resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface BoolRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/CallSuper.java b/AndroidCompat/src/main/java/android/annotation/CallSuper.java new file mode 100644 index 00000000..b10a28ac --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/CallSuper.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that any overriding methods should invoke this method as well. + *

+ * Example: + *


+ *  @CallSuper
+ *  public abstract void onFocusLost();
+ * 
+ * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD}) +public @interface CallSuper { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/CheckResult.java b/AndroidCompat/src/main/java/android/annotation/CheckResult.java new file mode 100644 index 00000000..97d031a7 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/CheckResult.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method returns a result that it typically is + * an error to ignore. This is usually used for methods that have no side effect, + * so calling it without actually looking at the result usually means the developer + * has misunderstood what the method does. + *

+ * Example: + *

{@code
+ *  public @CheckResult String trim(String s) { return s.trim(); }
+ *  ...
+ *  s.trim(); // this is probably an error
+ *  s = s.trim(); // ok
+ * }
+ * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD}) +public @interface CheckResult { + /** Defines the name of the suggested method to use instead, if applicable (using + * the same signature format as javadoc.) If there is more than one possibility, + * list them all separated by commas. + *

+ * For example, ProcessBuilder has a method named {@code redirectErrorStream()} + * which sounds like it might redirect the error stream. It does not. It's just + * a getter which returns whether the process builder will redirect the error stream, + * and to actually set it, you must call {@code redirectErrorStream(boolean)}. + * In that case, the method should be defined like this: + *

+     *  @CheckResult(suggest="#redirectErrorStream(boolean)")
+     *  public boolean redirectErrorStream() { ... }
+     * 
+ */ + String suggest() default ""; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/ColorInt.java b/AndroidCompat/src/main/java/android/annotation/ColorInt.java new file mode 100644 index 00000000..f6e59bc1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/ColorInt.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element represents a packed color + * int, {@code AARRGGBB}. If applied to an int array, every element + * in the array represents a color integer. + *

+ * Example: + *

{@code
+ *  public abstract void setTextColor(@ColorInt int color);
+ * }
+ * + * @hide + */ +@Retention(SOURCE) +@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD}) +public @interface ColorInt { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/ColorRes.java b/AndroidCompat/src/main/java/android/annotation/ColorRes.java new file mode 100644 index 00000000..a6c772b9 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/ColorRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a color resource reference (e.g. {@link android.R.color#black}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface ColorRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/DimenRes.java b/AndroidCompat/src/main/java/android/annotation/DimenRes.java new file mode 100644 index 00000000..d840a93c --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/DimenRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface DimenRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/DrawableRes.java b/AndroidCompat/src/main/java/android/annotation/DrawableRes.java new file mode 100644 index 00000000..926aa284 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/DrawableRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface DrawableRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/FloatRange.java b/AndroidCompat/src/main/java/android/annotation/FloatRange.java new file mode 100644 index 00000000..20216b2a --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/FloatRange.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element should be a float or double in the given range + *

+ * Example: + *


+ *  @FloatRange(from=0.0,to=1.0)
+ *  public float getAlpha() {
+ *      ...
+ *  }
+ * 
+ * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE}) +public @interface FloatRange { + /** Smallest value. Whether it is inclusive or not is determined + * by {@link #fromInclusive} */ + double from() default Double.NEGATIVE_INFINITY; + /** Largest value. Whether it is inclusive or not is determined + * by {@link #toInclusive} */ + double to() default Double.POSITIVE_INFINITY; + + /** Whether the from value is included in the range */ + boolean fromInclusive() default true; + + /** Whether the to value is included in the range */ + boolean toInclusive() default true; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/FractionRes.java b/AndroidCompat/src/main/java/android/annotation/FractionRes.java new file mode 100644 index 00000000..3779c2e8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/FractionRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a fraction resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface FractionRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/IdRes.java b/AndroidCompat/src/main/java/android/annotation/IdRes.java new file mode 100644 index 00000000..91c9b9ad --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/IdRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an id resource reference (e.g. {@link android.R.id#copy}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface IdRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/IntDef.java b/AndroidCompat/src/main/java/android/annotation/IntDef.java new file mode 100644 index 00000000..6cab43c3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/IntDef.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 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 android.annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; +/** + * Denotes that the annotated element of integer type, represents + * a logical type and that its value should be one of the explicitly + * named constants. If the {@link #flag()} attribute is set to true, + * multiple constants can be combined. + *

+ *


+ *  @Retention(SOURCE)
+ *  @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
+ *  public static final int NAVIGATION_MODE_STANDARD = 0;
+ *  public static final int NAVIGATION_MODE_LIST = 1;
+ *  public static final int NAVIGATION_MODE_TABS = 2;
+ *  ...
+ *  public abstract void setNavigationMode(@NavigationMode int mode);
+ *  @NavigationMode
+ *  public abstract int getNavigationMode();
+ * 
+ * For a flag, set the flag attribute: + *

+ *  @IntDef(
+ *      flag = true,
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * 
+ * + * @hide + */ +@Retention(SOURCE) +@Target({ANNOTATION_TYPE}) +public @interface IntDef { + /** Defines the constant prefix for this element */ + String[] prefix() default ""; + /** Defines the allowed constants for this element */ + long[] value() default {}; + /** Defines whether the constants can be used as a flag, or just as an enum (the default) */ + boolean flag() default false; +} diff --git a/AndroidCompat/src/main/java/android/annotation/IntRange.java b/AndroidCompat/src/main/java/android/annotation/IntRange.java new file mode 100644 index 00000000..e9f09801 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/IntRange.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element should be an int or long in the given range + *

+ * Example: + *


+ *  @IntRange(from=0,to=255)
+ *  public int getAlpha() {
+ *      ...
+ *  }
+ * 
+ * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) +public @interface IntRange { + /** Smallest value, inclusive */ + long from() default Long.MIN_VALUE; + /** Largest value, inclusive */ + long to() default Long.MAX_VALUE; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/IntegerRes.java b/AndroidCompat/src/main/java/android/annotation/IntegerRes.java new file mode 100644 index 00000000..59099b5a --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/IntegerRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface IntegerRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/InterpolatorRes.java b/AndroidCompat/src/main/java/android/annotation/InterpolatorRes.java new file mode 100644 index 00000000..e441c38d --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/InterpolatorRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface InterpolatorRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/LayoutRes.java b/AndroidCompat/src/main/java/android/annotation/LayoutRes.java new file mode 100644 index 00000000..7e018099 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/LayoutRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a layout resource reference (e.g. {@link android.R.layout#list_content}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface LayoutRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/MainThread.java b/AndroidCompat/src/main/java/android/annotation/MainThread.java new file mode 100644 index 00000000..f0463ff3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/MainThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on the main thread. + * If the annotated element is a class, then all methods in the class should be called + * on the main thread. + *

+ * Example: + *


+ *  @MainThread
+ *  public void deliverResult(D data) { ... }
+ * 
+ * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface MainThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/MenuRes.java b/AndroidCompat/src/main/java/android/annotation/MenuRes.java new file mode 100644 index 00000000..534dfaba --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/MenuRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a menu resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface MenuRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/NonNull.java b/AndroidCompat/src/main/java/android/annotation/NonNull.java new file mode 100644 index 00000000..0ff40398 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/NonNull.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a parameter, field or method return value can never be null. + *

+ * This is a marker annotation and it has no specific attributes. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface NonNull { +} diff --git a/AndroidCompat/src/main/java/android/annotation/Nullable.java b/AndroidCompat/src/main/java/android/annotation/Nullable.java new file mode 100644 index 00000000..5d258141 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/Nullable.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a parameter, field or method return value can be null. + *

+ * When decorating a method call parameter, this denotes that the parameter can + * legitimately be null and the method will gracefully deal with it. Typically + * used on optional parameters. + *

+ * When decorating a method, this denotes the method might legitimately return + * null. + *

+ * This is a marker annotation and it has no specific attributes. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface Nullable { +} diff --git a/AndroidCompat/src/main/java/android/annotation/PluralsRes.java b/AndroidCompat/src/main/java/android/annotation/PluralsRes.java new file mode 100644 index 00000000..d98a9f91 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/PluralsRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a plurals resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface PluralsRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/RawRes.java b/AndroidCompat/src/main/java/android/annotation/RawRes.java new file mode 100644 index 00000000..0db9b31d --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/RawRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a raw resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface RawRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/RequiresPermission.java b/AndroidCompat/src/main/java/android/annotation/RequiresPermission.java new file mode 100644 index 00000000..f74ee1e8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/RequiresPermission.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element requires (or may require) one or more permissions. + *

+ * Example of requiring a single permission: + *

{@code
+ *   {@literal @}RequiresPermission(Manifest.permission.SET_WALLPAPER)
+ *   public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+ *
+ *   {@literal @}RequiresPermission(ACCESS_COARSE_LOCATION)
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }
+ * Example of requiring at least one permission from a set: + *
{@code
+ *   {@literal @}RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }
+ * Example of requiring multiple permissions: + *
{@code
+ *   {@literal @}RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }
+ * Example of requiring separate read and write permissions for a content provider: + *
{@code
+ *   {@literal @}RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ *   {@literal @}RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ *   public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * }
+ *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter. For example, consider + * {@link android.app.Activity#startActivity(Intent)}: + *

{@code
+ *   public void startActivity(@RequiresPermission Intent intent) { ... }
+ * }
+ * Notice how there are no actual permission names listed in the annotation. The actual + * permissions required will depend on the particular intent passed in. For example, + * the code may look like this: + *
{@code
+ *   Intent intent = new Intent(Intent.ACTION_CALL);
+ *   startActivity(intent);
+ * }
+ * and the actual permission requirement for this particular intent is described on + * the Intent name itself: + *
{@code
+ *   {@literal @}RequiresPermission(Manifest.permission.CALL_PHONE)
+ *   public static final String ACTION_CALL = "android.intent.action.CALL";
+ * }
+ * + * @hide + */ +@Retention(SOURCE) +@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER}) +public @interface RequiresPermission { + /** + * The name of the permission that is required, if precisely one permission + * is required. If more than one permission is required, specify either + * {@link #allOf()} or {@link #anyOf()} instead. + *

+ * If specified, {@link #anyOf()} and {@link #allOf()} must both be null. + */ + String value() default ""; + + /** + * Specifies a list of permission names that are all required. + *

+ * If specified, {@link #anyOf()} and {@link #value()} must both be null. + */ + String[] allOf() default {}; + + /** + * Specifies a list of permission names where at least one is required + *

+ * If specified, {@link #allOf()} and {@link #value()} must both be null. + */ + String[] anyOf() default {}; + + /** + * If true, the permission may not be required in all cases (e.g. it may only be + * enforced on certain platforms, or for certain call parameters, etc. + */ + boolean conditional() default false; + + /** + * Specifies that the given permission is required for read operations. + *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter (and typically + * the corresponding field passed in will be one of a set of constants which have + * been annotated with a @RequiresPermission annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Read { + RequiresPermission value() default @RequiresPermission; + } + + /** + * Specifies that the given permission is required for write operations. + *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter (and typically + * the corresponding field passed in will be one of a set of constants which have + * been annotated with a @RequiresPermission annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Write { + RequiresPermission value() default @RequiresPermission; + } +} diff --git a/AndroidCompat/src/main/java/android/annotation/SdkConstant.java b/AndroidCompat/src/main/java/android/annotation/SdkConstant.java new file mode 100644 index 00000000..bce8ca0e --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/SdkConstant.java @@ -0,0 +1,36 @@ +/* + * 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 android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a constant field value should be exported to be used in the SDK tools. + * @hide + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.SOURCE) +public @interface SdkConstant { + public static enum SdkConstantType { + ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE; + } + + SdkConstantType value(); +} diff --git a/AndroidCompat/src/main/java/android/annotation/Size.java b/AndroidCompat/src/main/java/android/annotation/Size.java new file mode 100644 index 00000000..903805cc --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/Size.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element should have a given size or length. + * Note that "-1" means "unset". Typically used with a parameter or + * return value of type array or collection. + *

+ * Example: + *

{@code
+ *  public void getLocationInWindow(@Size(2) int[] location) {
+ *      ...
+ *  }
+ * }
+ * + * @hide + */ +@Retention(SOURCE) +@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD}) +public @interface Size { + /** An exact size (or -1 if not specified) */ + long value() default -1; + /** A minimum size, inclusive */ + long min() default Long.MIN_VALUE; + /** A maximum size, inclusive */ + long max() default Long.MAX_VALUE; + /** The size must be a multiple of this factor */ + long multiple() default 1; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/StringDef.java b/AndroidCompat/src/main/java/android/annotation/StringDef.java new file mode 100644 index 00000000..8c8d5d87 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/StringDef.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated String element, represents a logical + * type and that its value should be one of the explicitly named constants. + *

+ * Example: + *


+ *  @Retention(SOURCE)
+ *  @StringDef({
+ *     POWER_SERVICE,
+ *     WINDOW_SERVICE,
+ *     LAYOUT_INFLATER_SERVICE
+ *  })
+ *  public @interface ServiceName {}
+ *  public static final String POWER_SERVICE = "power";
+ *  public static final String WINDOW_SERVICE = "window";
+ *  public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ *  ...
+ *  public abstract Object getSystemService(@ServiceName String name);
+ * 
+ * + * @hide + */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE}) +public @interface StringDef { + /** Defines the allowed constants for this element */ + String[] value() default {}; +} diff --git a/AndroidCompat/src/main/java/android/annotation/StringRes.java b/AndroidCompat/src/main/java/android/annotation/StringRes.java new file mode 100644 index 00000000..72a21720 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/StringRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a String resource reference (e.g. {@link android.R.string#ok}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface StringRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/StyleRes.java b/AndroidCompat/src/main/java/android/annotation/StyleRes.java new file mode 100644 index 00000000..7a473500 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/StyleRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a integer parameter, field or method return value is expected + * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface StyleRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/StyleableRes.java b/AndroidCompat/src/main/java/android/annotation/StyleableRes.java new file mode 100644 index 00000000..f0de876d --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/StyleableRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that a integer parameter, field or method return value is expected + * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}). + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface StyleableRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/SuppressLint.java b/AndroidCompat/src/main/java/android/annotation/SuppressLint.java new file mode 100644 index 00000000..4920043f --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/SuppressLint.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** Indicates that Lint should ignore the specified warnings for the annotated element. */ +@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) +@Retention(RetentionPolicy.CLASS) +public @interface SuppressLint { + /** + * The set of warnings (identified by the lint issue id) that should be + * ignored by lint. It is not an error to specify an unrecognized name. + */ + String[] value(); +} diff --git a/AndroidCompat/src/main/java/android/annotation/SystemApi.java b/AndroidCompat/src/main/java/android/annotation/SystemApi.java new file mode 100644 index 00000000..3d538b32 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/SystemApi.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Indicates an API is exposed for use by bundled system applications. + *

+ * These APIs are not guaranteed to remain consistent release-to-release, + * and are not for use by apps linking against the Android SDK. + *

+ * This annotation should only appear on API that is already marked

@hide
. + *

+ * + * @hide + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) +@Retention(RetentionPolicy.SOURCE) +public @interface SystemApi { +} diff --git a/AndroidCompat/src/main/java/android/annotation/TargetApi.java b/AndroidCompat/src/main/java/android/annotation/TargetApi.java new file mode 100644 index 00000000..e33c70e0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/TargetApi.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** Indicates that Lint should treat this type as targeting a given API level, no matter what the + project target is. */ +@Target({TYPE, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface TargetApi { + /** + * This sets the target api level for the type.. + */ + int value(); +} diff --git a/AndroidCompat/src/main/java/android/annotation/TestApi.java b/AndroidCompat/src/main/java/android/annotation/TestApi.java new file mode 100644 index 00000000..63793035 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/TestApi.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Indicates an API is exposed for use by CTS. + *

+ * These APIs are not guaranteed to remain consistent release-to-release, + * and are not for use by apps linking against the Android SDK. + *

+ * + * @hide + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) +@Retention(RetentionPolicy.SOURCE) +public @interface TestApi { +} diff --git a/AndroidCompat/src/main/java/android/annotation/TransitionRes.java b/AndroidCompat/src/main/java/android/annotation/TransitionRes.java new file mode 100644 index 00000000..b20cb46a --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/TransitionRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a transition resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface TransitionRes { +} diff --git a/AndroidCompat/src/main/java/android/annotation/UiThread.java b/AndroidCompat/src/main/java/android/annotation/UiThread.java new file mode 100644 index 00000000..4da86db7 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/UiThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method or constructor should only be called on the UI thread. + * If the annotated element is a class, then all methods in the class should be called + * on the UI thread. + *

+ * Example: + *


+ *  @UiThread
+ *  public abstract void setText(@NonNull String text) { ... }
+ * 
+ * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface UiThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/UserIdInt.java b/AndroidCompat/src/main/java/android/annotation/UserIdInt.java new file mode 100644 index 00000000..27539753 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/UserIdInt.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element is a multi-user user ID. This is + * not the same as a UID. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface UserIdInt { +} diff --git a/AndroidCompat/src/main/java/android/annotation/Widget.java b/AndroidCompat/src/main/java/android/annotation/Widget.java new file mode 100644 index 00000000..d69f8826 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/Widget.java @@ -0,0 +1,37 @@ +/* + * 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 android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a class is a widget usable by application developers to create UI. + *

+ * This must be used in cases where: + *

    + *
  • The widget is not in the package android.widget
  • + *
  • The widget extends android.view.ViewGroup
  • + *
+ * @hide + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.SOURCE) +public @interface Widget { +} diff --git a/AndroidCompat/src/main/java/android/annotation/WorkerThread.java b/AndroidCompat/src/main/java/android/annotation/WorkerThread.java new file mode 100644 index 00000000..b35fd797 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/WorkerThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated method should only be called on a worker thread. + * If the annotated element is a class, then all methods in the class should be called + * on a worker thread. + *

+ * Example: + *


+ *  @WorkerThread
+ *  protected abstract FilterResults performFiltering(CharSequence constraint);
+ * 
+ * + * {@hide} + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface WorkerThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/annotation/XmlRes.java b/AndroidCompat/src/main/java/android/annotation/XmlRes.java new file mode 100644 index 00000000..f6a8e4b5 --- /dev/null +++ b/AndroidCompat/src/main/java/android/annotation/XmlRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an XML resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface XmlRes { +} diff --git a/AndroidCompat/src/main/java/android/app/Application.java b/AndroidCompat/src/main/java/android/app/Application.java new file mode 100644 index 00000000..d3287292 --- /dev/null +++ b/AndroidCompat/src/main/java/android/app/Application.java @@ -0,0 +1,284 @@ +/* + * 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 android.app; + +import android.annotation.CallSuper; +import android.content.*; +import android.content.res.Configuration; +import android.os.Bundle; + +import java.util.ArrayList; + +/** + * Base class for maintaining global application state. You can provide your own + * implementation by creating a subclass and specifying the fully-qualified name + * of this subclass as the "android:name" attribute in your + * AndroidManifest.xml's <application> tag. The Application + * class, or your subclass of the Application class, is instantiated before any + * other class when the process for your application/package is created. + * + *

Note: There is normally no need to subclass + * Application. In most situations, static singletons can provide the same + * functionality in a more modular way. If your singleton needs a global + * context (for example to register broadcast receivers), include + * {@link android.content.Context#getApplicationContext() Context.getApplicationContext()} + * as a {@link android.content.Context} argument when invoking your singleton's + * getInstance() method. + *

+ */ +public class Application extends ContextWrapper implements ComponentCallbacks2 { + private ArrayList mComponentCallbacks = + new ArrayList(); + private ArrayList mActivityLifecycleCallbacks = + new ArrayList(); + private ArrayList mAssistCallbacks = null; + + public interface ActivityLifecycleCallbacks { + void onActivityCreated(Activity activity, Bundle savedInstanceState); + void onActivityStarted(Activity activity); + void onActivityResumed(Activity activity); + void onActivityPaused(Activity activity); + void onActivityStopped(Activity activity); + void onActivitySaveInstanceState(Activity activity, Bundle outState); + void onActivityDestroyed(Activity activity); + } + + /** + * Callback interface for use with {@link Application#registerOnProvideAssistDataListener} + * and {@link Application#unregisterOnProvideAssistDataListener}. + */ + public interface OnProvideAssistDataListener { + /** + * This is called when the user is requesting an assist, to build a full + * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current + * application. You can override this method to place into the bundle anything + * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part + * of the assist Intent. + */ + public void onProvideAssistData(Activity activity, Bundle data); + } + + public Application() { + super(null); + } + + /** + * Called when the application is starting, before any activity, service, + * or receiver objects (excluding content providers) have been created. + * Implementations should be as quick as possible (for example using + * lazy initialization of state) since the time spent in this function + * directly impacts the performance of starting the first activity, + * service, or receiver in a process. + * If you override this method, be sure to call super.onCreate(). + */ + @CallSuper + public void onCreate() { + } + + /** + * This method is for use in emulated process environments. It will + * never be called on a production Android device, where processes are + * removed by simply killing them; no user code (including this callback) + * is executed when doing so. + */ + @CallSuper + public void onTerminate() { + } + + @CallSuper + public void onConfigurationChanged(Configuration newConfig) { + Object[] callbacks = collectComponentCallbacks(); + if (callbacks != null) { + for (int i=0; i(); + } + mAssistCallbacks.add(callback); + } + } + + public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) { + synchronized (this) { + if (mAssistCallbacks != null) { + mAssistCallbacks.remove(callback); + } + } + } + + // ------------------ Internal API ------------------ + + /** + * @hide + */ + public final void attach(Context context) { + attachBaseContext(context); + } + + /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i 0) { + callbacks = mComponentCallbacks.toArray(); + } + } + return callbacks; + } + + private Object[] collectActivityLifecycleCallbacks() { + Object[] callbacks = null; + synchronized (mActivityLifecycleCallbacks) { + if (mActivityLifecycleCallbacks.size() > 0) { + callbacks = mActivityLifecycleCallbacks.toArray(); + } + } + return callbacks; + } + + /* package */ void dispatchOnProvideAssistData(Activity activity, Bundle data) { + Object[] callbacks; + synchronized (this) { + if (mAssistCallbacks == null) { + return; + } + callbacks = mAssistCallbacks.toArray(); + } + if (callbacks != null) { + for (int i=0; iAndroidManifest.xml. Services + * can be started with + * {@link android.content.Context#startService Context.startService()} and + * {@link android.content.Context#bindService Context.bindService()}. + * + *

Note that services, like other application objects, run in the main + * thread of their hosting process. This means that, if your service is going + * to do any CPU intensive (such as MP3 playback) or blocking (such as + * networking) operations, it should spawn its own thread in which to do that + * work. More information on this can be found in + * Processes and + * Threads. The {@link IntentService} class is available + * as a standard implementation of Service that has its own thread where it + * schedules its work to be done.

+ * + *

Topics covered here: + *

    + *
  1. What is a Service? + *
  2. Service Lifecycle + *
  3. Permissions + *
  4. Process Lifecycle + *
  5. Local Service Sample + *
  6. Remote Messenger Service Sample + *
+ * + *
+ *

Developer Guides

+ *

For a detailed discussion about how to create services, read the + * Services developer guide.

+ *
+ * + * + *

What is a Service?

+ * + *

Most confusion about the Service class actually revolves around what + * it is not:

+ * + *
    + *
  • A Service is not a separate process. The Service object itself + * does not imply it is running in its own process; unless otherwise specified, + * it runs in the same process as the application it is part of. + *
  • A Service is not a thread. It is not a means itself to do work off + * of the main thread (to avoid Application Not Responding errors). + *
+ * + *

Thus a Service itself is actually very simple, providing two main features:

+ * + *
    + *
  • A facility for the application to tell the system about + * something it wants to be doing in the background (even when the user is not + * directly interacting with the application). This corresponds to calls to + * {@link android.content.Context#startService Context.startService()}, which + * ask the system to schedule work for the service, to be run until the service + * or someone else explicitly stop it. + *
  • A facility for an application to expose some of its functionality to + * other applications. This corresponds to calls to + * {@link android.content.Context#bindService Context.bindService()}, which + * allows a long-standing connection to be made to the service in order to + * interact with it. + *
+ * + *

When a Service component is actually created, for either of these reasons, + * all that the system actually does is instantiate the component + * and call its {@link #onCreate} and any other appropriate callbacks on the + * main thread. It is up to the Service to implement these with the appropriate + * behavior, such as creating a secondary thread in which it does its work.

+ * + *

Note that because Service itself is so simple, you can make your + * interaction with it as simple or complicated as you want: from treating it + * as a local Java object that you make direct method calls on (as illustrated + * by Local Service Sample), to providing + * a full remoteable interface using AIDL.

+ * + * + *

Service Lifecycle

+ * + *

There are two reasons that a service can be run by the system. If someone + * calls {@link android.content.Context#startService Context.startService()} then the system will + * retrieve the service (creating it and calling its {@link #onCreate} method + * if needed) and then call its {@link #onStartCommand} method with the + * arguments supplied by the client. The service will at this point continue + * running until {@link android.content.Context#stopService Context.stopService()} or + * {@link #stopSelf()} is called. Note that multiple calls to + * Context.startService() do not nest (though they do result in multiple corresponding + * calls to onStartCommand()), so no matter how many times it is started a service + * will be stopped once Context.stopService() or stopSelf() is called; however, + * services can use their {@link #stopSelf(int)} method to ensure the service is + * not stopped until started intents have been processed. + * + *

For started services, there are two additional major modes of operation + * they can decide to run in, depending on the value they return from + * onStartCommand(): {@link #START_STICKY} is used for services that are + * explicitly started and stopped as needed, while {@link #START_NOT_STICKY} + * or {@link #START_REDELIVER_INTENT} are used for services that should only + * remain running while processing any commands sent to them. See the linked + * documentation for more detail on the semantics. + * + *

Clients can also use {@link android.content.Context#bindService Context.bindService()} to + * obtain a persistent connection to a service. This likewise creates the + * service if it is not already running (calling {@link #onCreate} while + * doing so), but does not call onStartCommand(). The client will receive the + * {@link android.os.IBinder} object that the service returns from its + * {@link #onBind} method, allowing the client to then make calls back + * to the service. The service will remain running as long as the connection + * is established (whether or not the client retains a reference on the + * service's IBinder). Usually the IBinder returned is for a complex + * interface that has been written + * in aidl. + * + *

A service can be both started and have connections bound to it. In such + * a case, the system will keep the service running as long as either it is + * started or there are one or more connections to it with the + * {@link android.content.Context#BIND_AUTO_CREATE Context.BIND_AUTO_CREATE} + * flag. Once neither + * of these situations hold, the service's {@link #onDestroy} method is called + * and the service is effectively terminated. All cleanup (stopping threads, + * unregistering receivers) should be complete upon returning from onDestroy(). + * + * + *

Permissions

+ * + *

Global access to a service can be enforced when it is declared in its + * manifest's {@link android.R.styleable#AndroidManifestService <service>} + * tag. By doing so, other applications will need to declare a corresponding + * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element in their own manifest to be able to start, stop, or bind to + * the service. + * + *

As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, when using + * {@link Context#startService(Intent) Context.startService(Intent)}, you can + * also set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the + * Service temporary access to the specific URIs in the Intent. Access will + * remain until the Service has called {@link #stopSelf(int)} for that start + * command or a later one, or until the Service has been completely stopped. + * This works for granting access to the other apps that have not requested + * the permission protecting the Service, or even when the Service is not + * exported at all. + * + *

In addition, a service can protect individual IPC calls into it with + * permissions, by calling the + * {@link #checkCallingPermission} + * method before executing the implementation of that call. + * + *

See the Security and Permissions + * document for more information on permissions and security in general. + * + * + *

Process Lifecycle

+ * + *

The Android system will attempt to keep the process hosting a service + * around as long as the service has been started or has clients bound to it. + * When running low on memory and needing to kill existing processes, the + * priority of a process hosting the service will be the higher of the + * following possibilities: + * + *

    + *
  • If the service is currently executing code in its + * {@link #onCreate onCreate()}, {@link #onStartCommand onStartCommand()}, + * or {@link #onDestroy onDestroy()} methods, then the hosting process will + * be a foreground process to ensure this code can execute without + * being killed. + *

  • If the service has been started, then its hosting process is considered + * to be less important than any processes that are currently visible to the + * user on-screen, but more important than any process not visible. Because + * only a few processes are generally visible to the user, this means that + * the service should not be killed except in low memory conditions. However, since + * the user is not directly aware of a background service, in that state it is + * considered a valid candidate to kill, and you should be prepared for this to + * happen. In particular, long-running services will be increasingly likely to + * kill and are guaranteed to be killed (and restarted if appropriate) if they + * remain started long enough. + *

  • If there are clients bound to the service, then the service's hosting + * process is never less important than the most important client. That is, + * if one of its clients is visible to the user, then the service itself is + * considered to be visible. The way a client's importance impacts the service's + * importance can be adjusted through {@link Context#BIND_ABOVE_CLIENT}, + * {@link Context#BIND_ALLOW_OOM_MANAGEMENT}, {@link Context#BIND_WAIVE_PRIORITY}, + * {@link Context#BIND_IMPORTANT}, and {@link Context#BIND_ADJUST_WITH_ACTIVITY}. + *

  • A started service can use the {@link #startForeground(int, Notification)} + * API to put the service in a foreground state, where the system considers + * it to be something the user is actively aware of and thus not a candidate + * for killing when low on memory. (It is still theoretically possible for + * the service to be killed under extreme memory pressure from the current + * foreground application, but in practice this should not be a concern.) + *

+ * + *

Note this means that most of the time your service is running, it may + * be killed by the system if it is under heavy memory pressure. If this + * happens, the system will later try to restart the service. An important + * consequence of this is that if you implement {@link #onStartCommand onStartCommand()} + * to schedule work to be done asynchronously or in another thread, then you + * may want to use {@link #START_FLAG_REDELIVERY} to have the system + * re-deliver an Intent for you so that it does not get lost if your service + * is killed while processing it. + * + *

Other application components running in the same process as the service + * (such as an {@link android.app.Activity}) can, of course, increase the + * importance of the overall + * process beyond just the importance of the service itself. + * + * + *

Local Service Sample

+ * + *

One of the most common uses of a Service is as a secondary component + * running alongside other parts of an application, in the same process as + * the rest of the components. All components of an .apk run in the same + * process unless explicitly stated otherwise, so this is a typical situation. + * + *

When used in this way, by assuming the + * components are in the same process, you can greatly simplify the interaction + * between them: clients of the service can simply cast the IBinder they + * receive from it to a concrete class published by the service. + * + *

An example of this use of a Service is shown here. First is the Service + * itself, publishing a custom class when bound: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java + * service} + * + *

With that done, one can now write client code that directly accesses the + * running service, such as: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java + * bind} + * + * + *

Remote Messenger Service Sample

+ * + *

If you need to be able to write a Service that can perform complicated + * communication with clients in remote processes (beyond simply the use of + * {@link Context#startService(Intent) Context.startService} to send + * commands to it), then you can use the {@link android.os.Messenger} class + * instead of writing full AIDL files. + * + *

An example of a Service that uses Messenger as its client interface + * is shown here. First is the Service itself, publishing a Messenger to + * an internal Handler when bound: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.java + * service} + * + *

If we want to make this service run in a remote process (instead of the + * standard one for its .apk), we can use android:process in its + * manifest tag to specify one: + * + * {@sample development/samples/ApiDemos/AndroidManifest.xml remote_service_declaration} + * + *

Note that the name "remote" chosen here is arbitrary, and you can use + * other names if you want additional processes. The ':' prefix appends the + * name to your package's standard process name. + * + *

With that done, clients can now bind to the service and send messages + * to it. Note that this allows clients to register with it to receive + * messages back as well: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java + * bind} + */ +public abstract class Service extends ContextWrapper implements ComponentCallbacks2 { + + private static final ServiceSupport serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class); + + private static final String TAG = "Service"; + /** + * Flag for {@link #stopForeground(int)}: if set, the notification previously provided + * to {@link #startForeground} will be removed. Otherwise it will remain + * until a later call (to {@link #startForeground(int, Notification)} or + * {@link #stopForeground(int)} removes it, or the service is destroyed. + */ + public static final int STOP_FOREGROUND_REMOVE = 1<<0; + /** + * Flag for {@link #stopForeground(int)}: if set, the notification previously provided + * to {@link #startForeground} will be detached from the service. Only makes sense + * when {@link #STOP_FOREGROUND_REMOVE} is not set -- in this case, the notification + * will remain shown, but be completely detached from the service and so no longer changed + * except through direct calls to the notification manager. + */ + public static final int STOP_FOREGROUND_DETACH = 1<<1; + /** @hide */ + @IntDef(flag = true, + value = { + STOP_FOREGROUND_REMOVE, + STOP_FOREGROUND_DETACH + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StopForegroundFlags {} + public Service() { + //==================[THIS LINE MODIFIED FROM ANDROID SOURCE!]================== + //Service must be initialized with a base context! + super(KodeinGlobalHelper.instance(Context.class)); + } + /** Return the application that owns this service. */ + public final Application getApplication() { + return mApplication; + } + /** + * Called by the system when the service is first created. Do not call this method directly. + */ + public void onCreate() { + } + /** + * @deprecated Implement {@link #onStartCommand(Intent, int, int)} instead. + */ + @Deprecated + public void onStart(Intent intent, int startId) { + } + /** + * Bits returned by {@link #onStartCommand} describing how to continue + * the service if it is killed. May be {@link #START_STICKY}, + * {@link #START_NOT_STICKY}, {@link #START_REDELIVER_INTENT}, + * or {@link #START_STICKY_COMPATIBILITY}. + */ + public static final int START_CONTINUATION_MASK = 0xf; + + /** + * Constant to return from {@link #onStartCommand}: compatibility + * version of {@link #START_STICKY} that does not guarantee that + * {@link #onStartCommand} will be called again after being killed. + */ + public static final int START_STICKY_COMPATIBILITY = 0; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then leave it in the started state but + * don't retain this delivered intent. Later the system will try to + * re-create the service. Because it is in the started state, it will + * guarantee to call {@link #onStartCommand} after creating the new + * service instance; if there are not any pending start commands to be + * delivered to the service, it will be called with a null intent + * object, so you must take care to check for this. + * + *

This mode makes sense for things that will be explicitly started + * and stopped to run for arbitrary periods of time, such as a service + * performing background music playback. + */ + public static final int START_STICKY = 1; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), and there are no new start intents to + * deliver to it, then take the service out of the started state and + * don't recreate until a future explicit call to + * {@link Context#startService Context.startService(Intent)}. The + * service will not receive a {@link #onStartCommand(Intent, int, int)} + * call with a null Intent because it will not be re-started if there + * are no pending Intents to deliver. + * + *

This mode makes sense for things that want to do some work as a + * result of being started, but can be stopped when under memory pressure + * and will explicit start themselves again later to do more work. An + * example of such a service would be one that polls for data from + * a server: it could schedule an alarm to poll every N minutes by having + * the alarm start its service. When its {@link #onStartCommand} is + * called from the alarm, it schedules a new alarm for N minutes later, + * and spawns a thread to do its networking. If its process is killed + * while doing that check, the service will not be restarted until the + * alarm goes off. + */ + public static final int START_NOT_STICKY = 2; + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then it will be scheduled for a restart + * and the last delivered Intent re-delivered to it again via + * {@link #onStartCommand}. This Intent will remain scheduled for + * redelivery until the service calls {@link #stopSelf(int)} with the + * start ID provided to {@link #onStartCommand}. The + * service will not receive a {@link #onStartCommand(Intent, int, int)} + * call with a null Intent because it will will only be re-started if + * it is not finished processing all Intents sent to it (and any such + * pending events will be delivered at the point of restart). + */ + public static final int START_REDELIVER_INTENT = 3; + /** @hide */ + @IntDef(flag = false, + value = { + START_STICKY_COMPATIBILITY, + START_STICKY, + START_NOT_STICKY, + START_REDELIVER_INTENT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StartResult {} + /** + * Special constant for reporting that we are done processing + * {@link #onTaskRemoved(Intent)}. + * @hide + */ + public static final int START_TASK_REMOVED_COMPLETE = 1000; + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * re-delivery of a previously delivered intent, because the service + * had previously returned {@link #START_REDELIVER_INTENT} but had been + * killed before calling {@link #stopSelf(int)} for that Intent. + */ + public static final int START_FLAG_REDELIVERY = 0x0001; + + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * retry because the original attempt never got to or returned from + * {@link #onStartCommand(Intent, int, int)}. + */ + public static final int START_FLAG_RETRY = 0x0002; + /** @hide */ + @IntDef(flag = true, + value = { + START_FLAG_REDELIVERY, + START_FLAG_RETRY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StartArgFlags {} + /** + * Called by the system every time a client explicitly starts the service by calling + * {@link android.content.Context#startService}, providing the arguments it supplied and a + * unique integer token representing the start request. Do not call this method directly. + * + *

For backwards compatibility, the default implementation calls + * {@link #onStart} and returns either {@link #START_STICKY} + * or {@link #START_STICKY_COMPATIBILITY}. + * + *

If you need your application to run on platform versions prior to API + * level 5, you can use the following model to handle the older {@link #onStart} + * callback in that case. The handleCommand method is implemented by + * you as appropriate: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java + * start_compatibility} + * + *

Note that the system calls this on your + * service's main thread. A service's main thread is the same + * thread where UI operations take place for Activities running in the + * same process. You should always avoid stalling the main + * thread's event loop. When doing long-running operations, + * network calls, or heavy disk I/O, you should kick off a new + * thread, or use {@link android.os.AsyncTask}.

+ * + * @param intent The Intent supplied to {@link android.content.Context#startService}, + * as given. This may be null if the service is being restarted after + * its process has gone away, and it had previously returned anything + * except {@link #START_STICKY_COMPATIBILITY}. + * @param flags Additional data about this start request. Currently either + * 0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}. + * @param startId A unique integer representing this specific request to + * start. Use with {@link #stopSelfResult(int)}. + * + * @return The return value indicates what semantics the system should + * use for the service's current started state. It may be one of the + * constants associated with the {@link #START_CONTINUATION_MASK} bits. + * + * @see #stopSelfResult(int) + */ + public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) { + onStart(intent, startId); + return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY; + } + + /** + * Called by the system to notify a Service that it is no longer used and is being removed. The + * service should clean up any resources it holds (threads, registered + * receivers, etc) at this point. Upon return, there will be no more calls + * in to this Service object and it is effectively dead. Do not call this method directly. + */ + public void onDestroy() { + } + public void onConfigurationChanged(Configuration newConfig) { + } + + public void onLowMemory() { + } + public void onTrimMemory(int level) { + } + /** + * Return the communication channel to the service. May return null if + * clients can not bind to the service. The returned + * {@link android.os.IBinder} is usually for a complex interface + * that has been described using + * aidl. + * + *

Note that unlike other application components, calls on to the + * IBinder interface returned here may not happen on the main thread + * of the process. More information about the main thread can be found in + * Processes and + * Threads.

+ * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will not be seen here. + * + * @return Return an IBinder through which clients can call on to the + * service. + */ + @Nullable + public abstract IBinder onBind(Intent intent); + /** + * Called when all clients have disconnected from a particular interface + * published by the service. The default implementation does nothing and + * returns false. + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will not be seen here. + * + * @return Return true if you would like to have the service's + * {@link #onRebind} method later called when new clients bind to it. + */ + public boolean onUnbind(Intent intent) { + return false; + } + + /** + * Called when new clients have connected to the service, after it had + * previously been notified that all had disconnected in its + * {@link #onUnbind}. This will only be called if the implementation + * of {@link #onUnbind} was overridden to return true. + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will not be seen here. + */ + public void onRebind(Intent intent) { + } + + /** + * This is called if the service is currently running and the user has + * removed a task that comes from the service's application. If you have + * set {@link android.content.pm.ServiceInfo#FLAG_STOP_WITH_TASK ServiceInfo.FLAG_STOP_WITH_TASK} + * then you will not receive this callback; instead, the service will simply + * be stopped. + * + * @param rootIntent The original root Intent that was used to launch + * the task that is being removed. + */ + public void onTaskRemoved(Intent rootIntent) { + } + /** + * Stop the service, if it was previously started. This is the same as + * calling {@link android.content.Context#stopService} for this particular service. + * + * @see #stopSelfResult(int) + */ + public final void stopSelf() { + stopSelf(-1); + } + /** + * Old version of {@link #stopSelfResult} that doesn't return a result. + * + * @see #stopSelfResult + */ + public final void stopSelf(int startId) { + serviceSupport.stopSelf(this); + //TODO Do something with startId + } + + /** + * Stop the service if the most recent time it was started was + * startId. This is the same as calling {@link + * android.content.Context#stopService} for this particular service but allows you to + * safely avoid stopping if there is a start request from a client that you + * haven't yet seen in {@link #onStart}. + * + *

Be careful about ordering of your calls to this function.. + * If you call this function with the most-recently received ID before + * you have called it for previously received IDs, the service will be + * immediately stopped anyway. If you may end up processing IDs out + * of order (such as by dispatching them on separate threads), then you + * are responsible for stopping them in the same order you received them.

+ * + * @param startId The most recent start identifier received in {@link + * #onStart}. + * @return Returns true if the startId matches the last start request + * and the service will be stopped, else false. + * + * @see #stopSelf() + */ + public final boolean stopSelfResult(int startId) { + stopSelf(startId); + //TODO Actually return result maybe + return true; + } + + /** + * @deprecated This is a now a no-op, use + * {@link #startForeground(int, Notification)} instead. This method + * has been turned into a no-op rather than simply being deprecated + * because analysis of numerous poorly behaving devices has shown that + * increasingly often the trouble is being caused in part by applications + * that are abusing it. Thus, given a choice between introducing + * problems in existing applications using this API (by allowing them to + * be killed when they would like to avoid it), vs allowing the performance + * of the entire system to be decreased, this method was deemed less + * important. + * + * @hide + */ + @Deprecated + public final void setForeground(boolean isForeground) { + Log.w(TAG, "setForeground: ignoring old API call on " + getClass().getName()); + } + + /** + * Make this service run in the foreground, supplying the ongoing + * notification to be shown to the user while in this state. + * By default services are background, meaning that if the system needs to + * kill them to reclaim more memory (such as to display a large page in a + * web browser), they can be killed without too much harm. You can set this + * flag if killing your service would be disruptive to the user, such as + * if your service is performing background music playback, so the user + * would notice if their music stopped playing. + * + *

If you need your application to run on platform versions prior to API + * level 5, you can use the following model to call the the older setForeground() + * or this modern method as appropriate: + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java + * foreground_compatibility} + * + * @param id The identifier for this notification as per + * {@link NotificationManager#notify(int, Notification) + * NotificationManager.notify(int, Notification)}; must not be 0. + * @param notification The Notification to be displayed. + * + * @see #stopForeground(boolean) + */ + public final void startForeground(int id, Notification notification) { + throw new NotImplementedError("TODO"); + } + + /** + * Synonym for {@link #stopForeground(int)}. + * @param removeNotification If true, the {@link #STOP_FOREGROUND_REMOVE} flag + * will be supplied. + * @see #stopForeground(int) + * @see #startForeground(int, Notification) + */ + public final void stopForeground(boolean removeNotification) { + stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : 0); + } + /** + * Remove this service from foreground state, allowing it to be killed if + * more memory is needed. + * @param flags Additional behavior options: {@link #STOP_FOREGROUND_REMOVE}, + * {@link #STOP_FOREGROUND_DETACH}. + * @see #startForeground(int, Notification) + */ + public final void stopForeground(@StopForegroundFlags int flags) { + throw new NotImplementedError("TODO"); + } + /** + * Print the Service's state into the given stream. This gets invoked if + * you run "adb shell dumpsys activity service <yourservicename>" + * (note that for this command to work, the service must be running, and + * you must specify a fully-qualified service name). + * This is distinct from "dumpsys <servicename>", which only works for + * named system services and which invokes the {@link IBinder#dump} method + * on the {@link IBinder} interface registered with ServiceManager. + * + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be + * closed for you after you return. + * @param args additional arguments to the dump request. + */ + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("nothing to dump"); + } + // ------------------ Internal API ------------------ + + + final String getClassName() { + return mClassName; + } + // set by the thread after the constructor and before onCreate(Bundle icicle) is called. + private String mClassName = null; + private IBinder mToken = null; + private Application mApplication = null; + private boolean mStartCompatibility = false; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java new file mode 100644 index 00000000..251ff75b --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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 android.arch.persistence.db; + +import androidx.annotation.Nullable; + +/** + * A basic implementation of {@link SupportSQLiteQuery} which receives a query and its args and + * binds args based on the passed in Object type. + */ +public final class SimpleSQLiteQuery implements SupportSQLiteQuery { + private final String mQuery; + @Nullable + private final Object[] mBindArgs; + + /** + * Creates an SQL query with the sql string and the bind arguments. + * + * @param query The query string, can include bind arguments (.e.g ?). + * @param bindArgs The bind argument value that will replace the placeholders in the query. + */ + public SimpleSQLiteQuery(String query, @Nullable Object[] bindArgs) { + mQuery = query; + mBindArgs = bindArgs; + } + + /** + * Creates an SQL query without any bind arguments. + * + * @param query The SQL query to execute. Cannot include bind parameters. + */ + public SimpleSQLiteQuery(String query) { + this(query, null); + } + + /** + * Binds the given arguments into the given sqlite statement. + * + * @param statement The sqlite statement + * @param bindArgs The list of bind arguments + */ + public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) { + if (bindArgs == null) { + return; + } + final int limit = bindArgs.length; + for (int i = 0; i < limit; i++) { + final Object arg = bindArgs[i]; + bind(statement, i + 1, arg); + } + } + + private static void bind(SupportSQLiteProgram statement, int index, Object arg) { + // extracted from android.database.sqlite.SQLiteConnection + if (arg == null) { + statement.bindNull(index); + } else if (arg instanceof byte[]) { + statement.bindBlob(index, (byte[]) arg); + } else if (arg instanceof Float) { + statement.bindDouble(index, (Float) arg); + } else if (arg instanceof Double) { + statement.bindDouble(index, (Double) arg); + } else if (arg instanceof Long) { + statement.bindLong(index, (Long) arg); + } else if (arg instanceof Integer) { + statement.bindLong(index, (Integer) arg); + } else if (arg instanceof Short) { + statement.bindLong(index, (Short) arg); + } else if (arg instanceof Byte) { + statement.bindLong(index, (Byte) arg); + } else if (arg instanceof String) { + statement.bindString(index, (String) arg); + } else if (arg instanceof Boolean) { + statement.bindLong(index, ((Boolean) arg) ? 1 : 0); + } else { + throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index + + " Supported types: null, byte[], float, double, long, int, short, byte," + + " string"); + } + } + + @Override + public String getSql() { + return mQuery; + } + + @Override + public void bindTo(SupportSQLiteProgram statement) { + bind(statement, mBindArgs); + } + + @Override + public int getArgCount() { + return mBindArgs == null ? 0 : mBindArgs.length; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java new file mode 100644 index 00000000..b00396a3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteTransactionListener; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; +import android.util.Pair; +import androidx.annotation.RequiresApi; + +import java.io.Closeable; +import java.util.List; +import java.util.Locale; + +/** + * A database abstraction which removes the framework dependency and allows swapping underlying + * sql versions. It mimics the behavior of {@link android.database.sqlite.SQLiteDatabase} + */ +@SuppressWarnings("unused") +public interface SupportSQLiteDatabase extends Closeable { + /** + * Compiles the given SQL statement. + * + * @param sql The sql query. + * @return Compiled statement. + */ + SupportSQLiteStatement compileStatement(String sql); + + /** + * Begins a transaction in EXCLUSIVE 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.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + void beginTransaction(); + + /** + * Begins a transaction in IMMEDIATE 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.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + void beginTransactionNonExclusive(); + + /** + * Begins a transaction in EXCLUSIVE 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}. + */ + void beginTransactionWithListener(SQLiteTransactionListener transactionListener); + + /** + * Begins a transaction in IMMEDIATE 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.beginTransactionWithListenerNonExclusive(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}. + */ + void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener); + + /** + * End a transaction. See beginTransaction for notes about how to use this and when transactions + * are committed and rolled back. + */ + void endTransaction(); + + /** + * 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. + */ + void setTransactionSuccessful(); + + /** + * Returns true if the current thread has a transaction pending. + * + * @return True if the current thread is in a transaction. + */ + boolean inTransaction(); + + /** + * Returns true if the current thread is holding an active connection to the database. + *

+ * The name of this method comes from a time when having an active connection + * to the database meant that the thread was holding an actual lock on the + * database. Nowadays, there is no longer a true "database lock" although threads + * may block if they cannot acquire a database connection to perform a + * particular operation. + *

+ * + * @return True if the current thread is holding an active connection to the database. + */ + boolean isDbLockedByCurrentThread(); + + /** + * 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 + */ + boolean yieldIfContendedSafely(); + + /** + * 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 + */ + boolean yieldIfContendedSafely(long sleepAfterYieldDelay); + + /** + * Gets the database version. + * + * @return the database version + */ + int getVersion(); + + /** + * Sets the database version. + * + * @param version the new database version + */ + void setVersion(int version); + + /** + * Returns the maximum size the database may grow to. + * + * @return the new maximum database size + */ + long getMaximumSize(); + + /** + * 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 + */ + long setMaximumSize(long numBytes); + + /** + * Returns the current database page size, in bytes. + * + * @return the database page size, in bytes + */ + long getPageSize(); + + /** + * 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 + */ + void setPageSize(long numBytes); + + /** + * Runs the given query on the database. If you would like to have typed bind arguments, + * use {@link #query(SupportSQLiteQuery)}. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @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 #query(SupportSQLiteQuery) + */ + Cursor query(String query); + + /** + * Runs the given query on the database. If you would like to have bind arguments, + * use {@link #query(SupportSQLiteQuery)}. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @param bindArgs The query arguments to bind. + * @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 #query(SupportSQLiteQuery) + */ + Cursor query(String query, Object[] bindArgs); + + /** + * Runs the given query on the database. + *

+ * This class allows using type safe sql program bindings while running queries. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @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 SimpleSQLiteQuery + */ + Cursor query(SupportSQLiteQuery query); + + /** + * Runs the given query on the database. + *

+ * This class allows using type safe sql program bindings while running queries. + * + * @param query The SQL query that includes the query and can bind into a given compiled + * program. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @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. + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal); + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @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 + * @param conflictAlgorithm for insert conflict resolver. One of + * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK}, + * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL}, + * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}. + * @return the row ID of the newly inserted row, or -1 if an error occurred + * @throws SQLException If the insert fails + */ + long insert(String table, int conflictAlgorithm, ContentValues values) throws SQLException; + + /** + * 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. + * @param whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @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. + */ + int delete(String table, String whereClause, Object[] whereArgs); + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param conflictAlgorithm for update conflict resolver. One of + * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK}, + * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL}, + * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}. + * @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 whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @return the number of rows affected + */ + int update(String table, int conflictAlgorithm, + ContentValues values, String whereClause, Object[] whereArgs); + + /** + * Execute a single SQL statement that does not return any data. + *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are + * not supported. + * @throws SQLException if the SQL string is invalid + * @see #query(SupportSQLiteQuery) + */ + void execSQL(String sql) throws SQLException; + + /** + * Execute a single SQL statement that does not return any data. + *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons + * are + * not supported. + * @param bindArgs only byte[], String, Long and Double are supported in selectionArgs. + * @throws SQLException if the SQL string is invalid + * @see #query(SupportSQLiteQuery) + */ + void execSQL(String sql, Object[] bindArgs) throws SQLException; + + /** + * Returns true if the database is opened as read only. + * + * @return True if database is opened as read only. + */ + boolean isReadOnly(); + + /** + * Returns true if the database is currently open. + * + * @return True if the database is currently open (has not been closed). + */ + boolean isOpen(); + + /** + * Returns true if the new version code is greater than the current database version. + * + * @param newVersion The new version code. + * @return True if the new version code is greater than the current database version. + */ + boolean needUpgrade(int newVersion); + + /** + * Gets the path to the database file. + * + * @return The path to the database file. + */ + String getPath(); + + /** + * Sets the locale for this database. Does nothing if this database has + * the {@link SQLiteDatabase#NO_LOCALIZED_COLLATORS} flag set or was opened read only. + * + * @param locale The new locale. + * @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. + */ + void setLocale(Locale locale); + + /** + * Sets the maximum size of the prepared-statement cache for this database. + * (size of the cache = number of compiled-sql-statements stored in the cache). + *

+ * Maximum cache size can ONLY be increased from its current size (default = 10). + * If this method is called with smaller size than the current maximum value, + * then IllegalStateException is thrown. + *

+ * This method is thread-safe. + * + * @param cacheSize the size of the cache. can be (0 to + * {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}) + * @throws IllegalStateException if input cacheSize gt; + * {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}. + */ + void setMaxSqlCacheSize(int cacheSize); + + /** + * Sets whether foreign key constraints are enabled for the database. + *

+ * By default, foreign key constraints are not enforced by the database. + * This method allows an application to enable foreign key constraints. + * It must be called each time the database is opened to ensure that foreign + * key constraints are enabled for the session. + *

+ * A good time to call this method is right after calling {@code #openOrCreateDatabase} + * or in the {@link SupportSQLiteOpenHelper.Callback#onConfigure} callback. + *

+ * When foreign key constraints are disabled, the database does not check whether + * changes to the database will violate foreign key constraints. Likewise, when + * foreign key constraints are disabled, the database will not execute cascade + * delete or update triggers. As a result, it is possible for the database + * state to become inconsistent. To perform a database integrity check, + * call {@link #isDatabaseIntegrityOk}. + *

+ * This method must not be called while a transaction is in progress. + *

+ * See also SQLite Foreign Key Constraints + * for more details about foreign key constraint support. + *

+ * + * @param enable True to enable foreign key constraints, false to disable them. + * @throws IllegalStateException if the are transactions is in progress + * when this method is called. + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + void setForeignKeyConstraintsEnabled(boolean enable); + + /** + * This method enables parallel execution of queries from multiple threads on the + * same database. It does this by opening multiple connections to the database + * and using a different database connection for each query. The database + * journal mode is also changed to enable writes to proceed concurrently with reads. + *

+ * When write-ahead logging is not enabled (the default), it is not possible for + * reads and writes to occur on the database at the same time. Before modifying the + * database, the writer implicitly acquires an exclusive lock on the database which + * prevents readers from accessing the database until the write is completed. + *

+ * In contrast, when write-ahead logging is enabled (by calling this method), write + * operations occur in a separate log file which allows reads to proceed concurrently. + * While a write is in progress, readers on other threads will perceive the state + * of the database as it was before the write began. When the write completes, readers + * on other threads will then perceive the new state of the database. + *

+ * It is a good idea to enable write-ahead logging whenever a database will be + * concurrently accessed and modified by multiple threads at the same time. + * However, write-ahead logging uses significantly more memory than ordinary + * journaling because there are multiple connections to the same database. + * So if a database will only be used by a single thread, or if optimizing + * concurrency is not very important, then write-ahead logging should be disabled. + *

+ * After calling this method, execution of queries in parallel is enabled as long as + * the database remains open. To disable execution of queries in parallel, either + * call {@link #disableWriteAheadLogging} or close the database and reopen it. + *

+ * The maximum number of connections used to execute queries in parallel is + * dependent upon the device memory and possibly other properties. + *

+ * If a query is part of a transaction, then it is executed on the same database handle the + * transaction was begun. + *

+ * Writers should use {@link #beginTransactionNonExclusive()} or + * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)} + * to start a transaction. Non-exclusive mode allows database file to be in readable + * by other threads executing queries. + *

+ * If the database has any attached databases, then execution of queries in parallel is NOT + * possible. Likewise, write-ahead logging is not supported for read-only databases + * or memory databases. In such cases, {@code enableWriteAheadLogging} returns false. + *

+ * The best way to enable write-ahead logging is to pass the + * {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag to + * {@link SQLiteDatabase#openDatabase}. This is more efficient than calling + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * Another way to enable write-ahead logging is to call {@code enableWriteAheadLogging} + * after opening the database. + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * See also SQLite Write-Ahead Logging for + * more details about how write-ahead logging works. + *

+ * + * @return True if write-ahead logging is enabled. + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when + * there are no + * transactions in progress. + * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING + * @see #disableWriteAheadLogging + */ + boolean enableWriteAheadLogging(); + + /** + * This method disables the features enabled by {@link #enableWriteAheadLogging()}. + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when + * there are no + * transactions in progress. + * @see #enableWriteAheadLogging + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + void disableWriteAheadLogging(); + + /** + * Returns true if write-ahead logging has been enabled for this database. + * + * @return True if write-ahead logging has been enabled for this database. + * @see #enableWriteAheadLogging + * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + boolean isWriteAheadLoggingEnabled(); + + /** + * Returns list of full path names of all attached databases including the main database + * by executing 'pragma database_list' on the database. + * + * @return ArrayList of pairs of (database name, database file path) or null if the database + * is not open. + */ + List> getAttachedDbs(); + + /** + * Runs 'pragma integrity_check' on the given database (and all the attached databases) + * and returns true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + *

+ * If the result is false, then this method logs the errors reported by the integrity_check + * command execution. + *

+ * Note that 'pragma integrity_check' on a database can take a long time. + * + * @return true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + */ + boolean isDatabaseIntegrityOk(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java new file mode 100644 index 00000000..2beca6b3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java @@ -0,0 +1,389 @@ +package android.arch.persistence.db; + +/* + * Copyright (C) 2016 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. + */ + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.Build; +import android.util.Log; +import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +// TODO Replace with androidx variant once Tachiyomi updates + +/** + * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}. + * Note that since that class requires overriding certain methods, support implementation + * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement + * the methods that should be overridden. + */ +@SuppressWarnings("unused") +public interface SupportSQLiteOpenHelper { + /** + * Return the name of the SQLite database being opened, as given to + * the constructor. + */ + String getDatabaseName(); + + /** + * 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 SupportSQLiteDatabase#enableWriteAheadLogging() + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + void setWriteAheadLoggingEnabled(boolean enabled); + + /** + * Create and/or open a database that will be used for reading and writing. + * The first time this is called, the database will be opened and + * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be + * called. + * + *

Once opened successfully, the database is cached, so you can + * call this method every time you need to write to the database. + * (Make sure to call {@link #close} when you no longer need the database.) + * Errors such as bad permissions or a full disk may cause this method + * to fail, but future attempts may succeed if the problem is fixed.

+ * + *

Database upgrade may take a long time, you + * should not call this method from the application main thread, including + * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @return a read/write database object valid until {@link #close} is called + * @throws SQLiteException if the database cannot be opened for writing + */ + SupportSQLiteDatabase getWritableDatabase(); + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + *

Like {@link #getWritableDatabase}, this method may + * take a long time to return, so you should not call it from the + * application main thread, including from + * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @return a database object valid until {@link #getWritableDatabase} + * or {@link #close} is called. + * @throws SQLiteException if the database cannot be opened + */ + SupportSQLiteDatabase getReadableDatabase(); + + /** + * Close any open database object. + */ + void close(); + + /** + * Factory class to create instances of {@link SupportSQLiteOpenHelper} using + * {@link Configuration}. + */ + interface Factory { + /** + * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration. + * + * @param configuration The configuration to use while creating the open helper. + * @return A SupportSQLiteOpenHelper which can be used to open a database. + */ + SupportSQLiteOpenHelper create(Configuration configuration); + } + + /** + * Handles various lifecycle events for the SQLite connection, similar to + * {@link android.database.sqlite.SQLiteOpenHelper}. + */ + @SuppressWarnings({"unused", "WeakerAccess"}) + abstract class Callback { + private static final String TAG = "SupportSQLite"; + /** + * Version number of the database (starting at 1); if the database is older, + * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)} + * will be used to upgrade the database; if the database is newer, + * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)} + * will be used to downgrade the database. + */ + public final int version; + + /** + * Creates a new Callback to get database lifecycle events. + * + * @param version The version for the database instance. See {@link #version}. + */ + public Callback(int version) { + this.version = version; + } + + /** + * 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 SupportSQLiteDatabase#enableWriteAheadLogging} + * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled}, + * {@link SupportSQLiteDatabase#setLocale}, + * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements. + *

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

+ * The SQLite ALTER TABLE documentation can be found + * here. If you add new columns + * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns + * you can use ALTER TABLE to rename the old table, then create the new table and then + * populate the new table with the contents of the old table. + *

+ * 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 abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion); + + /** + * 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(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + throw new SQLiteException("Can't downgrade database from version " + + oldVersion + " to " + newVersion); + } + + /** + * Called when the database has been opened. The implementation + * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the + * database. + *

+ * This method is called after the database connection has been configured + * and after the database schema has been created, upgraded or downgraded as necessary. + * If the database connection must be configured in some way before the schema + * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. + *

+ * + * @param db The database. + */ + public void onOpen(SupportSQLiteDatabase db) { + } + + /** + * The method invoked when database corruption is detected. Default implementation will + * delete the database file. + * + * @param db the {@link SupportSQLiteDatabase} object representing the database on which + * corruption is detected. + */ + public void onCorruption(SupportSQLiteDatabase db) { + // the following implementation is taken from {@link DefaultDatabaseErrorHandler}. + Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath()); + // is the corruption detected even before database could be 'opened'? + if (!db.isOpen()) { + // database files are not even openable. delete this database file. + // NOTE if the database has attached databases, then any of them could be corrupt. + // and not deleting all of them could cause corrupted database file to remain and + // make the application crash on database open operation. To avoid this problem, + // the application should provide its own {@link DatabaseErrorHandler} impl class + // to delete ALL files of the database (including the attached databases). + deleteDatabaseFile(db.getPath()); + return; + } + List> attachedDbs = null; + try { + // Close the database, which will cause subsequent operations to fail. + // before that, get the attached database list first. + try { + attachedDbs = db.getAttachedDbs(); + } catch (SQLiteException e) { + /* ignore */ + } + try { + db.close(); + } catch (IOException e) { + /* ignore */ + } + } finally { + // Delete all files of this corrupt database and/or attached databases + if (attachedDbs != null) { + for (Pair p : attachedDbs) { + deleteDatabaseFile(p.second); + } + } else { + // attachedDbs = null is possible when the database is so corrupt that even + // "PRAGMA database_list;" also fails. delete the main database file + deleteDatabaseFile(db.getPath()); + } + } + } + + private void deleteDatabaseFile(String fileName) { + if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) { + return; + } + Log.w(TAG, "deleting the database file: " + fileName); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + SQLiteDatabase.deleteDatabase(new File(fileName)); + } else { + try { + final boolean deleted = new File(fileName).delete(); + if (!deleted) { + Log.e(TAG, "Could not delete the database file " + fileName); + } + } catch (Exception error) { + Log.e(TAG, "error while deleting corrupted database file", error); + } + } + } catch (Exception e) { + /* print warning and ignore exception */ + Log.w(TAG, "delete failed: ", e); + } + } + } + + /** + * The configuration to create an SQLite open helper object using {@link Factory}. + */ + @SuppressWarnings("WeakerAccess") + class Configuration { + /** + * Context to use to open or create the database. + */ + @NonNull + public final Context context; + /** + * Name of the database file, or null for an in-memory database. + */ + @Nullable + public final String name; + /** + * The callback class to handle creation, upgrade and downgrade. + */ + @NonNull + public final SupportSQLiteOpenHelper.Callback callback; + + Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) { + this.context = context; + this.name = name; + this.callback = callback; + } + + /** + * Creates a new Configuration.Builder to create an instance of Configuration. + * + * @param context to use to open or create the database. + */ + public static Builder builder(Context context) { + return new Builder(context); + } + + /** + * Builder class for {@link Configuration}. + */ + public static class Builder { + Context mContext; + String mName; + SupportSQLiteOpenHelper.Callback mCallback; + + Builder(@NonNull Context context) { + mContext = context; + } + + public Configuration build() { + if (mCallback == null) { + throw new IllegalArgumentException("Must set a callback to create the" + + " configuration."); + } + if (mContext == null) { + throw new IllegalArgumentException("Must set a non-null context to create" + + " the configuration."); + } + return new Configuration(mContext, mName, mCallback); + } + + /** + * @param name Name of the database file, or null for an in-memory database. + * @return This + */ + public Builder name(@Nullable String name) { + mName = name; + return this; + } + + /** + * @param callback The callback class to handle creation, upgrade and downgrade. + * @return this + */ + public Builder callback(@NonNull Callback callback) { + mCallback = callback; + return this; + } + } + } +} diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java new file mode 100644 index 00000000..21046fee --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db; + +import java.io.Closeable; + +/** + * An interface to map the behavior of {@link android.database.sqlite.SQLiteProgram}. + */ +@SuppressWarnings("unused") +public interface SupportSQLiteProgram extends Closeable { + /** + * Bind a NULL value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind null to + */ + void bindNull(int index); + + /** + * Bind a long value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * addToBindArgs + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + void bindLong(int index, long value); + + /** + * Bind a double value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + void bindDouble(int index, double value); + + /** + * Bind a String value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + void bindString(int index, String value); + + /** + * Bind a byte array value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + void bindBlob(int index, byte[] value); + + /** + * Clears all existing bindings. Unset bindings are treated as NULL. + */ + void clearBindings(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java new file mode 100644 index 00000000..4569dae5 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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 android.arch.persistence.db; + +/** + * A query with typed bindings. It is better to use this API instead of + * {@link android.database.sqlite.SQLiteDatabase#rawQuery(String, String[])} because it allows + * binding type safe parameters. + */ +public interface SupportSQLiteQuery { + /** + * The SQL query. This query can have placeholders(?) for bind arguments. + * + * @return The SQL query to compile + */ + String getSql(); + + /** + * Callback to bind the query parameters to the compiled statement. + * + * @param statement The compiled statement + */ + void bindTo(SupportSQLiteProgram statement); + + /** + * Returns the number of arguments in this query. This is equal to the number of placeholders + * in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details. + * + * @return The number of arguments in the query. + */ + int getArgCount(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java new file mode 100644 index 00000000..680e9579 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 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 android.arch.persistence.db; + +import java.util.regex.Pattern; + +/** + * A simple query builder to create SQL SELECT queries. + */ +@SuppressWarnings("unused") +public final class SupportSQLiteQueryBuilder { + private static final Pattern sLimitPattern = + Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); + private final String mTable; + private boolean mDistinct = false; + private String[] mColumns = null; + private String mSelection; + private Object[] mBindArgs; + private String mGroupBy = null; + private String mHaving = null; + private String mOrderBy = null; + private String mLimit = null; + + private SupportSQLiteQueryBuilder(String table) { + mTable = table; + } + + /** + * Creates a query for the given table name. + * + * @param tableName The table name(s) to query. + * @return A builder to create a query. + */ + public static SupportSQLiteQueryBuilder builder(String tableName) { + return new SupportSQLiteQueryBuilder(tableName); + } + + private static void appendClause(StringBuilder s, String name, String clause) { + if (!isEmpty(clause)) { + s.append(name); + s.append(clause); + } + } + + /** + * Add the names that are non-null in columns to s, separating + * them with commas. + */ + private static void appendColumns(StringBuilder s, String[] columns) { + int n = columns.length; + for (int i = 0; i < n; i++) { + String column = columns[i]; + if (i > 0) { + s.append(", "); + } + s.append(column); + } + s.append(' '); + } + + private static boolean isEmpty(String input) { + return input == null || input.length() == 0; + } + + /** + * Adds DISTINCT keyword to the query. + * + * @return this + */ + public SupportSQLiteQueryBuilder distinct() { + mDistinct = true; + return this; + } + + /** + * Sets the given list of columns as the columns that will be returned. + * + * @param columns The list of column names that should be returned. + * @return this + */ + public SupportSQLiteQueryBuilder columns(String[] columns) { + mColumns = columns; + return this; + } + + /** + * Sets the arguments for the WHERE clause. + * + * @param selection The list of selection columns + * @param bindArgs The list of bind arguments to match against these columns + * @return this + */ + public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) { + mSelection = selection; + mBindArgs = bindArgs; + return this; + } + + /** + * Adds a GROUP BY statement. + * + * @param groupBy The value of the GROUP BY statement. + * @return this + */ + @SuppressWarnings("WeakerAccess") + public SupportSQLiteQueryBuilder groupBy(String groupBy) { + mGroupBy = groupBy; + return this; + } + + /** + * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work. + * + * @param having The having clause. + * @return this + */ + public SupportSQLiteQueryBuilder having(String having) { + mHaving = having; + return this; + } + + /** + * Adds an ORDER BY statement. + * + * @param orderBy The order clause. + * @return this + */ + public SupportSQLiteQueryBuilder orderBy(String orderBy) { + mOrderBy = orderBy; + return this; + } + + /** + * Adds a LIMIT statement. + * + * @param limit The limit value. + * @return this + */ + public SupportSQLiteQueryBuilder limit(String limit) { + if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { + throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); + } + mLimit = limit; + return this; + } + + /** + * Creates the {@link SupportSQLiteQuery} that can be passed into + * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}. + * + * @return a new query + */ + public SupportSQLiteQuery create() { + if (isEmpty(mGroupBy) && !isEmpty(mHaving)) { + throw new IllegalArgumentException( + "HAVING clauses are only permitted when using a groupBy clause"); + } + StringBuilder query = new StringBuilder(120); + query.append("SELECT "); + if (mDistinct) { + query.append("DISTINCT "); + } + if (mColumns != null && mColumns.length != 0) { + appendColumns(query, mColumns); + } else { + query.append(" * "); + } + query.append(" FROM "); + query.append(mTable); + appendClause(query, " WHERE ", mSelection); + appendClause(query, " GROUP BY ", mGroupBy); + appendClause(query, " HAVING ", mHaving); + appendClause(query, " ORDER BY ", mOrderBy); + appendClause(query, " LIMIT ", mLimit); + return new SimpleSQLiteQuery(query.toString(), mBindArgs); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java new file mode 100644 index 00000000..c6ba243d --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db; + +/** + * An interface to map the behavior of {@link android.database.sqlite.SQLiteStatement}. + */ +@SuppressWarnings("unused") +public interface SupportSQLiteStatement extends SupportSQLiteProgram { + /** + * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example + * CREATE / DROP table, view, trigger, index etc. + * + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + void execute(); + + /** + * Execute this SQL statement, if the the number of rows affected by execution of this SQL + * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. + * + * @return the number of rows affected by this SQL statement execution. + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + int executeUpdateDelete(); + + /** + * Execute this SQL statement and return the ID of the row inserted due to this call. + * The SQL statement should be an INSERT for this to be a useful call. + * + * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + long executeInsert(); + + /** + * Execute a statement that returns a 1 by 1 table with a numeric value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows + */ + long simpleQueryForLong(); + + /** + * Execute a statement that returns a 1 by 1 table with a text value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows + */ + String simpleQueryForString(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java new file mode 100644 index 00000000..d92762fb --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db.framework; + +import android.arch.persistence.db.SimpleSQLiteQuery; +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteQuery; +import android.arch.persistence.db.SupportSQLiteStatement; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.*; +import android.os.Build; +import android.os.CancellationSignal; +import android.util.Pair; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import static android.text.TextUtils.isEmpty; + +/** + * Delegates all calls to an implementation of {@link SQLiteDatabase}. + */ +@SuppressWarnings("unused") +class FrameworkSQLiteDatabase implements SupportSQLiteDatabase { + private static final String[] CONFLICT_VALUES = new String[] + {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private final SQLiteDatabase mDelegate; + + /** + * Creates a wrapper around {@link SQLiteDatabase}. + * + * @param delegate The delegate to receive all calls. + */ + FrameworkSQLiteDatabase(SQLiteDatabase delegate) { + mDelegate = delegate; + } + + @Override + public SupportSQLiteStatement compileStatement(String sql) { + return new FrameworkSQLiteStatement(mDelegate.compileStatement(sql)); + } + + @Override + public void beginTransaction() { + mDelegate.beginTransaction(); + } + + @Override + public void beginTransactionNonExclusive() { + mDelegate.beginTransactionNonExclusive(); + } + + @Override + public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { + mDelegate.beginTransactionWithListener(transactionListener); + } + + @Override + public void beginTransactionWithListenerNonExclusive( + SQLiteTransactionListener transactionListener) { + mDelegate.beginTransactionWithListenerNonExclusive(transactionListener); + } + + @Override + public void endTransaction() { + mDelegate.endTransaction(); + } + + @Override + public void setTransactionSuccessful() { + mDelegate.setTransactionSuccessful(); + } + + @Override + public boolean inTransaction() { + return mDelegate.inTransaction(); + } + + @Override + public boolean isDbLockedByCurrentThread() { + return mDelegate.isDbLockedByCurrentThread(); + } + + @Override + public boolean yieldIfContendedSafely() { + return mDelegate.yieldIfContendedSafely(); + } + + @Override + public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { + return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelay); + } + + @Override + public int getVersion() { + return mDelegate.getVersion(); + } + + @Override + public void setVersion(int version) { + mDelegate.setVersion(version); + } + + @Override + public long getMaximumSize() { + return mDelegate.getMaximumSize(); + } + + @Override + public long setMaximumSize(long numBytes) { + return mDelegate.setMaximumSize(numBytes); + } + + @Override + public long getPageSize() { + return mDelegate.getPageSize(); + } + + @Override + public void setPageSize(long numBytes) { + mDelegate.setPageSize(numBytes); + } + + @Override + public Cursor query(String query) { + return query(new SimpleSQLiteQuery(query)); + } + + @Override + public Cursor query(String query, Object[] bindArgs) { + return query(new SimpleSQLiteQuery(query, bindArgs)); + } + + @Override + public Cursor query(final SupportSQLiteQuery supportQuery) { + return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() { + @Override + public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, + String editTable, SQLiteQuery query) { + supportQuery.bindTo(new FrameworkSQLiteProgram(query)); + return new SQLiteCursor(masterQuery, editTable, query); + } + }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null); + } + + @Override + @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public Cursor query(final SupportSQLiteQuery supportQuery, + CancellationSignal cancellationSignal) { + return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() { + @Override + public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, + String editTable, SQLiteQuery query) { + supportQuery.bindTo(new FrameworkSQLiteProgram(query)); + return new SQLiteCursor(masterQuery, editTable, query); + } + }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal); + } + + @Override + public long insert(String table, int conflictAlgorithm, ContentValues values) + throws SQLException { + return mDelegate.insertWithOnConflict(table, null, values, + conflictAlgorithm); + } + + @Override + public int delete(String table, String whereClause, Object[] whereArgs) { + String query = "DELETE FROM " + table + + (isEmpty(whereClause) ? "" : " WHERE " + whereClause); + SupportSQLiteStatement statement = compileStatement(query); + SimpleSQLiteQuery.bind(statement, whereArgs); + return statement.executeUpdateDelete(); + } + + @Override + public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause, + Object[] whereArgs) { + // taken from SQLiteDatabase class. + 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 "); + // move all bind args to one array + int setValuesSize = values.size(); + int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); + Object[] bindArgs = new Object[bindArgsSize]; + int i = 0; + for (String colName : values.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = values.get(colName); + sql.append("=?"); + } + if (whereArgs != null) { + for (i = setValuesSize; i < bindArgsSize; i++) { + bindArgs[i] = whereArgs[i - setValuesSize]; + } + } + if (!isEmpty(whereClause)) { + sql.append(" WHERE "); + sql.append(whereClause); + } + SupportSQLiteStatement stmt = compileStatement(sql.toString()); + SimpleSQLiteQuery.bind(stmt, bindArgs); + return stmt.executeUpdateDelete(); + } + + @Override + public void execSQL(String sql) throws SQLException { + mDelegate.execSQL(sql); + } + + @Override + public void execSQL(String sql, Object[] bindArgs) throws SQLException { + mDelegate.execSQL(sql, bindArgs); + } + + @Override + public boolean isReadOnly() { + return mDelegate.isReadOnly(); + } + + @Override + public boolean isOpen() { + return mDelegate.isOpen(); + } + + @Override + public boolean needUpgrade(int newVersion) { + return mDelegate.needUpgrade(newVersion); + } + + @Override + public String getPath() { + return mDelegate.getPath(); + } + + @Override + public void setLocale(Locale locale) { + mDelegate.setLocale(locale); + } + + @Override + public void setMaxSqlCacheSize(int cacheSize) { + mDelegate.setMaxSqlCacheSize(cacheSize); + } + + @Override + @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void setForeignKeyConstraintsEnabled(boolean enable) { + mDelegate.setForeignKeyConstraintsEnabled(enable); + } + + @Override + public boolean enableWriteAheadLogging() { + return mDelegate.enableWriteAheadLogging(); + } + + @Override + @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void disableWriteAheadLogging() { + mDelegate.disableWriteAheadLogging(); + } + + @Override + @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean isWriteAheadLoggingEnabled() { + return mDelegate.isWriteAheadLoggingEnabled(); + } + + @Override + public List> getAttachedDbs() { + return mDelegate.getAttachedDbs(); + } + + @Override + public boolean isDatabaseIntegrityOk() { + return mDelegate.isDatabaseIntegrityOk(); + } + + @Override + public void close() throws IOException { + mDelegate.close(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java new file mode 100644 index 00000000..d668c13c --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db.framework; + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteOpenHelper; +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Build; + +class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper { + private final OpenHelper mDelegate; + + FrameworkSQLiteOpenHelper(Context context, String name, + Callback callback) { + mDelegate = createDelegate(context, name, callback); + } + + private OpenHelper createDelegate(Context context, String name, Callback callback) { + final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1]; + return new OpenHelper(context, name, dbRef, callback); + } + + @Override + public String getDatabaseName() { + return mDelegate.getDatabaseName(); + } + + @Override + @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void setWriteAheadLoggingEnabled(boolean enabled) { + mDelegate.setWriteAheadLoggingEnabled(enabled); + } + + @Override + public SupportSQLiteDatabase getWritableDatabase() { + return mDelegate.getWritableSupportDatabase(); + } + + @Override + public SupportSQLiteDatabase getReadableDatabase() { + return mDelegate.getReadableSupportDatabase(); + } + + @Override + public void close() { + mDelegate.close(); + } + + static class OpenHelper extends SQLiteOpenHelper { + /** + * This is used as an Object reference so that we can access the wrapped database inside + * the constructor. SQLiteOpenHelper requires the error handler to be passed in the + * constructor. + */ + final FrameworkSQLiteDatabase[] mDbRef; + final Callback mCallback; + // see b/78359448 + private boolean mMigrated; + + OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef, + final Callback callback) { + super(context, name, null, callback.version, + new DatabaseErrorHandler() { + @Override + public void onCorruption(SQLiteDatabase dbObj) { + FrameworkSQLiteDatabase db = dbRef[0]; + if (db != null) { + callback.onCorruption(db); + } + } + }); + mCallback = callback; + mDbRef = dbRef; + } + + synchronized SupportSQLiteDatabase getWritableSupportDatabase() { + mMigrated = false; + SQLiteDatabase db = super.getWritableDatabase(); + if (mMigrated) { + // there might be a connection w/ stale structure, we should re-open. + close(); + return getWritableSupportDatabase(); + } + return getWrappedDb(db); + } + + synchronized SupportSQLiteDatabase getReadableSupportDatabase() { + mMigrated = false; + SQLiteDatabase db = super.getReadableDatabase(); + if (mMigrated) { + // there might be a connection w/ stale structure, we should re-open. + close(); + return getReadableSupportDatabase(); + } + return getWrappedDb(db); + } + + FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) { + FrameworkSQLiteDatabase dbRef = mDbRef[0]; + if (dbRef == null) { + dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase); + mDbRef[0] = dbRef; + } + return mDbRef[0]; + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + mCallback.onCreate(getWrappedDb(sqLiteDatabase)); + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { + mMigrated = true; + mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + mCallback.onConfigure(getWrappedDb(db)); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + mMigrated = true; + mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (!mMigrated) { + // if we've migrated, we'll re-open the db so we should not call the callback. + mCallback.onOpen(getWrappedDb(db)); + } + } + + @Override + public synchronized void close() { + super.close(); + mDbRef[0] = null; + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java new file mode 100644 index 00000000..ddbe3d6a --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db.framework; + +import android.arch.persistence.db.SupportSQLiteOpenHelper; + +/** + * Implements {@link SupportSQLiteOpenHelper.Factory} using the SQLite implementation in the + * framework. + */ +@SuppressWarnings("unused") +public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory { + @Override + public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) { + return new FrameworkSQLiteOpenHelper( + configuration.context, configuration.name, configuration.callback); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java new file mode 100644 index 00000000..0f935b38 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 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 android.arch.persistence.db.framework; + +import android.arch.persistence.db.SupportSQLiteProgram; +import android.database.sqlite.SQLiteProgram; + +/** + * An wrapper around {@link SQLiteProgram} to implement {@link SupportSQLiteProgram} API. + */ +class FrameworkSQLiteProgram implements SupportSQLiteProgram { + private final SQLiteProgram mDelegate; + + FrameworkSQLiteProgram(SQLiteProgram delegate) { + mDelegate = delegate; + } + + @Override + public void bindNull(int index) { + mDelegate.bindNull(index); + } + + @Override + public void bindLong(int index, long value) { + mDelegate.bindLong(index, value); + } + + @Override + public void bindDouble(int index, double value) { + mDelegate.bindDouble(index, value); + } + + @Override + public void bindString(int index, String value) { + mDelegate.bindString(index, value); + } + + @Override + public void bindBlob(int index, byte[] value) { + mDelegate.bindBlob(index, value); + } + + @Override + public void clearBindings() { + mDelegate.clearBindings(); + } + + @Override + public void close() { + mDelegate.close(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java new file mode 100644 index 00000000..9be7cd18 --- /dev/null +++ b/AndroidCompat/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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 android.arch.persistence.db.framework; + +import android.arch.persistence.db.SupportSQLiteStatement; +import android.database.sqlite.SQLiteStatement; + +/** + * Delegates all calls to a {@link SQLiteStatement}. + */ +class FrameworkSQLiteStatement extends FrameworkSQLiteProgram implements SupportSQLiteStatement { + private final SQLiteStatement mDelegate; + + /** + * Creates a wrapper around a framework {@link SQLiteStatement}. + * + * @param delegate The SQLiteStatement to delegate calls to. + */ + FrameworkSQLiteStatement(SQLiteStatement delegate) { + super(delegate); + mDelegate = delegate; + } + + @Override + public void execute() { + mDelegate.execute(); + } + + @Override + public int executeUpdateDelete() { + return mDelegate.executeUpdateDelete(); + } + + @Override + public long executeInsert() { + return mDelegate.executeInsert(); + } + + @Override + public long simpleQueryForLong() { + return mDelegate.simpleQueryForLong(); + } + + @Override + public String simpleQueryForString() { + return mDelegate.simpleQueryForString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/ComponentCallbacks.java b/AndroidCompat/src/main/java/android/content/ComponentCallbacks.java new file mode 100644 index 00000000..f030468b --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/ComponentCallbacks.java @@ -0,0 +1,64 @@ +/* + * 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 android.content; +import android.content.res.Configuration; +/** + * The set of callback APIs that are common to all application components + * ({@link android.app.Activity}, {@link android.app.Service}, + * {@link ContentProvider}, and {@link android.app.Application}). + * + *

Note: You should also implement the {@link + * ComponentCallbacks2} interface, which provides the {@link + * ComponentCallbacks2#onTrimMemory} callback to help your app manage its memory usage more + * effectively.

+ */ +public interface ComponentCallbacks { + /** + * Called by the system when the device configuration changes while your + * component is running. Note that, unlike activities, other components + * are never restarted when a configuration changes: they must always deal + * with the results of the change, such as by re-retrieving resources. + * + *

At the time that this function has been called, your Resources + * object will have been updated to return resource values matching the + * new configuration. + * + *

For more information, read Handling Runtime Changes. + * + * @param newConfig The new device configuration. + */ + void onConfigurationChanged(Configuration newConfig); + /** + * This is called when the overall system is running low on memory, and + * actively running processes should trim their memory usage. While + * the exact point at which this will be called is not defined, generally + * it will happen when all background process have been killed. + * That is, before reaching the point of killing processes hosting + * service and foreground UI that we would like to avoid killing. + * + *

You should implement this method to release + * any caches or other unnecessary resources you may be holding on to. + * The system will perform a garbage collection for you after returning from this method. + *

Preferably, you should implement {@link ComponentCallbacks2#onTrimMemory} from + * {@link ComponentCallbacks2} to incrementally unload your resources based on various + * levels of memory demands. That API is available for API level 14 and higher, so you should + * only use this {@link #onLowMemory} method as a fallback for older versions, which can be + * treated the same as {@link ComponentCallbacks2#onTrimMemory} with the {@link + * ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.

+ */ + void onLowMemory(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/ComponentCallbacks2.java b/AndroidCompat/src/main/java/android/content/ComponentCallbacks2.java new file mode 100644 index 00000000..bea8e926 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/ComponentCallbacks2.java @@ -0,0 +1,168 @@ +/* + * 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 android.content; +import android.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** + * Extended {@link ComponentCallbacks} interface with a new callback for + * finer-grained memory management. This interface is available in all application components + * ({@link android.app.Activity}, {@link android.app.Service}, + * {@link ContentProvider}, and {@link android.app.Application}). + * + *

You should implement {@link #onTrimMemory} to incrementally release memory based on current + * system constraints. Using this callback to release your resources helps provide a more + * responsive system overall, but also directly benefits the user experience for + * your app by allowing the system to keep your process alive longer. That is, + * if you don't trim your resources based on memory levels defined by this callback, + * the system is more likely to kill your process while it is cached in the least-recently used + * (LRU) list, thus requiring your app to restart and restore all state when the user returns to it. + * + *

The values provided by {@link #onTrimMemory} do not represent a single linear progression of + * memory limits, but provide you different types of clues about memory availability:

+ *
    + *
  • When your app is running: + *
      + *
    1. {@link #TRIM_MEMORY_RUNNING_MODERATE}
      The device is beginning to run low on memory. + * Your app is running and not killable. + *
    2. {@link #TRIM_MEMORY_RUNNING_LOW}
      The device is running much lower on memory. + * Your app is running and not killable, but please release unused resources to improve system + * performance (which directly impacts your app's performance). + *
    3. {@link #TRIM_MEMORY_RUNNING_CRITICAL}
      The device is running extremely low on memory. + * Your app is not yet considered a killable process, but the system will begin killing + * background processes if apps do not release resources, so you should release non-critical + * resources now to prevent performance degradation. + *
    + *
  • + *
  • When your app's visibility changes: + *
      + *
    1. {@link #TRIM_MEMORY_UI_HIDDEN}
      Your app's UI is no longer visible, so this is a good + * time to release large resources that are used only by your UI. + *
    + *
  • + *
  • When your app's process resides in the background LRU list: + *
      + *
    1. {@link #TRIM_MEMORY_BACKGROUND}
      The system is running low on memory and your process is + * near the beginning of the LRU list. Although your app process is not at a high risk of being + * killed, the system may already be killing processes in the LRU list, so you should release + * resources that are easy to recover so your process will remain in the list and resume + * quickly when the user returns to your app. + *
    2. {@link #TRIM_MEMORY_MODERATE}
      The system is running low on memory and your process is + * near the middle of the LRU list. If the system becomes further constrained for memory, there's a + * chance your process will be killed. + *
    3. {@link #TRIM_MEMORY_COMPLETE}
      The system is running low on memory and your process is + * one of the first to be killed if the system does not recover memory now. You should release + * absolutely everything that's not critical to resuming your app state. + *

      To support API levels lower than 14, you can use the {@link #onLowMemory} method as a + * fallback that's roughly equivalent to the {@link ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level. + *

    4. + *
    + *

    Note: When the system begins + * killing processes in the LRU list, although it primarily works bottom-up, it does give some + * consideration to which processes are consuming more memory and will thus provide more gains in + * memory if killed. So the less memory you consume while in the LRU list overall, the better + * your chances are to remain in the list and be able to quickly resume.

    + *
  • + *
+ *

More information about the different stages of a process lifecycle (such as what it means + * to be placed in the background LRU list) is provided in the Processes and Threads + * document. + */ +public interface ComponentCallbacks2 extends ComponentCallbacks { + /** @hide */ + @IntDef(prefix = { "TRIM_MEMORY_" }, value = { + TRIM_MEMORY_COMPLETE, + TRIM_MEMORY_MODERATE, + TRIM_MEMORY_BACKGROUND, + TRIM_MEMORY_UI_HIDDEN, + TRIM_MEMORY_RUNNING_CRITICAL, + TRIM_MEMORY_RUNNING_LOW, + TRIM_MEMORY_RUNNING_MODERATE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TrimMemoryLevel {} + /** + * Level for {@link #onTrimMemory(int)}: the process is nearing the end + * of the background LRU list, and if more memory isn't found soon it will + * be killed. + */ + static final int TRIM_MEMORY_COMPLETE = 80; + + /** + * Level for {@link #onTrimMemory(int)}: the process is around the middle + * of the background LRU list; freeing memory can help the system keep + * other processes running later in the list for better overall performance. + */ + static final int TRIM_MEMORY_MODERATE = 60; + + /** + * Level for {@link #onTrimMemory(int)}: the process has gone on to the + * LRU list. This is a good opportunity to clean up resources that can + * efficiently and quickly be re-built if the user returns to the app. + */ + static final int TRIM_MEMORY_BACKGROUND = 40; + + /** + * Level for {@link #onTrimMemory(int)}: the process had been showing + * a user interface, and is no longer doing so. Large allocations with + * the UI should be released at this point to allow memory to be better + * managed. + */ + static final int TRIM_MEMORY_UI_HIDDEN = 20; + /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running extremely low on memory + * and is about to not be able to keep any background processes running. + * Your running process should free up as many non-critical resources as it + * can to allow that memory to be used elsewhere. The next thing that + * will happen after this is {@link #onLowMemory()} called to report that + * nothing at all can be kept in the background, a situation that can start + * to notably impact the user. + */ + static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; + /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running low on memory. + * Your running process should free up unneeded resources to allow that + * memory to be used elsewhere. + */ + static final int TRIM_MEMORY_RUNNING_LOW = 10; + /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running moderately low on memory. + * Your running process may want to release some unneeded resources for + * use elsewhere. + */ + static final int TRIM_MEMORY_RUNNING_MODERATE = 5; + /** + * Called when the operating system has determined that it is a good + * time for a process to trim unneeded memory from its process. This will + * happen for example when it goes in the background and there is not enough + * memory to keep as many background processes running as desired. You + * should never compare to exact values of the level, since new intermediate + * values may be added -- you will typically want to compare if the value + * is greater or equal to a level you are interested in. + * + *

To retrieve the processes current trim level at any point, you can + * use {@link android.app.ActivityManager#getMyMemoryState + * ActivityManager.getMyMemoryState(RunningAppProcessInfo)}. + * + * @param level The context of the trim, giving a hint of the amount of + * trimming the application may like to perform. + */ + void onTrimMemory(@TrimMemoryLevel int level); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/ComponentName.java b/AndroidCompat/src/main/java/android/content/ComponentName.java new file mode 100644 index 00000000..2f2c438a --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/ComponentName.java @@ -0,0 +1,362 @@ +/* + * 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 android.content; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import java.io.PrintWriter; +import java.lang.Comparable; +/** + * Identifier for a specific application component + * ({@link android.app.Activity}, {@link android.app.Service}, + * {@link android.content.BroadcastReceiver}, or + * {@link android.content.ContentProvider}) that is available. Two + * pieces of information, encapsulated here, are required to identify + * a component: the package (a String) it exists in, and the class (a String) + * name inside of that package. + * + */ +public final class ComponentName implements Parcelable, Cloneable, Comparable { + private final String mPackage; + private final String mClass; + /** + * Create a new component identifier where the class name may be specified + * as either absolute or relative to the containing package. + * + *

Relative package names begin with a '.' character. For a package + * "com.example" and class name ".app.MyActivity" this method + * will return a ComponentName with the package "com.example"and class name + * "com.example.app.MyActivity". Fully qualified class names are also + * permitted.

+ * + * @param pkg the name of the package the component exists in + * @param cls the name of the class inside of pkg that implements + * the component + * @return the new ComponentName + */ + public static ComponentName createRelative(String pkg, String cls) { + if (TextUtils.isEmpty(cls)) { + throw new IllegalArgumentException("class name cannot be empty"); + } + final String fullName; + if (cls.charAt(0) == '.') { + // Relative to the package. Prepend the package name. + fullName = pkg + cls; + } else { + // Fully qualified package name. + fullName = cls; + } + return new ComponentName(pkg, fullName); + } + /** + * Create a new component identifier where the class name may be specified + * as either absolute or relative to the containing package. + * + *

Relative package names begin with a '.' character. For a package + * "com.example" and class name ".app.MyActivity" this method + * will return a ComponentName with the package "com.example"and class name + * "com.example.app.MyActivity". Fully qualified class names are also + * permitted.

+ * + * @param pkg a Context for the package implementing the component + * @param cls the name of the class inside of pkg that implements + * the component + * @return the new ComponentName + */ + public static ComponentName createRelative(Context pkg, String cls) { + return createRelative(pkg.getPackageName(), cls); + } + /** + * Create a new component identifier. + * + * @param pkg The name of the package that the component exists in. Can + * not be null. + * @param cls The name of the class inside of pkg that + * implements the component. Can not be null. + */ + public ComponentName(String pkg, String cls) { + if (pkg == null) throw new NullPointerException("package name is null"); + if (cls == null) throw new NullPointerException("class name is null"); + mPackage = pkg; + mClass = cls; + } + /** + * Create a new component identifier from a Context and class name. + * + * @param pkg A Context for the package implementing the component, + * from which the actual package name will be retrieved. + * @param cls The name of the class inside of pkg that + * implements the component. + */ + public ComponentName(Context pkg, String cls) { + if (cls == null) throw new NullPointerException("class name is null"); + mPackage = pkg.getPackageName(); + mClass = cls; + } + /** + * Create a new component identifier from a Context and Class object. + * + * @param pkg A Context for the package implementing the component, from + * which the actual package name will be retrieved. + * @param cls The Class object of the desired component, from which the + * actual class name will be retrieved. + */ + public ComponentName(Context pkg, Class cls) { + mPackage = pkg.getPackageName(); + mClass = cls.getName(); + } + public ComponentName clone() { + return new ComponentName(mPackage, mClass); + } + /** + * Return the package name of this component. + */ + public String getPackageName() { + return mPackage; + } + + /** + * Return the class name of this component. + */ + public String getClassName() { + return mClass; + } + + /** + * Return the class name, either fully qualified or in a shortened form + * (with a leading '.') if it is a suffix of the package. + */ + public String getShortClassName() { + if (mClass.startsWith(mPackage)) { + int PN = mPackage.length(); + int CN = mClass.length(); + if (CN > PN && mClass.charAt(PN) == '.') { + return mClass.substring(PN, CN); + } + } + return mClass; + } + + private static void appendShortClassName(StringBuilder sb, String packageName, + String className) { + if (className.startsWith(packageName)) { + int PN = packageName.length(); + int CN = className.length(); + if (CN > PN && className.charAt(PN) == '.') { + sb.append(className, PN, CN); + return; + } + } + sb.append(className); + } + private static void printShortClassName(PrintWriter pw, String packageName, + String className) { + if (className.startsWith(packageName)) { + int PN = packageName.length(); + int CN = className.length(); + if (CN > PN && className.charAt(PN) == '.') { + pw.write(className, PN, CN-PN); + return; + } + } + pw.print(className); + } + /** + * Return a String that unambiguously describes both the package and + * class names contained in the ComponentName. You can later recover + * the ComponentName from this string through + * {@link #unflattenFromString(String)}. + * + * @return Returns a new String holding the package and class names. This + * is represented as the package name, concatenated with a '/' and then the + * class name. + * + * @see #unflattenFromString(String) + */ + public String flattenToString() { + return mPackage + "/" + mClass; + } + + /** + * The same as {@link #flattenToString()}, but abbreviates the class + * name if it is a suffix of the package. The result can still be used + * with {@link #unflattenFromString(String)}. + * + * @return Returns a new String holding the package and class names. This + * is represented as the package name, concatenated with a '/' and then the + * class name. + * + * @see #unflattenFromString(String) + */ + public String flattenToShortString() { + StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length()); + appendShortString(sb, mPackage, mClass); + return sb.toString(); + } + /** @hide */ + public void appendShortString(StringBuilder sb) { + appendShortString(sb, mPackage, mClass); + } + /** @hide */ + public static void appendShortString(StringBuilder sb, String packageName, String className) { + sb.append(packageName).append('/'); + appendShortClassName(sb, packageName, className); + } + /** @hide */ + public static void printShortString(PrintWriter pw, String packageName, String className) { + pw.print(packageName); + pw.print('/'); + printShortClassName(pw, packageName, className); + } + /** + * Recover a ComponentName from a String that was previously created with + * {@link #flattenToString()}. It splits the string at the first '/', + * taking the part before as the package name and the part after as the + * class name. As a special convenience (to use, for example, when + * parsing component names on the command line), if the '/' is immediately + * followed by a '.' then the final class name will be the concatenation + * of the package name with the string following the '/'. Thus + * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah". + * + * @param str The String that was returned by flattenToString(). + * @return Returns a new ComponentName containing the package and class + * names that were encoded in str + * + * @see #flattenToString() + */ + public static ComponentName unflattenFromString(String str) { + int sep = str.indexOf('/'); + if (sep < 0 || (sep+1) >= str.length()) { + return null; + } + String pkg = str.substring(0, sep); + String cls = str.substring(sep+1); + if (cls.length() > 0 && cls.charAt(0) == '.') { + cls = pkg + cls; + } + return new ComponentName(pkg, cls); + } + + /** + * Return string representation of this class without the class's name + * as a prefix. + */ + public String toShortString() { + return "{" + mPackage + "/" + mClass + "}"; + } + @Override + public String toString() { + return "ComponentInfo{" + mPackage + "/" + mClass + "}"; + } + @Override + public boolean equals(Object obj) { + try { + if (obj != null) { + ComponentName other = (ComponentName)obj; + // Note: no null checks, because mPackage and mClass can + // never be null. + return mPackage.equals(other.mPackage) + && mClass.equals(other.mClass); + } + } catch (ClassCastException e) { + } + return false; + } + @Override + public int hashCode() { + return mPackage.hashCode() + mClass.hashCode(); + } + public int compareTo(ComponentName that) { + int v; + v = this.mPackage.compareTo(that.mPackage); + if (v != 0) { + return v; + } + return this.mClass.compareTo(that.mClass); + } + + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel out, int flags) { + out.writeString(mPackage); + out.writeString(mClass); + } + /** + * Write a ComponentName to a Parcel, handling null pointers. Must be + * read with {@link #readFromParcel(Parcel)}. + * + * @param c The ComponentName to be written. + * @param out The Parcel in which the ComponentName will be placed. + * + * @see #readFromParcel(Parcel) + */ + public static void writeToParcel(ComponentName c, Parcel out) { + if (c != null) { + c.writeToParcel(out, 0); + } else { + out.writeString(null); + } + } + + /** + * Read a ComponentName from a Parcel that was previously written + * with {@link #writeToParcel(ComponentName, Parcel)}, returning either + * a null or new object as appropriate. + * + * @param in The Parcel from which to read the ComponentName + * @return Returns a new ComponentName matching the previously written + * object, or null if a null had been written. + * + * @see #writeToParcel(ComponentName, Parcel) + */ + public static ComponentName readFromParcel(Parcel in) { + String pkg = in.readString(); + return pkg != null ? new ComponentName(pkg, in) : null; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public ComponentName createFromParcel(Parcel in) { + return new ComponentName(in); + } + public ComponentName[] newArray(int size) { + return new ComponentName[size]; + } + }; + /** + * Instantiate a new ComponentName from the data in a Parcel that was + * previously written with {@link #writeToParcel(Parcel, int)}. Note that you + * must not use this with data written by + * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible + * to handle a null ComponentObject here. + * + * @param in The Parcel containing the previously written ComponentName, + * positioned at the location in the buffer where it was written. + */ + public ComponentName(Parcel in) { + mPackage = in.readString(); + if (mPackage == null) throw new NullPointerException( + "package name is null"); + mClass = in.readString(); + if (mClass == null) throw new NullPointerException( + "class name is null"); + } + private ComponentName(String pkg, Parcel in) { + mPackage = pkg; + mClass = in.readString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/ContentValues.java b/AndroidCompat/src/main/java/android/content/ContentValues.java new file mode 100644 index 00000000..a2ee35af --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/ContentValues.java @@ -0,0 +1,473 @@ +/* + * 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 android.content; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +/** + * This class is used to store a set of values that the {@link ContentResolver} + * can process. + */ +public final class ContentValues implements Parcelable { + public static final String TAG = "ContentValues"; + /** Holds the actual values */ + private HashMap mValues; + /** + * Creates an empty set of values using the default initial size + */ + public ContentValues() { + // Choosing a default size of 8 based on analysis of typical + // consumption by applications. + mValues = new HashMap(8); + } + /** + * Creates an empty set of values using the given initial size + * + * @param size the initial size of the set of values + */ + public ContentValues(int size) { + mValues = new HashMap(size, 1.0f); + } + /** + * Creates a set of values copied from the given set + * + * @param from the values to copy + */ + public ContentValues(ContentValues from) { + mValues = new HashMap(from.mValues); + } + /** + * Creates a set of values copied from the given HashMap. This is used + * by the Parcel unmarshalling code. + * + * @param values the values to start with + * {@hide} + */ + private ContentValues(HashMap values) { + mValues = values; + } + @Override + public boolean equals(Object object) { + if (!(object instanceof ContentValues)) { + return false; + } + return mValues.equals(((ContentValues) object).mValues); + } + @Override + public int hashCode() { + return mValues.hashCode(); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, String value) { + mValues.put(key, value); + } + /** + * Adds all values from the passed in ContentValues. + * + * @param other the ContentValues from which to copy + */ + public void putAll(ContentValues other) { + mValues.putAll(other.mValues); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Byte value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Short value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Integer value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Long value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Float value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Double value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, Boolean value) { + mValues.put(key, value); + } + /** + * Adds a value to the set. + * + * @param key the name of the value to put + * @param value the data for the value to put + */ + public void put(String key, byte[] value) { + mValues.put(key, value); + } + /** + * Adds a null value to the set. + * + * @param key the name of the value to make null + */ + public void putNull(String key) { + mValues.put(key, null); + } + /** + * Returns the number of values. + * + * @return the number of values + */ + public int size() { + return mValues.size(); + } + /** + * Remove a single value. + * + * @param key the name of the value to remove + */ + public void remove(String key) { + mValues.remove(key); + } + /** + * Removes all values. + */ + public void clear() { + mValues.clear(); + } + /** + * Returns true if this object has the named value. + * + * @param key the value to check for + * @return {@code true} if the value is present, {@code false} otherwise + */ + public boolean containsKey(String key) { + return mValues.containsKey(key); + } + /** + * Gets a value. Valid value types are {@link String}, {@link Boolean}, + * {@link Number}, and {@code byte[]} implementations. + * + * @param key the value to get + * @return the data for the value, or {@code null} if the value is missing or if {@code null} + * was previously added with the given {@code key} + */ + public Object get(String key) { + return mValues.get(key); + } + /** + * Gets a value and converts it to a String. + * + * @param key the value to get + * @return the String for the value + */ + public String getAsString(String key) { + Object value = mValues.get(key); + return value != null ? value.toString() : null; + } + /** + * Gets a value and converts it to a Long. + * + * @param key the value to get + * @return the Long value, or {@code null} if the value is missing or cannot be converted + */ + public Long getAsLong(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).longValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Long.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Long value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to an Integer. + * + * @param key the value to get + * @return the Integer value, or {@code null} if the value is missing or cannot be converted + */ + public Integer getAsInteger(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).intValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Integer.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Integer value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to a Short. + * + * @param key the value to get + * @return the Short value, or {@code null} if the value is missing or cannot be converted + */ + public Short getAsShort(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).shortValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Short.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Short value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Short: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to a Byte. + * + * @param key the value to get + * @return the Byte value, or {@code null} if the value is missing or cannot be converted + */ + public Byte getAsByte(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).byteValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Byte.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Byte value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Byte: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to a Double. + * + * @param key the value to get + * @return the Double value, or {@code null} if the value is missing or cannot be converted + */ + public Double getAsDouble(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).doubleValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Double.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Double value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Double: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to a Float. + * + * @param key the value to get + * @return the Float value, or {@code null} if the value is missing or cannot be converted + */ + public Float getAsFloat(String key) { + Object value = mValues.get(key); + try { + return value != null ? ((Number) value).floatValue() : null; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + try { + return Float.valueOf(value.toString()); + } catch (NumberFormatException e2) { + Log.e(TAG, "Cannot parse Float value for " + value + " at key " + key); + return null; + } + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Float: " + value, e); + return null; + } + } + } + /** + * Gets a value and converts it to a Boolean. + * + * @param key the value to get + * @return the Boolean value, or {@code null} if the value is missing or cannot be converted + */ + public Boolean getAsBoolean(String key) { + Object value = mValues.get(key); + try { + return (Boolean) value; + } catch (ClassCastException e) { + if (value instanceof CharSequence) { + return Boolean.valueOf(value.toString()); + } else if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } else { + Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e); + return null; + } + } + } + /** + * Gets a value that is a byte array. Note that this method will not convert + * any other types to byte arrays. + * + * @param key the value to get + * @return the {@code byte[]} value, or {@code null} is the value is missing or not a + * {@code byte[]} + */ + public byte[] getAsByteArray(String key) { + Object value = mValues.get(key); + if (value instanceof byte[]) { + return (byte[]) value; + } else { + return null; + } + } + /** + * Returns a set of all of the keys and values + * + * @return a set of all of the keys and values + */ + public Set> valueSet() { + return mValues.entrySet(); + } + /** + * Returns a set of all of the keys + * + * @return a set of all of the keys + */ + public Set keySet() { + return mValues.keySet(); + } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @SuppressWarnings({"deprecation", "unchecked"}) + public ContentValues createFromParcel(Parcel in) { + // TODO - what ClassLoader should be passed to readHashMap? + HashMap values = in.readHashMap(null); + return new ContentValues(values); + } + public ContentValues[] newArray(int size) { + return new ContentValues[size]; + } + }; + public int describeContents() { + return 0; + } + @SuppressWarnings("deprecation") + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeMap(mValues); + } + /** + * Unsupported, here until we get proper bulk insert APIs. + * {@hide} + */ + @Deprecated + public void putStringArrayList(String key, ArrayList value) { + mValues.put(key, value); + } + /** + * Unsupported, here until we get proper bulk insert APIs. + * {@hide} + */ + @SuppressWarnings("unchecked") + @Deprecated + public ArrayList getStringArrayList(String key) { + return (ArrayList) mValues.get(key); + } + /** + * Returns a string containing a concise, human-readable description of this object. + * @return a printable representation of this object. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (String name : mValues.keySet()) { + String value = getAsString(name); + if (sb.length() > 0) sb.append(" "); + sb.append(name + "=" + value); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/Context.java b/AndroidCompat/src/main/java/android/content/Context.java new file mode 100644 index 00000000..6b6939ff --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/Context.java @@ -0,0 +1,4047 @@ +/* + * 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 android.content; + +import android.annotation.*; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.*; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.*; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.view.Display; +import android.view.DisplayAdjustments; +import android.view.ViewDebug; +import android.view.WindowManager; +import xyz.nulldev.androidcompat.res.RCompat; + +import java.io.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** + * Interface to global information about an application environment. This is + * an abstract class whose implementation is provided by + * the Android system. It + * allows access to application-specific resources and classes, as well as + * up-calls for application-level operations such as launching activities, + * broadcasting and receiving intents, etc. + */ +public abstract class Context { + /** + * File creation mode: the default mode, where the created file can only + * be accessed by the calling application (or all applications sharing the + * same user ID). + */ + public static final int MODE_PRIVATE = 0x0000; + /** + * File creation mode: allow all other applications to have read access to + * the created file. + *

+ * As of {@link android.os.Build.VERSION_CODES#N} attempting to use this + * mode will throw a {@link SecurityException}. + * + * @deprecated Creating world-readable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. + * @see android.support.v4.content.FileProvider + * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION + */ + @Deprecated + public static final int MODE_WORLD_READABLE = 0x0001; + /** + * File creation mode: allow all other applications to have write access to + * the created file. + *

+ * As of {@link android.os.Build.VERSION_CODES#N} attempting to use this + * mode will throw a {@link SecurityException}. + * + * @deprecated Creating world-writable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. + * @see android.support.v4.content.FileProvider + * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION + */ + @Deprecated + public static final int MODE_WORLD_WRITEABLE = 0x0002; + /** + * File creation mode: for use with {@link #openFileOutput}, if the file + * already exists then write data to the end of the existing file + * instead of erasing it. + * @see #openFileOutput + */ + public static final int MODE_APPEND = 0x8000; + /** + * SharedPreference loading flag: when set, the file on disk will + * be checked for modification even if the shared preferences + * instance is already loaded in this process. This behavior is + * sometimes desired in cases where the application has multiple + * processes, all writing to the same SharedPreferences file. + * Generally there are better forms of communication between + * processes, though. + * + *

This was the legacy (but undocumented) behavior in and + * before Gingerbread (Android 2.3) and this flag is implied when + * targetting such releases. For applications targetting SDK + * versions greater than Android 2.3, this flag must be + * explicitly set if desired. + * + * @see #getSharedPreferences + * + * @deprecated MODE_MULTI_PROCESS does not work reliably in + * some versions of Android, and furthermore does not provide any + * mechanism for reconciling concurrent modifications across + * processes. Applications should not attempt to use it. Instead, + * they should use an explicit cross-process data management + * approach such as {@link android.content.ContentProvider ContentProvider}. + */ + @Deprecated + public static final int MODE_MULTI_PROCESS = 0x0004; + /** + * Database open flag: when set, the database is opened with write-ahead + * logging enabled by default. + * + * @see #openOrCreateDatabase(String, int, CursorFactory) + * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler) + * @see SQLiteDatabase#enableWriteAheadLogging + */ + public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008; + /** + * Database open flag: when set, the database is opened without support for + * localized collators. + * + * @see #openOrCreateDatabase(String, int, CursorFactory) + * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler) + * @see SQLiteDatabase#NO_LOCALIZED_COLLATORS + */ + public static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010; + /** @hide */ + @IntDef(flag = true, + value = { + BIND_AUTO_CREATE, + BIND_DEBUG_UNBIND, + BIND_NOT_FOREGROUND, + BIND_ABOVE_CLIENT, + BIND_ALLOW_OOM_MANAGEMENT, + BIND_WAIVE_PRIORITY, + BIND_IMPORTANT, + BIND_ADJUST_WITH_ACTIVITY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BindServiceFlags {} + /** + * Flag for {@link #bindService}: automatically create the service as long + * as the binding exists. Note that while this will create the service, + * its {@link android.app.Service#onStartCommand} + * method will still only be called due to an + * explicit call to {@link #startService}. Even without that, though, + * this still provides you with access to the service object while the + * service is created. + * + *

Note that prior to {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, + * not supplying this flag would also impact how important the system + * consider's the target service's process to be. When set, the only way + * for it to be raised was by binding from a service in which case it will + * only be important when that activity is in the foreground. Now to + * achieve this behavior you must explicitly supply the new flag + * {@link #BIND_ADJUST_WITH_ACTIVITY}. For compatibility, old applications + * that don't specify {@link #BIND_AUTO_CREATE} will automatically have + * the flags {@link #BIND_WAIVE_PRIORITY} and + * {@link #BIND_ADJUST_WITH_ACTIVITY} set for them in order to achieve + * the same result. + */ + public static final int BIND_AUTO_CREATE = 0x0001; + /** + * Flag for {@link #bindService}: include debugging help for mismatched + * calls to unbind. When this flag is set, the callstack of the following + * {@link #unbindService} call is retained, to be printed if a later + * incorrect unbind call is made. Note that doing this requires retaining + * information about the binding that was made for the lifetime of the app, + * resulting in a leak -- this should only be used for debugging. + */ + public static final int BIND_DEBUG_UNBIND = 0x0002; + /** + * Flag for {@link #bindService}: don't allow this binding to raise + * the target service's process to the foreground scheduling priority. + * It will still be raised to at least the same memory priority + * as the client (so that its process will not be killable in any + * situation where the client is not killable), but for CPU scheduling + * purposes it may be left in the background. This only has an impact + * in the situation where the binding client is a foreground process + * and the target service is in a background process. + */ + public static final int BIND_NOT_FOREGROUND = 0x0004; + /** + * Flag for {@link #bindService}: indicates that the client application + * binding to this service considers the service to be more important than + * the app itself. When set, the platform will try to have the out of + * memory killer kill the app before it kills the service it is bound to, though + * this is not guaranteed to be the case. + */ + public static final int BIND_ABOVE_CLIENT = 0x0008; + /** + * Flag for {@link #bindService}: allow the process hosting the bound + * service to go through its normal memory management. It will be + * treated more like a running service, allowing the system to + * (temporarily) expunge the process if low on memory or for some other + * whim it may have, and being more aggressive about making it a candidate + * to be killed (and restarted) if running for a long time. + */ + public static final int BIND_ALLOW_OOM_MANAGEMENT = 0x0010; + /** + * Flag for {@link #bindService}: don't impact the scheduling or + * memory management priority of the target service's hosting process. + * Allows the service's process to be managed on the background LRU list + * just like a regular application process in the background. + */ + public static final int BIND_WAIVE_PRIORITY = 0x0020; + /** + * Flag for {@link #bindService}: this service is very important to + * the client, so should be brought to the foreground process level + * when the client is. Normally a process can only be raised to the + * visibility level by a client, even if that client is in the foreground. + */ + public static final int BIND_IMPORTANT = 0x0040; + /** + * Flag for {@link #bindService}: If binding from an activity, allow the + * target service's process importance to be raised based on whether the + * activity is visible to the user, regardless whether another flag is + * used to reduce the amount that the client process's overall importance + * is used to impact it. + */ + public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080; + /** + * @hide Flag for {@link #bindService}: allows application hosting service to manage whitelists + * such as temporary allowing a {@code PendingIntent} to bypass Power Save mode. + */ + public static final int BIND_ALLOW_WHITELIST_MANAGEMENT = 0x01000000; + /** + * @hide Flag for {@link #bindService}: Like {@link #BIND_FOREGROUND_SERVICE}, + * but only applies while the device is awake. + */ + public static final int BIND_FOREGROUND_SERVICE_WHILE_AWAKE = 0x02000000; + /** + * @hide Flag for {@link #bindService}: For only the case where the binding + * is coming from the system, set the process state to FOREGROUND_SERVICE + * instead of the normal maximum of IMPORTANT_FOREGROUND. That is, this is + * saying that the process shouldn't participate in the normal power reduction + * modes (removing network access etc). + */ + public static final int BIND_FOREGROUND_SERVICE = 0x04000000; + /** + * @hide Flag for {@link #bindService}: Treat the binding as hosting + * an activity, an unbinding as the activity going in the background. + * That is, when unbinding, the process when empty will go on the activity + * LRU list instead of the regular one, keeping it around more aggressively + * than it otherwise would be. This is intended for use with IMEs to try + * to keep IME processes around for faster keyboard switching. + */ + public static final int BIND_TREAT_LIKE_ACTIVITY = 0x08000000; + /** + * @hide An idea that is not yet implemented. + * Flag for {@link #bindService}: If binding from an activity, consider + * this service to be visible like the binding activity is. That is, + * it will be treated as something more important to keep around than + * invisible background activities. This will impact the number of + * recent activities the user can switch between without having them + * restart. There is no guarantee this will be respected, as the system + * tries to balance such requests from one app vs. the importantance of + * keeping other apps around. + */ + public static final int BIND_VISIBLE = 0x10000000; + /** + * @hide + * Flag for {@link #bindService}: Consider this binding to be causing the target + * process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes + * away. + */ + public static final int BIND_SHOWING_UI = 0x20000000; + /** + * Flag for {@link #bindService}: Don't consider the bound service to be + * visible, even if the caller is visible. + * @hide + */ + public static final int BIND_NOT_VISIBLE = 0x40000000; + /** + * Flag for {@link #bindService}: The service being bound is an + * {@link android.R.attr#isolatedProcess isolated}, + * {@link android.R.attr#externalService external} service. This binds the service into the + * calling application's package, rather than the package in which the service is declared. + *

+ * When using this flag, the code for the service being bound will execute under the calling + * application's package name and user ID. Because the service must be an isolated process, + * it will not have direct access to the application's data, though. + * + * The purpose of this flag is to allow applications to provide services that are attributed + * to the app using the service, rather than the application providing the service. + *

+ */ + public static final int BIND_EXTERNAL_SERVICE = 0x80000000; + /** + * Returns an AssetManager instance for the application's package. + *

+ * Note: Implementations of this method should return + * an AssetManager instance that is consistent with the Resources instance + * returned by {@link #getResources()}. For example, they should share the + * same {@link Configuration} object. + * + * @return an AssetManager instance for the application's package + * @see #getResources() + */ + public abstract AssetManager getAssets(); + /** + * Returns a Resources instance for the application's package. + *

+ * Note: Implementations of this method should return + * a Resources instance that is consistent with the AssetManager instance + * returned by {@link #getAssets()}. For example, they should share the + * same {@link Configuration} object. + * + * @return a Resources instance for the application's package + * @see #getAssets() + */ + public abstract Resources getResources(); + /** Return PackageManager instance to find global package information. */ + public abstract PackageManager getPackageManager(); + /** Return a ContentResolver instance for your application's package. */ + public abstract ContentResolver getContentResolver(); + /** + * Return the Looper for the main thread of the current process. This is + * the thread used to dispatch calls to application components (activities, + * services, etc). + *

+ * By definition, this method returns the same result as would be obtained + * by calling {@link Looper#getMainLooper() Looper.getMainLooper()}. + *

+ * + * @return The main looper. + */ + public abstract Looper getMainLooper(); + /** + * Return the context of the single, global Application object of the + * current process. This generally should only be used if you need a + * Context whose lifecycle is separate from the current context, that is + * tied to the lifetime of the process rather than the current component. + * + *

Consider for example how this interacts with + * {@link #registerReceiver(BroadcastReceiver, IntentFilter)}: + *

    + *
  • If used from an Activity context, the receiver is being registered + * within that activity. This means that you are expected to unregister + * before the activity is done being destroyed; in fact if you do not do + * so, the framework will clean up your leaked registration as it removes + * the activity and log an error. Thus, if you use the Activity context + * to register a receiver that is static (global to the process, not + * associated with an Activity instance) then that registration will be + * removed on you at whatever point the activity you used is destroyed. + *

  • If used from the Context returned here, the receiver is being + * registered with the global state associated with your application. Thus + * it will never be unregistered for you. This is necessary if the receiver + * is associated with static data, not a particular component. However + * using the ApplicationContext elsewhere can easily lead to serious leaks + * if you forget to unregister, unbind, etc. + *

+ */ + public abstract Context getApplicationContext(); + /** + * Add a new {@link ComponentCallbacks} to the base application of the + * Context, which will be called at the same times as the ComponentCallbacks + * methods of activities and other components are called. Note that you + * must be sure to use {@link #unregisterComponentCallbacks} when + * appropriate in the future; this will not be removed for you. + * + * @param callback The interface to call. This can be either a + * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface. + */ + public void registerComponentCallbacks(ComponentCallbacks callback) { + getApplicationContext().registerComponentCallbacks(callback); + } + /** + * Remove a {@link ComponentCallbacks} object that was previously registered + * with {@link #registerComponentCallbacks(ComponentCallbacks)}. + */ + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + getApplicationContext().unregisterComponentCallbacks(callback); + } + /** + * Return a localized, styled CharSequence from the application's package's + * default string table. + * + * @param resId Resource id for the CharSequence text + */ + public final CharSequence getText(@StringRes int resId) { + return getResources().getText(resId); + } + /** + * Returns a localized string from the application's package's + * default string table. + * + * @param resId Resource id for the string + * @return The string data associated with the resource, stripped of styled + * text information. + */ + @NonNull + public final String getString(@StringRes int resId) { + //Defer to custom R.java + return RCompat.getString(resId); + } + /** + * Returns a localized formatted string from the application's package's + * default string table, substituting the format arguments as defined in + * {@link java.util.Formatter} and {@link java.lang.String#format}. + * + * @param resId Resource id for the format string + * @param formatArgs The format arguments that will be used for + * substitution. + * @return The string data associated with the resource, formatted and + * stripped of styled text information. + */ + @NonNull + public final String getString(@StringRes int resId, Object... formatArgs) { + //Defer to custom R.java and format + return String.format(getString(resId), formatArgs); + } + /** + * Returns a color associated with a particular resource ID and styled for + * the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return A single color value in the form 0xAARRGGBB. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @ColorInt + public final int getColor(@ColorRes int id) { + return getResources().getColor(id, getTheme()); + } + /** + * Returns a drawable object associated with a particular resource ID and + * styled for the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return An object that can be used to draw this resource, or + * {@code null} if the resource could not be resolved. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @Nullable + public final Drawable getDrawable(@DrawableRes int id) { + return getResources().getDrawable(id, getTheme()); + } + /** + * Returns a color state list associated with a particular resource ID and + * styled for the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return A color state list, or {@code null} if the resource could not be + * resolved. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @Nullable + public final ColorStateList getColorStateList(@ColorRes int id) { + return getResources().getColorStateList(id, getTheme()); + } + /** + * Set the base theme for this context. Note that this should be called + * before any views are instantiated in the Context (for example before + * calling {@link android.app.Activity#setContentView} or + * {@link android.view.LayoutInflater#inflate}). + * + * @param resid The style resource describing the theme. + */ + public abstract void setTheme(@StyleRes int resid); + /** @hide Needed for some internal implementation... not public because + * you can't assume this actually means anything. */ + public int getThemeResId() { + return 0; + } + /** + * Return the Theme object associated with this Context. + */ + @ViewDebug.ExportedProperty(deepExport = true) + public abstract Resources.Theme getTheme(); + /** + * Retrieve styled attribute information in this Context's theme. See + * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])} + * for more information. + * + * @see android.content.res.Resources.Theme#obtainStyledAttributes(int[]) + */ + public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { + return getTheme().obtainStyledAttributes(attrs); + } + /** + * Retrieve styled attribute information in this Context's theme. See + * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])} + * for more information. + * + * @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[]) + */ + public final TypedArray obtainStyledAttributes( + @StyleRes int resid, @StyleableRes int[] attrs) throws Resources.NotFoundException { + return getTheme().obtainStyledAttributes(resid, attrs); + } + /** + * Retrieve styled attribute information in this Context's theme. See + * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)} + * for more information. + * + * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) + */ + public final TypedArray obtainStyledAttributes( + AttributeSet set, @StyleableRes int[] attrs) { + return getTheme().obtainStyledAttributes(set, attrs, 0, 0); + } + /** + * Retrieve styled attribute information in this Context's theme. See + * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)} + * for more information. + * + * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) + */ + public final TypedArray obtainStyledAttributes( + AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, + @StyleRes int defStyleRes) { + return getTheme().obtainStyledAttributes( + set, attrs, defStyleAttr, defStyleRes); + } + /** + * Return a class loader you can use to retrieve classes in this package. + */ + public abstract ClassLoader getClassLoader(); + /** Return the name of this application's package. */ + public abstract String getPackageName(); + /** @hide Return the name of the base context this context is derived from. */ + public abstract String getBasePackageName(); + /** @hide Return the package name that should be used for app ops calls from + * this context. This is the same as {@link #getBasePackageName()} except in + * cases where system components are loaded into other app processes, in which + * case this will be the name of the primary package in that process (so that app + * ops uid verification will work with the name). */ + public abstract String getOpPackageName(); + /** Return the full application info for this context's package. */ + public abstract ApplicationInfo getApplicationInfo(); + /** + * Return the full path to this context's primary Android package. + * The Android package is a ZIP file which contains the application's + * primary resources. + * + *

Note: this is not generally useful for applications, since they should + * not be directly accessing the file system. + * + * @return String Path to the resources. + */ + public abstract String getPackageResourcePath(); + /** + * Return the full path to this context's primary Android package. + * The Android package is a ZIP file which contains application's + * primary code and assets. + * + *

Note: this is not generally useful for applications, since they should + * not be directly accessing the file system. + * + * @return String Path to the code and assets. + */ + public abstract String getPackageCodePath(); + /** + * @hide + * @deprecated use {@link #getSharedPreferencesPath(String)} + */ + @Deprecated + public File getSharedPrefsFile(String name) { + return getSharedPreferencesPath(name); + } + /** + * Retrieve and hold the contents of the preferences file 'name', returning + * a SharedPreferences through which you can retrieve and modify its + * values. Only one instance of the SharedPreferences object is returned + * to any callers for the same name, meaning they will see each other's + * edits as soon as they are made. + * + * @param name Desired preferences file. If a preferences file by this name + * does not exist, it will be created when you retrieve an + * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()). + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. + * + * @return The single {@link SharedPreferences} instance that can be used + * to retrieve and modify the preference values. + * + * @see #MODE_PRIVATE + */ + public abstract SharedPreferences getSharedPreferences(String name, int mode); + /** + * Retrieve and hold the contents of the preferences file, returning + * a SharedPreferences through which you can retrieve and modify its + * values. Only one instance of the SharedPreferences object is returned + * to any callers for the same name, meaning they will see each other's + * edits as soon as they are made. + * + * @param file Desired preferences file. If a preferences file by this name + * does not exist, it will be created when you retrieve an + * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()). + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. + * + * @return The single {@link SharedPreferences} instance that can be used + * to retrieve and modify the preference values. + * + * @see #getSharedPreferencesPath(String) + * @see #MODE_PRIVATE + * @removed + */ + public abstract SharedPreferences getSharedPreferences(File file, int mode); + /** + * Move an existing shared preferences file from the given source storage + * context to this context. This is typically used to migrate data between + * storage locations after an upgrade, such as moving to device protected + * storage. + * + * @param sourceContext The source context which contains the existing + * shared preferences to move. + * @param name The name of the shared preferences file. + * @return {@code true} if the move was successful or if the shared + * preferences didn't exist in the source context, otherwise + * {@code false}. + * @see #createDeviceProtectedStorageContext() + */ + public abstract boolean moveSharedPreferencesFrom(Context sourceContext, String name); + /** @removed */ + @Deprecated + public boolean migrateSharedPreferencesFrom(Context sourceContext, String name) { + return moveSharedPreferencesFrom(sourceContext, name); + } + /** + * Delete an existing shared preferences file. + * + * @param name The name (unique in the application package) of the shared + * preferences file. + * @return {@code true} if the shared preferences file was successfully + * deleted; else {@code false}. + * @see #getSharedPreferences(String, int) + */ + public abstract boolean deleteSharedPreferences(String name); + /** + * Open a private file associated with this Context's application package + * for reading. + * + * @param name The name of the file to open; can not contain path + * separators. + * + * @return The resulting {@link FileInputStream}. + * + * @see #openFileOutput + * @see #fileList + * @see #deleteFile + * @see java.io.FileInputStream#FileInputStream(String) + */ + public abstract FileInputStream openFileInput(String name) + throws FileNotFoundException; + /** + * Open a private file associated with this Context's application package + * for writing. Creates the file if it doesn't already exist. + *

+ * No additional permissions are required for the calling app to read or + * write the returned file. + * + * @param name The name of the file to open; can not contain path + * separators. + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. Use {@link #MODE_APPEND} to append to an + * existing file. + * @return The resulting {@link FileOutputStream}. + * @see #MODE_APPEND + * @see #MODE_PRIVATE + * @see #openFileInput + * @see #fileList + * @see #deleteFile + * @see java.io.FileOutputStream#FileOutputStream(String) + */ + public abstract FileOutputStream openFileOutput(String name, int mode) + throws FileNotFoundException; + /** + * Delete the given private file associated with this Context's + * application package. + * + * @param name The name of the file to delete; can not contain path + * separators. + * + * @return {@code true} if the file was successfully deleted; else + * {@code false}. + * + * @see #openFileInput + * @see #openFileOutput + * @see #fileList + * @see java.io.File#delete() + */ + public abstract boolean deleteFile(String name); + /** + * Returns the absolute path on the filesystem where a file created with + * {@link #openFileOutput} is stored. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + * + * @param name The name of the file for which you would like to get + * its path. + * + * @return An absolute path to the given file. + * + * @see #openFileOutput + * @see #getFilesDir + * @see #getDir + */ + public abstract File getFileStreamPath(String name); + /** + * Returns the absolute path on the filesystem where a file created with + * {@link #getSharedPreferences(String, int)} is stored. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + * + * @param name The name of the shared preferences for which you would like + * to get a path. + * @return An absolute path to the given file. + * @see #getSharedPreferences(String, int) + * @removed + */ + public abstract File getSharedPreferencesPath(String name); + /** + * Returns the absolute path to the directory on the filesystem where all + * private files belonging to this app are stored. Apps should not use this + * path directly; they should instead use {@link #getFilesDir()}, + * {@link #getCacheDir()}, {@link #getDir(String, int)}, or other storage + * APIs on this class. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. + * + * @see ApplicationInfo#dataDir + */ + public abstract File getDataDir(); + /** + * Returns the absolute path to the directory on the filesystem where files + * created with {@link #openFileOutput} are stored. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. + * + * @return The path of the directory holding application files. + * @see #openFileOutput + * @see #getFileStreamPath + * @see #getDir + */ + public abstract File getFilesDir(); + /** + * Returns the absolute path to the directory on the filesystem similar to + * {@link #getFilesDir()}. The difference is that files placed under this + * directory will be excluded from automatic backup to remote storage. See + * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion + * of the automatic backup mechanism in Android. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. + * + * @return The path of the directory holding application files that will not + * be automatically backed up to remote storage. + * @see #openFileOutput + * @see #getFileStreamPath + * @see #getDir + * @see android.app.backup.BackupAgent + */ + public abstract File getNoBackupFilesDir(); + /** + * Returns the absolute path to the directory on the primary shared/external + * storage device where the application can place persistent files it owns. + * These files are internal to the applications, and not typically visible + * to the user as media. + *

+ * This is like {@link #getFilesDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * If a shared storage device is emulated (as determined by + * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * backed by a private user data partition, which means there is little + * benefit to storing data here instead of the private directories returned + * by {@link #getFilesDir()}, etc. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging to + * other packages, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + *

+ * On devices with multiple users (as described by {@link UserManager}), + * each user has their own isolated shared storage. Applications only have + * access to the shared storage for the user they're running as. + *

+ * The returned path may change over time if different shared storage media + * is inserted, so only relative paths should be persisted. + *

+ * Here is an example of typical code to manipulate a file in an + * application's shared storage: + *

+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * private_file} + *

+ * If you supply a non-null type to this function, the returned + * file will be a path to a sub-directory of the given type. Though these + * files are not automatically scanned by the media scanner, you can + * explicitly add them to the media database with + * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[], android.media.MediaScannerConnection.OnScanCompletedListener) + * MediaScannerConnection.scanFile}. Note that this is not the same as + * {@link android.os.Environment#getExternalStoragePublicDirectory + * Environment.getExternalStoragePublicDirectory()}, which provides + * directories of media shared by all applications. The directories returned + * here are owned by the application, and their contents will be removed + * when the application is uninstalled. Unlike + * {@link android.os.Environment#getExternalStoragePublicDirectory + * Environment.getExternalStoragePublicDirectory()}, the directory returned + * here will be automatically created for you. + *

+ * Here is an example of typical code to manipulate a picture in an + * application's shared storage and add it to the media database: + *

+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java + * private_picture} + * + * @param type The type of files directory to return. May be {@code null} + * for the root of the files directory or one of the following + * constants for a subdirectory: + * {@link android.os.Environment#DIRECTORY_MUSIC}, + * {@link android.os.Environment#DIRECTORY_PODCASTS}, + * {@link android.os.Environment#DIRECTORY_RINGTONES}, + * {@link android.os.Environment#DIRECTORY_ALARMS}, + * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS}, + * {@link android.os.Environment#DIRECTORY_PICTURES}, or + * {@link android.os.Environment#DIRECTORY_MOVIES}. + * @return the absolute path to application-specific directory. May return + * {@code null} if shared storage is not currently available. + * @see #getFilesDir + * @see #getExternalFilesDirs(String) + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + @Nullable + public abstract File getExternalFilesDir(@Nullable String type); + /** + * Returns absolute paths to application-specific directories on all + * shared/external storage devices where the application can place + * persistent files it owns. These files are internal to the application, + * and not typically visible to the user as media. + *

+ * This is like {@link #getFilesDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * If a shared storage device is emulated (as determined by + * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * backed by a private user data partition, which means there is little + * benefit to storing data here instead of the private directories returned + * by {@link #getFilesDir()}, etc. + *

+ * Shared storage devices returned here are considered a stable part of the + * device, including physical media slots under a protective cover. The + * returned paths do not include transient devices, such as USB flash drives + * connected to handheld devices. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. Write access outside of these paths + * on secondary external storage devices is not available. + *

+ * The returned path may change over time if different shared storage media + * is inserted, so only relative paths should be persisted. + * + * @param type The type of files directory to return. May be {@code null} + * for the root of the files directory or one of the following + * constants for a subdirectory: + * {@link android.os.Environment#DIRECTORY_MUSIC}, + * {@link android.os.Environment#DIRECTORY_PODCASTS}, + * {@link android.os.Environment#DIRECTORY_RINGTONES}, + * {@link android.os.Environment#DIRECTORY_ALARMS}, + * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS}, + * {@link android.os.Environment#DIRECTORY_PICTURES}, or + * {@link android.os.Environment#DIRECTORY_MOVIES}. + * @return the absolute paths to application-specific directories. Some + * individual paths may be {@code null} if that shared storage is + * not currently available. The first path returned is the same as + * {@link #getExternalFilesDir(String)}. + * @see #getExternalFilesDir(String) + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + public abstract File[] getExternalFilesDirs(String type); + /** + * Return the primary shared/external storage directory where this + * application's OBB files (if there are any) can be found. Note if the + * application does not have any OBB files, this directory may not exist. + *

+ * This is like {@link #getFilesDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging to + * other packages, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + *

+ * On devices with multiple users (as described by {@link UserManager}), + * multiple users may share the same OBB storage location. Applications + * should ensure that multiple instances running under different users don't + * interfere with each other. + * + * @return the absolute path to application-specific directory. May return + * {@code null} if shared storage is not currently available. + * @see #getObbDirs() + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + public abstract File getObbDir(); + /** + * Returns absolute paths to application-specific directories on all + * shared/external storage devices where the application's OBB files (if + * there are any) can be found. Note if the application does not have any + * OBB files, these directories may not exist. + *

+ * This is like {@link #getFilesDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * Shared storage devices returned here are considered a stable part of the + * device, including physical media slots under a protective cover. The + * returned paths do not include transient devices, such as USB flash drives + * connected to handheld devices. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. Write access outside of these paths + * on secondary external storage devices is not available. + * + * @return the absolute paths to application-specific directories. Some + * individual paths may be {@code null} if that shared storage is + * not currently available. The first path returned is the same as + * {@link #getObbDir()} + * @see #getObbDir() + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + public abstract File[] getObbDirs(); + /** + * Returns the absolute path to the application specific cache directory on + * the filesystem. These files will be ones that get deleted first when the + * device runs low on storage. There is no guarantee when these files will + * be deleted. + *

+ * Note: you should not rely on the system deleting these + * files for you; you should always have a reasonable maximum, such as 1 MB, + * for the amount of space you consume with cache files, and prune those + * files when exceeding that space. If your app requires a larger + * cache (larger than 1 MB), you should use {@link #getExternalCacheDir()} + * instead. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * Apps require no extra permissions to read or write to the returned path, + * since this path lives in their private storage. + * + * @return The path of the directory holding application cache files. + * @see #openFileOutput + * @see #getFileStreamPath + * @see #getDir + * @see #getExternalCacheDir + */ + public abstract File getCacheDir(); + /** + * Returns the absolute path to the application specific cache directory on + * the filesystem designed for storing cached code. The system will delete + * any files stored in this location both when your specific application is + * upgraded, and when the entire platform is upgraded. + *

+ * This location is optimal for storing compiled or optimized code generated + * by your application at runtime. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * Apps require no extra permissions to read or write to the returned path, + * since this path lives in their private storage. + * + * @return The path of the directory holding application code cache files. + */ + public abstract File getCodeCacheDir(); + /** + * Returns absolute path to application-specific directory on the primary + * shared/external storage device where the application can place cache + * files it owns. These files are internal to the application, and not + * typically visible to the user as media. + *

+ * This is like {@link #getCacheDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • The platform does not always monitor the space available in shared + * storage, and thus may not automatically delete these files. Apps should + * always manage the maximum space used in this location. Currently the only + * time files here will be deleted by the platform is when running on + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and + * {@link Environment#isExternalStorageEmulated(File)} returns true. + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * If a shared storage device is emulated (as determined by + * {@link Environment#isExternalStorageEmulated(File)}), its contents are + * backed by a private user data partition, which means there is little + * benefit to storing data here instead of the private directory returned by + * {@link #getCacheDir()}. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to read or write to the returned path; it's always + * accessible to the calling app. This only applies to paths generated for + * package name of the calling application. To access paths belonging to + * other packages, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required. + *

+ * On devices with multiple users (as described by {@link UserManager}), + * each user has their own isolated shared storage. Applications only have + * access to the shared storage for the user they're running as. + *

+ * The returned path may change over time if different shared storage media + * is inserted, so only relative paths should be persisted. + * + * @return the absolute path to application-specific directory. May return + * {@code null} if shared storage is not currently available. + * @see #getCacheDir + * @see #getExternalCacheDirs() + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + @Nullable + public abstract File getExternalCacheDir(); + /** + * Returns absolute paths to application-specific directories on all + * shared/external storage devices where the application can place cache + * files it owns. These files are internal to the application, and not + * typically visible to the user as media. + *

+ * This is like {@link #getCacheDir()} in that these files will be deleted + * when the application is uninstalled, however there are some important + * differences: + *

    + *
  • The platform does not always monitor the space available in shared + * storage, and thus may not automatically delete these files. Apps should + * always manage the maximum space used in this location. Currently the only + * time files here will be deleted by the platform is when running on + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and + * {@link Environment#isExternalStorageEmulated(File)} returns true. + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * If a shared storage device is emulated (as determined by + * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * backed by a private user data partition, which means there is little + * benefit to storing data here instead of the private directory returned by + * {@link #getCacheDir()}. + *

+ * Shared storage devices returned here are considered a stable part of the + * device, including physical media slots under a protective cover. The + * returned paths do not include transient devices, such as USB flash drives + * connected to handheld devices. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. Write access outside of these paths + * on secondary external storage devices is not available. + *

+ * The returned paths may change over time if different shared storage media + * is inserted, so only relative paths should be persisted. + * + * @return the absolute paths to application-specific directories. Some + * individual paths may be {@code null} if that shared storage is + * not currently available. The first path returned is the same as + * {@link #getExternalCacheDir()}. + * @see #getExternalCacheDir() + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + public abstract File[] getExternalCacheDirs(); + /** + * Returns absolute paths to application-specific directories on all + * shared/external storage devices where the application can place media + * files. These files are scanned and made available to other apps through + * {@link MediaStore}. + *

+ * This is like {@link #getExternalFilesDirs} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + *

    + *
  • Shared storage may not always be available, since removable media can + * be ejected by the user. Media state can be checked using + * {@link Environment#getExternalStorageState(File)}. + *
  • There is no security enforced with these files. For example, any + * application holding + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to + * these files. + *
+ *

+ * Shared storage devices returned here are considered a stable part of the + * device, including physical media slots under a protective cover. The + * returned paths do not include transient devices, such as USB flash drives + * connected to handheld devices. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. Write access outside of these paths + * on secondary external storage devices is not available. + *

+ * The returned paths may change over time if different shared storage media + * is inserted, so only relative paths should be persisted. + * + * @return the absolute paths to application-specific directories. Some + * individual paths may be {@code null} if that shared storage is + * not currently available. + * @see Environment#getExternalStorageState(File) + * @see Environment#isExternalStorageEmulated(File) + * @see Environment#isExternalStorageRemovable(File) + */ + public abstract File[] getExternalMediaDirs(); + /** + * Returns an array of strings naming the private files associated with + * this Context's application package. + * + * @return Array of strings naming the private files. + * + * @see #openFileInput + * @see #openFileOutput + * @see #deleteFile + */ + public abstract String[] fileList(); + /** + * Retrieve, creating if needed, a new directory in which the application + * can place its own custom data files. You can use the returned File + * object to create and access files in this directory. Note that files + * created through a File object will only be accessible by your own + * application; you can only set the mode of the entire directory, not + * of individual files. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * Apps require no extra permissions to read or write to the returned path, + * since this path lives in their private storage. + * + * @param name Name of the directory to retrieve. This is a directory + * that is created as part of your application data. + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. + * + * @return A {@link File} object for the requested directory. The directory + * will have been created if it does not already exist. + * + * @see #openFileOutput(String, int) + */ + public abstract File getDir(String name, int mode); + /** + * Open a new private SQLiteDatabase associated with this Context's + * application package. Create the database file if it doesn't exist. + * + * @param name The name (unique in the application package) of the database. + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. Use + * {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead + * logging by default. Use {@link #MODE_NO_LOCALIZED_COLLATORS} + * to disable localized collators. + * @param factory An optional factory class that is called to instantiate a + * cursor when query is called. + * @return The contents of a newly created database with the given name. + * @throws android.database.sqlite.SQLiteException if the database file + * could not be opened. + * @see #MODE_PRIVATE + * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING + * @see #MODE_NO_LOCALIZED_COLLATORS + * @see #deleteDatabase + */ + public abstract SQLiteDatabase openOrCreateDatabase(String name, + int mode, CursorFactory factory); + /** + * Open a new private SQLiteDatabase associated with this Context's + * application package. Creates the database file if it doesn't exist. + *

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

+ * + * @param name The name (unique in the application package) of the database. + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation. Use + * {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead + * logging by default. Use {@link #MODE_NO_LOCALIZED_COLLATORS} + * to disable localized collators. + * @param factory An optional factory class that is called to instantiate a + * cursor when query is called. + * @param errorHandler the {@link DatabaseErrorHandler} to be used when + * sqlite reports database corruption. if null, + * {@link android.database.DefaultDatabaseErrorHandler} is + * assumed. + * @return The contents of a newly created database with the given name. + * @throws android.database.sqlite.SQLiteException if the database file + * could not be opened. + * @see #MODE_PRIVATE + * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING + * @see #MODE_NO_LOCALIZED_COLLATORS + * @see #deleteDatabase + */ + public abstract SQLiteDatabase openOrCreateDatabase(String name, + int mode, CursorFactory factory, + @Nullable DatabaseErrorHandler errorHandler); + /** + * Move an existing database file from the given source storage context to + * this context. This is typically used to migrate data between storage + * locations after an upgrade, such as migrating to device protected + * storage. + *

+ * The database must be closed before being moved. + * + * @param sourceContext The source context which contains the existing + * database to move. + * @param name The name of the database file. + * @return {@code true} if the move was successful or if the database didn't + * exist in the source context, otherwise {@code false}. + * @see #createDeviceProtectedStorageContext() + */ + public abstract boolean moveDatabaseFrom(Context sourceContext, String name); + /** @removed */ + @Deprecated + public boolean migrateDatabaseFrom(Context sourceContext, String name) { + return moveDatabaseFrom(sourceContext, name); + } + /** + * Delete an existing private SQLiteDatabase associated with this Context's + * application package. + * + * @param name The name (unique in the application package) of the + * database. + * + * @return {@code true} if the database was successfully deleted; else {@code false}. + * + * @see #openOrCreateDatabase + */ + public abstract boolean deleteDatabase(String name); + /** + * Returns the absolute path on the filesystem where a database created with + * {@link #openOrCreateDatabase} is stored. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + * + * @param name The name of the database for which you would like to get + * its path. + * + * @return An absolute path to the given database. + * + * @see #openOrCreateDatabase + */ + public abstract File getDatabasePath(String name); + /** + * Returns an array of strings naming the private databases associated with + * this Context's application package. + * + * @return Array of strings naming the private databases. + * + * @see #openOrCreateDatabase + * @see #deleteDatabase + */ + public abstract String[] databaseList(); + /** + * @deprecated Use {@link android.app.WallpaperManager#getDrawable + * WallpaperManager.get()} instead. + */ + @Deprecated + public abstract Drawable getWallpaper(); + /** + * @deprecated Use {@link android.app.WallpaperManager#peekDrawable + * WallpaperManager.peek()} instead. + */ + @Deprecated + public abstract Drawable peekWallpaper(); + /** + * @deprecated Use {@link android.app.WallpaperManager#getDesiredMinimumWidth() + * WallpaperManager.getDesiredMinimumWidth()} instead. + */ + @Deprecated + public abstract int getWallpaperDesiredMinimumWidth(); + /** + * @deprecated Use {@link android.app.WallpaperManager#getDesiredMinimumHeight() + * WallpaperManager.getDesiredMinimumHeight()} instead. + */ + @Deprecated + public abstract int getWallpaperDesiredMinimumHeight(); + /** + * @deprecated Use {@link android.app.WallpaperManager#setBitmap(Bitmap) + * WallpaperManager.set()} instead. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#SET_WALLPAPER}. + */ + @Deprecated + public abstract void setWallpaper(Bitmap bitmap) throws IOException; + /** + * @deprecated Use {@link android.app.WallpaperManager#setStream(InputStream) + * WallpaperManager.set()} instead. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#SET_WALLPAPER}. + */ + @Deprecated + public abstract void setWallpaper(InputStream data) throws IOException; + /** + * @deprecated Use {@link android.app.WallpaperManager#clear + * WallpaperManager.clear()} instead. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#SET_WALLPAPER}. + */ + @Deprecated + public abstract void clearWallpaper() throws IOException; + /** + * Same as {@link #startActivity(Intent, Bundle)} with no options + * specified. + * + * @param intent The description of the activity to start. + * + * @throws ActivityNotFoundException   + *` + * @see #startActivity(Intent, Bundle) + * @see PackageManager#resolveActivity + */ + public abstract void startActivity(@RequiresPermission Intent intent); + /** + * Version of {@link #startActivity(Intent)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS_FULL permission. + * @param intent The description of the activity to start. + * @param user The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException   + * @hide + */ + public void startActivityAsUser(@RequiresPermission Intent intent, UserHandle user) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Launch a new activity. You will not receive any information about when + * the activity exits. + * + *

Note that if this method is being called from outside of an + * {@link android.app.Activity} Context, then the Intent must include + * the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag. This is because, + * without being started from an existing Activity, there is no existing + * task in which to place the new activity and thus it needs to be placed + * in its own separate task. + * + *

This method throws {@link ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intent The description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * + * @throws ActivityNotFoundException   + * + * @see #startActivity(Intent) + * @see PackageManager#resolveActivity + */ + public abstract void startActivity(@RequiresPermission Intent intent, + @Nullable Bundle options); + /** + * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS_FULL permission. + * @param intent The description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * @param userId The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException   + * @hide + */ + public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options, + UserHandle userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Version of {@link #startActivity(Intent, Bundle)} that returns a result to the caller. This + * is only supported for Views and Fragments. + * @param who The identifier for the calling element that will receive the result. + * @param intent The intent to start. + * @param requestCode The code that will be returned with onActivityResult() identifying this + * request. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * @hide + */ + public void startActivityForResult( + @NonNull String who, Intent intent, int requestCode, @Nullable Bundle options) { + throw new RuntimeException("This method is only implemented for Activity-based Contexts. " + + "Check canStartActivityForResult() before calling."); + } + /** + * Identifies whether this Context instance will be able to process calls to + * {@link #startActivityForResult(String, Intent, int, Bundle)}. + * @hide + */ + public boolean canStartActivityForResult() { + return false; + } + /** + * Same as {@link #startActivities(Intent[], Bundle)} with no options + * specified. + * + * @param intents An array of Intents to be started. + * + * @throws ActivityNotFoundException   + * + * @see #startActivities(Intent[], Bundle) + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(@RequiresPermission Intent[] intents); + /** + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + *

This method throws {@link ActivityNotFoundException} + * if there was no Activity found for any given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws ActivityNotFoundException   + * + * @see #startActivities(Intent[]) + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(@RequiresPermission Intent[] intents, Bundle options); + /** + * @hide + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + *

This method throws {@link ActivityNotFoundException} + * if there was no Activity found for any given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * @param userHandle The user for whom to launch the activities + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws ActivityNotFoundException   + * + * @see #startActivities(Intent[]) + * @see PackageManager#resolveActivity + */ + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} + * with no options specified. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + * + * @see #startActivity(Intent) + * @see #startIntentSender(IntentSender, Intent, int, int, int, Bundle) + */ + public abstract void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException; + /** + * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender + * to start. If the IntentSender is for an activity, that activity will be started + * as if you had called the regular {@link #startActivity(Intent)} + * here; otherwise, its associated action will be executed (such as + * sending a broadcast) as if you had called + * {@link IntentSender#sendIntent IntentSender.sendIntent} on it. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. If options + * have also been supplied by the IntentSender, options given here will + * override any that conflict with those given by the IntentSender. + * + * @see #startActivity(Intent, Bundle) + * @see #startIntentSender(IntentSender, Intent, int, int, int) + */ + public abstract void startIntentSender(IntentSender intent, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException; + /** + * Broadcast the given intent to all interested BroadcastReceivers. This + * call is asynchronous; it returns immediately, and you will continue + * executing while the receivers are run. No results are propagated from + * receivers and receivers can not abort the broadcast. If you want + * to allow receivers to propagate results or abort the broadcast, you must + * send an ordered broadcast using + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendBroadcast(@RequiresPermission Intent intent); + /** + * Broadcast the given intent to all interested BroadcastReceivers, allowing + * an optional required permission to be enforced. This + * call is asynchronous; it returns immediately, and you will continue + * executing while the receivers are run. No results are propagated from + * receivers and receivers can not abort the broadcast. If you want + * to allow receivers to propagate results or abort the broadcast, you must + * send an ordered broadcast using + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermission (optional) String naming a permission that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendBroadcast(@RequiresPermission Intent intent, + @Nullable String receiverPermission); + /** + * Broadcast the given intent to all interested BroadcastReceivers, allowing + * an array of required permissions to be enforced. This call is asynchronous; it returns + * immediately, and you will continue executing while the receivers are run. No results are + * propagated from receivers and receivers can not abort the broadcast. If you want to allow + * receivers to propagate results or abort the broadcast, you must send an ordered broadcast + * using {@link #sendOrderedBroadcast(Intent, String)}. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermissions Array of names of permissions that a receiver must hold + * in order to receive your broadcast. + * If null or empty, no permissions are required. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + * @hide + */ + public abstract void sendBroadcastMultiplePermissions(Intent intent, + String[] receiverPermissions); + /** + * Broadcast the given intent to all interested BroadcastReceivers, allowing + * an optional required permission to be enforced. This + * call is asynchronous; it returns immediately, and you will continue + * executing while the receivers are run. No results are propagated from + * receivers and receivers can not abort the broadcast. If you want + * to allow receivers to propagate results or abort the broadcast, you must + * send an ordered broadcast using + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermission (optional) String naming a permission that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + * @hide + */ + @SystemApi + public abstract void sendBroadcast(Intent intent, + @Nullable String receiverPermission, + @Nullable Bundle options); + /** + * Like {@link #sendBroadcast(Intent, String)}, but also allows specification + * of an associated app op as per {@link android.app.AppOpsManager}. + * @hide + */ + public abstract void sendBroadcast(Intent intent, + String receiverPermission, int appOp); + /** + * Broadcast the given intent to all interested BroadcastReceivers, delivering + * them one at a time to allow more preferred receivers to consume the + * broadcast before it is delivered to less preferred receivers. This + * call is asynchronous; it returns immediately, and you will continue + * executing while the receivers are run. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermission (optional) String naming a permissions that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendOrderedBroadcast(@RequiresPermission Intent intent, + @Nullable String receiverPermission); + /** + * Version of {@link #sendBroadcast(Intent)} that allows you to + * receive data back from the broadcast. This is accomplished by + * supplying your own BroadcastReceiver when calling, which will be + * treated as a final receiver at the end of the broadcast -- its + * {@link BroadcastReceiver#onReceive} method will be called with + * the result values collected from the other receivers. The broadcast will + * be serialized in the same way as calling + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

Like {@link #sendBroadcast(Intent)}, this method is + * asynchronous; it will return before + * resultReceiver.onReceive() is called. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermission String naming a permissions that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendBroadcast(Intent) + * @see #sendBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String) + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see android.app.Activity#RESULT_OK + */ + public abstract void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent, + @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); + /** + * Version of {@link #sendBroadcast(Intent)} that allows you to + * receive data back from the broadcast. This is accomplished by + * supplying your own BroadcastReceiver when calling, which will be + * treated as a final receiver at the end of the broadcast -- its + * {@link BroadcastReceiver#onReceive} method will be called with + * the result values collected from the other receivers. The broadcast will + * be serialized in the same way as calling + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

Like {@link #sendBroadcast(Intent)}, this method is + * asynchronous; it will return before + * resultReceiver.onReceive() is called. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermission String naming a permissions that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * @see #sendBroadcast(Intent) + * @see #sendBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String) + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see android.app.Activity#RESULT_OK + * @hide + */ + @SystemApi + public abstract void sendOrderedBroadcast(@NonNull Intent intent, + @Nullable String receiverPermission, @Nullable Bundle options, + @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, + int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras); + /** + * Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, + * int, String, android.os.Bundle)}, but also allows specification + * of an associated app op as per {@link android.app.AppOpsManager}. + * @hide + */ + public abstract void sendOrderedBroadcast(Intent intent, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras); + /** + * Version of {@link #sendBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * @param intent The intent to broadcast + * @param user UserHandle to send the intent to. + * @see #sendBroadcast(Intent) + */ + public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user); + /** + * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param receiverPermission (optional) String naming a permission that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * + * @see #sendBroadcast(Intent, String) + */ + public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, @Nullable String receiverPermission); + /** + * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param receiverPermission (optional) String naming a permission that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param appOp The app op associated with the broadcast. + * + * @see #sendBroadcast(Intent, String) + * + * @hide + */ + public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, @Nullable String receiverPermission, int appOp); + /** + * Version of + * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)} + * that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param receiverPermission String naming a permissions that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendOrderedBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, @Nullable String receiverPermission, BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); + /** + * Similar to above but takes an appOp as well, to enforce restrictions. + * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String, + * BroadcastReceiver, Handler, int, String, Bundle) + * @hide + */ + public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + @Nullable String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); + /** + * Similar to above but takes an appOp as well, to enforce restrictions, and an options Bundle. + * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String, + * BroadcastReceiver, Handler, int, String, Bundle) + * @hide + */ + public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + @Nullable String receiverPermission, int appOp, @Nullable Bundle options, + BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, + @Nullable String initialData, @Nullable Bundle initialExtras); + /** + *

Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + *

You must hold the {@link android.Manifest.permission#BROADCAST_STICKY} + * permission in order to use this API. If you do not hold that + * permission, {@link SecurityException} will be thrown. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Deprecated + public abstract void sendStickyBroadcast(@RequiresPermission Intent intent); + /** + *

Version of {@link #sendStickyBroadcast} that allows you to + * receive data back from the broadcast. This is accomplished by + * supplying your own BroadcastReceiver when calling, which will be + * treated as a final receiver at the end of the broadcast -- its + * {@link BroadcastReceiver#onReceive} method will be called with + * the result values collected from the other receivers. The broadcast will + * be serialized in the same way as calling + * {@link #sendOrderedBroadcast(Intent, String)}. + * + *

Like {@link #sendBroadcast(Intent)}, this method is + * asynchronous; it will return before + * resultReceiver.onReceive() is called. Note that the sticky data + * stored is only the data you initially supply to the broadcast, not + * the result of any changes made by the receivers. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendBroadcast(Intent) + * @see #sendBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendStickyBroadcast(Intent) + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see android.app.Activity#RESULT_OK + */ + @Deprecated + public abstract void sendStickyOrderedBroadcast(@RequiresPermission Intent intent, + BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); + /** + *

Remove the data previously sent with {@link #sendStickyBroadcast}, + * so that it is as if the sticky broadcast had never happened. + * + *

You must hold the {@link android.Manifest.permission#BROADCAST_STICKY} + * permission in order to use this API. If you do not hold that + * permission, {@link SecurityException} will be thrown. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent that was previously broadcast. + * + * @see #sendStickyBroadcast + */ + @Deprecated + public abstract void removeStickyBroadcast(@RequiresPermission Intent intent); + /** + *

Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param user UserHandle to send the intent to. + * + * @see #sendBroadcast(Intent) + */ + @Deprecated + public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user); + /** + * @hide + * This is just here for sending CONNECTIVITY_ACTION. + */ + @Deprecated + public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, Bundle options); + /** + *

Version of + * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)} + * that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Deprecated + public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras); + /** + *

Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + *

You must hold the {@link android.Manifest.permission#BROADCAST_STICKY} + * permission in order to use this API. If you do not hold that + * permission, {@link SecurityException} will be thrown. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that something + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent that was previously broadcast. + * @param user UserHandle to remove the sticky broadcast from. + * + * @see #sendStickyBroadcastAsUser + */ + @Deprecated + public abstract void removeStickyBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user); + /** + * Register a BroadcastReceiver to be run in the main activity thread. The + * receiver will be called with any broadcast Intent that + * matches filter, in the main application thread. + * + *

The system may broadcast Intents that are "sticky" -- these stay + * around after the broadcast as finished, to be sent to any later + * registrations. If your IntentFilter matches one of these sticky + * Intents, that Intent will be returned by this function + * and sent to your receiver as if it had just + * been broadcast. + * + *

There may be multiple sticky Intents that match filter, + * in which case each of these will be sent to receiver. In + * this case, only one of these can be returned directly by the function; + * which of these that is returned is arbitrarily decided by the system. + * + *

If you know the Intent your are registering for is sticky, you can + * supply null for your receiver. In this case, no receiver is + * registered -- the function simply returns the sticky Intent that + * matches filter. In the case of multiple matches, the same + * rules as described above apply. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + *

As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.

+ * + *

Note: this method cannot be called from a + * {@link BroadcastReceiver} component; that is, from a BroadcastReceiver + * that is declared in an application's manifest. It is okay, however, to call + * this method from another BroadcastReceiver that has itself been registered + * at run time with {@link #registerReceiver}, since the lifetime of such a + * registered BroadcastReceiver is tied to the object that registered it.

+ * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param filter Selects the Intent broadcasts to be received. + * + * @return The first sticky intent found that matches filter, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) + * @see #sendBroadcast + * @see #unregisterReceiver + */ + @Nullable + public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver, + IntentFilter filter); + /** + * Register to receive intent broadcasts, to run in the context of + * scheduler. See + * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more + * information. This allows you to enforce permissions on who can + * broadcast intents to your receiver, or have the receiver run in + * a different thread than the main application thread. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + *

As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.

+ * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param filter Selects the Intent broadcasts to be received. + * @param broadcastPermission String naming a permissions that a + * broadcaster must hold in order to send an Intent to you. If null, + * no permission is required. + * @param scheduler Handler identifying the thread that will receive + * the Intent. If null, the main thread of the process will be used. + * + * @return The first sticky intent found that matches filter, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter) + * @see #sendBroadcast + * @see #unregisterReceiver + */ + @Nullable + public abstract Intent registerReceiver(BroadcastReceiver receiver, + IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler); + /** + * @hide + * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) + * but for a specific user. This receiver will receiver broadcasts that + * are sent to the requested user. It + * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} + * permission. + * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param user UserHandle to send the intent to. + * @param filter Selects the Intent broadcasts to be received. + * @param broadcastPermission String naming a permissions that a + * broadcaster must hold in order to send an Intent to you. If null, + * no permission is required. + * @param scheduler Handler identifying the thread that will receive + * the Intent. If null, the main thread of the process will be used. + * + * @return The first sticky intent found that matches filter, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) + * @see #sendBroadcast + * @see #unregisterReceiver + */ + @Nullable + public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver, + UserHandle user, IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler); + /** + * Unregister a previously registered BroadcastReceiver. All + * filters that have been registered for this BroadcastReceiver will be + * removed. + * + * @param receiver The BroadcastReceiver to unregister. + * + * @see #registerReceiver + */ + public abstract void unregisterReceiver(BroadcastReceiver receiver); + /** + * Request that a given application service be started. The Intent + * should either contain the complete class name of a specific service + * implementation to start, or a specific package name to target. If the + * Intent is less specified, it logs a warning about this. In this case any of the + * multiple matching services may be used. If this service + * is not already running, it will be instantiated and started (creating a + * process for it if needed); if it is running then it remains running. + * + *

Every call to this method will result in a corresponding call to + * the target service's {@link android.app.Service#onStartCommand} method, + * with the intent given here. This provides a convenient way + * to submit jobs to a service without having to bind and call on to its + * interface. + * + *

Using startService() overrides the default service lifetime that is + * managed by {@link #bindService}: it requires the service to remain + * running until {@link #stopService} is called, regardless of whether + * any clients are connected to it. Note that calls to startService() + * do not nest: no matter how many times you call startService(), + * a single call to {@link #stopService} will stop it. + * + *

The system attempts to keep running services around as much as + * possible. The only time they should be stopped is if the current + * foreground application is using so many resources that the service needs + * to be killed. If any errors happen in the service's process, it will + * automatically be restarted. + * + *

This function will throw {@link SecurityException} if you do not + * have permission to start the given service. + * + *

Note: Each call to startService() + * results in significant work done by the system to manage service + * lifecycle surrounding the processing of the intent, which can take + * multiple milliseconds of CPU time. Due to this cost, startService() + * should not be used for frequent intent delivery to a service, and only + * for scheduling significant work. Use {@link #bindService bound services} + * for high frequency calls. + *

+ * + * @param service Identifies the service to be started. The Intent must be either + * fully explicit (supplying a component name) or specify a specific package + * name it is targetted to. Additional values + * may be included in the Intent extras to supply arguments along with + * this specific start call. + * + * @return If the service is being started or is already running, the + * {@link ComponentName} of the actual service that was started is + * returned; else if the service does not exist null is returned. + * + * @throws SecurityException   + * + * @see #stopService + * @see #bindService + */ + @Nullable + public abstract ComponentName startService(Intent service); + /** + * Request that a given application service be stopped. If the service is + * not running, nothing happens. Otherwise it is stopped. Note that calls + * to startService() are not counted -- this stops the service no matter + * how many times it was started. + * + *

Note that if a stopped service still has {@link ServiceConnection} + * objects bound to it with the {@link #BIND_AUTO_CREATE} set, it will + * not be destroyed until all of these bindings are removed. See + * the {@link android.app.Service} documentation for more details on a + * service's lifecycle. + * + *

This function will throw {@link SecurityException} if you do not + * have permission to stop the given service. + * + * @param service Description of the service to be stopped. The Intent must be either + * fully explicit (supplying a component name) or specify a specific package + * name it is targetted to. + * + * @return If there is a service matching the given Intent that is already + * running, then it is stopped and {@code true} is returned; else {@code false} is returned. + * + * @throws SecurityException   + * + * @see #startService + */ + public abstract boolean stopService(Intent service); + /** + * @hide like {@link #startService(Intent)} but for a specific user. + */ + public abstract ComponentName startServiceAsUser(Intent service, UserHandle user); + /** + * @hide like {@link #stopService(Intent)} but for a specific user. + */ + public abstract boolean stopServiceAsUser(Intent service, UserHandle user); + /** + * Connect to an application service, creating it if needed. This defines + * a dependency between your application and the service. The given + * conn will receive the service object when it is created and be + * told if it dies and restarts. The service will be considered required + * by the system only for as long as the calling context exists. For + * example, if this Context is an Activity that is stopped, the service will + * not be required to continue running until the Activity is resumed. + * + *

This function will throw {@link SecurityException} if you do not + * have permission to bind to the given service. + * + *

Note: this method can not be called from a + * {@link BroadcastReceiver} component. A pattern you can use to + * communicate from a BroadcastReceiver to a Service is to call + * {@link #startService} with the arguments containing the command to be + * sent, with the service calling its + * {@link android.app.Service#stopSelf(int)} method when done executing + * that command. See the API demo App/Service/Service Start Arguments + * Controller for an illustration of this. It is okay, however, to use + * this method from a BroadcastReceiver that has been registered with + * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver + * is tied to another object (the one that registered it).

+ * + * @param service Identifies the service to connect to. The Intent may + * specify either an explicit component name, or a logical + * description (action, category, etc) to match an + * {@link IntentFilter} published by a service. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param flags Operation options for the binding. May be 0, + * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, + * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, + * {@link #BIND_ALLOW_OOM_MANAGEMENT}, or + * {@link #BIND_WAIVE_PRIORITY}. + * @return If you have successfully bound to the service, {@code true} is returned; + * {@code false} is returned if the connection is not made so you will not + * receive the service object. + * + * @throws SecurityException   + * + * @see #unbindService + * @see #startService + * @see #BIND_AUTO_CREATE + * @see #BIND_DEBUG_UNBIND + * @see #BIND_NOT_FOREGROUND + */ + public abstract boolean bindService(@RequiresPermission Intent service, + @NonNull ServiceConnection conn, @BindServiceFlags int flags); + /** + * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle + * argument for use by system server and other multi-user aware code. + * @hide + */ + @SystemApi + @SuppressWarnings("unused") + public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn, + int flags, UserHandle user) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Same as {@link #bindService(Intent, ServiceConnection, int, UserHandle)}, but with an + * explicit non-null Handler to run the ServiceConnection callbacks on. + * + * @hide + */ + public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, + Handler handler, UserHandle user) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Disconnect from an application service. You will no longer receive + * calls as the service is restarted, and the service is now allowed to + * stop at any time. + * + * @param conn The connection interface previously supplied to + * bindService(). This parameter must not be null. + * + * @see #bindService + */ + public abstract void unbindService(@NonNull ServiceConnection conn); + /** + * Start executing an {@link android.app.Instrumentation} class. The given + * Instrumentation component will be run by killing its target application + * (if currently running), starting the target process, instantiating the + * instrumentation component, and then letting it drive the application. + * + *

This function is not synchronous -- it returns as soon as the + * instrumentation has started and while it is running. + * + *

Instrumentation is normally only allowed to run against a package + * that is either unsigned or signed with a signature that the + * the instrumentation package is also signed with (ensuring the target + * trusts the instrumentation). + * + * @param className Name of the Instrumentation component to be run. + * @param profileFile Optional path to write profiling data as the + * instrumentation runs, or null for no profiling. + * @param arguments Additional optional arguments to pass to the + * instrumentation, or null. + * + * @return {@code true} if the instrumentation was successfully started, + * else {@code false} if it could not be found. + */ + public abstract boolean startInstrumentation(@NonNull ComponentName className, + @Nullable String profileFile, @Nullable Bundle arguments); + /** @hide */ + @StringDef({ + POWER_SERVICE, + WINDOW_SERVICE, + LAYOUT_INFLATER_SERVICE, + ACCOUNT_SERVICE, + ACTIVITY_SERVICE, + ALARM_SERVICE, + NOTIFICATION_SERVICE, + ACCESSIBILITY_SERVICE, + CAPTIONING_SERVICE, + KEYGUARD_SERVICE, + LOCATION_SERVICE, + //@hide: COUNTRY_DETECTOR, + SEARCH_SERVICE, + SENSOR_SERVICE, + STORAGE_SERVICE, + WALLPAPER_SERVICE, + VIBRATOR_SERVICE, + //@hide: STATUS_BAR_SERVICE, + CONNECTIVITY_SERVICE, + IPSEC_SERVICE, + //@hide: UPDATE_LOCK_SERVICE, + //@hide: NETWORKMANAGEMENT_SERVICE, + NETWORK_STATS_SERVICE, + //@hide: NETWORK_POLICY_SERVICE, + WIFI_SERVICE, + WIFI_AWARE_SERVICE, + WIFI_P2P_SERVICE, + WIFI_SCANNING_SERVICE, + //@hide: WIFI_RTT_SERVICE, + //@hide: ETHERNET_SERVICE, + WIFI_RTT_SERVICE, + NSD_SERVICE, + AUDIO_SERVICE, + FINGERPRINT_SERVICE, + MEDIA_ROUTER_SERVICE, + TELEPHONY_SERVICE, + TELEPHONY_SUBSCRIPTION_SERVICE, + CARRIER_CONFIG_SERVICE, + TELECOM_SERVICE, + CLIPBOARD_SERVICE, + INPUT_METHOD_SERVICE, + TEXT_SERVICES_MANAGER_SERVICE, + APPWIDGET_SERVICE, + //@hide: VOICE_INTERACTION_MANAGER_SERVICE, + //@hide: BACKUP_SERVICE, + DROPBOX_SERVICE, + //@hide: DEVICE_IDLE_CONTROLLER, + DEVICE_POLICY_SERVICE, + UI_MODE_SERVICE, + DOWNLOAD_SERVICE, + NFC_SERVICE, + BLUETOOTH_SERVICE, + //@hide: SIP_SERVICE, + USB_SERVICE, + LAUNCHER_APPS_SERVICE, + //@hide: SERIAL_SERVICE, + //@hide: HDMI_CONTROL_SERVICE, + INPUT_SERVICE, + DISPLAY_SERVICE, + USER_SERVICE, + RESTRICTIONS_SERVICE, + APP_OPS_SERVICE, + CAMERA_SERVICE, + PRINT_SERVICE, + CONSUMER_IR_SERVICE, + //@hide: TRUST_SERVICE, + TV_INPUT_SERVICE, + //@hide: NETWORK_SCORE_SERVICE, + USAGE_STATS_SERVICE, + MEDIA_SESSION_SERVICE, + BATTERY_SERVICE, + JOB_SCHEDULER_SERVICE, + //@hide: PERSISTENT_DATA_BLOCK_SERVICE, + MEDIA_PROJECTION_SERVICE, + MIDI_SERVICE, + RADIO_SERVICE, + HARDWARE_PROPERTIES_SERVICE, + //@hide: SOUND_TRIGGER_SERVICE, + SHORTCUT_SERVICE, + //@hide: CONTEXTHUB_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ServiceName {} + /** + * Return the handle to a system-level service by name. The class of the + * returned object varies by the requested name. Currently available names + * are: + * + *

+ *
{@link #WINDOW_SERVICE} ("window") + *
The top-level window manager in which you can place custom + * windows. The returned object is a {@link android.view.WindowManager}. + *
{@link #LAYOUT_INFLATER_SERVICE} ("layout_inflater") + *
A {@link android.view.LayoutInflater} for inflating layout resources + * in this context. + *
{@link #ACTIVITY_SERVICE} ("activity") + *
A {@link android.app.ActivityManager} for interacting with the + * global activity state of the system. + *
{@link #POWER_SERVICE} ("power") + *
A {@link android.os.PowerManager} for controlling power + * management. + *
{@link #ALARM_SERVICE} ("alarm") + *
A {@link android.app.AlarmManager} for receiving intents at the + * time of your choosing. + *
{@link #NOTIFICATION_SERVICE} ("notification") + *
A {@link android.app.NotificationManager} for informing the user + * of background events. + *
{@link #KEYGUARD_SERVICE} ("keyguard") + *
A {@link android.app.KeyguardManager} for controlling keyguard. + *
{@link #LOCATION_SERVICE} ("location") + *
A {@link android.location.LocationManager} for controlling location + * (e.g., GPS) updates. + *
{@link #SEARCH_SERVICE} ("search") + *
A {@link android.app.SearchManager} for handling search. + *
{@link #VIBRATOR_SERVICE} ("vibrator") + *
A {@link android.os.Vibrator} for interacting with the vibrator + * hardware. + *
{@link #CONNECTIVITY_SERVICE} ("connection") + *
A {@link android.net.ConnectivityManager ConnectivityManager} for + * handling management of network connections. + *
{@link #IPSEC_SERVICE} ("ipsec") + *
A {@link android.net.IpSecManager IpSecManager} for managing IPSec on + * sockets and networks. + *
{@link #WIFI_SERVICE} ("wifi") + *
A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi + * connectivity. On releases before NYC, it should only be obtained from an application + * context, and not from any other derived context to avoid memory leaks within the calling + * process. + *
{@link #WIFI_AWARE_SERVICE} ("wifiaware") + *
A {@link android.net.wifi.aware.WifiAwareManager WifiAwareManager} for management of + * Wi-Fi Aware discovery and connectivity. + *
{@link #WIFI_P2P_SERVICE} ("wifip2p") + *
A {@link android.net.wifi.p2p.WifiP2pManager WifiP2pManager} for management of + * Wi-Fi Direct connectivity. + *
{@link #INPUT_METHOD_SERVICE} ("input_method") + *
An {@link android.view.inputmethod.InputMethodManager InputMethodManager} + * for management of input methods. + *
{@link #UI_MODE_SERVICE} ("uimode") + *
An {@link android.app.UiModeManager} for controlling UI modes. + *
{@link #DOWNLOAD_SERVICE} ("download") + *
A {@link android.app.DownloadManager} for requesting HTTP downloads + *
{@link #BATTERY_SERVICE} ("batterymanager") + *
A {@link android.os.BatteryManager} for managing battery state + *
{@link #JOB_SCHEDULER_SERVICE} ("taskmanager") + *
A {@link android.app.job.JobScheduler} for managing scheduled tasks + *
{@link #NETWORK_STATS_SERVICE} ("netstats") + *
A {@link android.app.usage.NetworkStatsManager NetworkStatsManager} for querying network + * usage statistics. + *
{@link #HARDWARE_PROPERTIES_SERVICE} ("hardware_properties") + *
A {@link android.os.HardwarePropertiesManager} for accessing hardware properties. + *
+ * + *

Note: System services obtained via this API may be closely associated with + * the Context in which they are obtained from. In general, do not share the + * service objects between various different contexts (Activities, Applications, + * Services, Providers, etc.) + * + * @param name The name of the desired service. + * + * @return The service or null if the name does not exist. + * + * @see #WINDOW_SERVICE + * @see android.view.WindowManager + * @see #LAYOUT_INFLATER_SERVICE + * @see android.view.LayoutInflater + * @see #ACTIVITY_SERVICE + * @see android.app.ActivityManager + * @see #POWER_SERVICE + * @see android.os.PowerManager + * @see #ALARM_SERVICE + * @see android.app.AlarmManager + * @see #NOTIFICATION_SERVICE + * @see android.app.NotificationManager + * @see #KEYGUARD_SERVICE + * @see android.app.KeyguardManager + * @see #LOCATION_SERVICE + * @see android.location.LocationManager + * @see #SEARCH_SERVICE + * @see android.app.SearchManager + * @see #SENSOR_SERVICE + * @see android.hardware.SensorManager + * @see #STORAGE_SERVICE + * @see android.os.storage.StorageManager + * @see #VIBRATOR_SERVICE + * @see android.os.Vibrator + * @see #CONNECTIVITY_SERVICE + * @see android.net.ConnectivityManager + * @see #WIFI_SERVICE + * @see android.net.wifi.WifiManager + * @see #AUDIO_SERVICE + * @see android.media.AudioManager + * @see #MEDIA_ROUTER_SERVICE + * @see android.media.MediaRouter + * @see #TELEPHONY_SERVICE + * @see android.telephony.TelephonyManager + * @see #TELEPHONY_SUBSCRIPTION_SERVICE + * @see android.telephony.SubscriptionManager + * @see #CARRIER_CONFIG_SERVICE + * @see android.telephony.CarrierConfigManager + * @see #INPUT_METHOD_SERVICE + * @see android.view.inputmethod.InputMethodManager + * @see #UI_MODE_SERVICE + * @see android.app.UiModeManager + * @see #DOWNLOAD_SERVICE + * @see android.app.DownloadManager + * @see #BATTERY_SERVICE + * @see android.os.BatteryManager + * @see #JOB_SCHEDULER_SERVICE + * @see android.app.job.JobScheduler + * @see #NETWORK_STATS_SERVICE + * @see android.app.usage.NetworkStatsManager + * @see android.os.HardwarePropertiesManager + * @see #HARDWARE_PROPERTIES_SERVICE + */ + public abstract Object getSystemService(@ServiceName @NonNull String name); + /** + * Return the handle to a system-level service by class. + *

+ * Currently available classes are: + * {@link android.view.WindowManager}, {@link android.view.LayoutInflater}, + * {@link android.app.ActivityManager}, {@link android.os.PowerManager}, + * {@link android.app.AlarmManager}, {@link android.app.NotificationManager}, + * {@link android.app.KeyguardManager}, {@link android.location.LocationManager}, + * {@link android.app.SearchManager}, {@link android.os.Vibrator}, + * {@link android.net.ConnectivityManager}, + * {@link android.net.wifi.WifiManager}, + * {@link android.media.AudioManager}, {@link android.media.MediaRouter}, + * {@link android.telephony.TelephonyManager}, {@link android.telephony.SubscriptionManager}, + * {@link android.view.inputmethod.InputMethodManager}, + * {@link android.app.UiModeManager}, {@link android.app.DownloadManager}, + * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}, + * {@link android.app.usage.NetworkStatsManager}. + *

+ * Note: System services obtained via this API may be closely associated with + * the Context in which they are obtained from. In general, do not share the + * service objects between various different contexts (Activities, Applications, + * Services, Providers, etc.) + *

+ * + * @param serviceClass The class of the desired service. + * @return The service or null if the class is not a supported system service. + */ + @SuppressWarnings("unchecked") + public final T getSystemService(Class serviceClass) { + // Because subclasses may override getSystemService(String) we cannot + // perform a lookup by class alone. We must first map the class to its + // service name then invoke the string-based method. + String serviceName = getSystemServiceName(serviceClass); + return serviceName != null ? (T)getSystemService(serviceName) : null; + } + /** + * Gets the name of the system-level service that is represented by the specified class. + * + * @param serviceClass The class of the desired service. + * @return The service name or null if the class is not a supported system service. + */ + public abstract String getSystemServiceName(Class serviceClass); + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.PowerManager} for controlling power management, + * including "wake locks," which let you keep the device on while + * you're running long tasks. + */ + public static final String POWER_SERVICE = "power"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.RecoverySystem} for accessing the recovery system + * service. + * + * @see #getSystemService + * @hide + */ + public static final String RECOVERY_SERVICE = "recovery"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.WindowManager} for accessing the system's window + * manager. + * + * @see #getSystemService + * @see android.view.WindowManager + */ + public static final String WINDOW_SERVICE = "window"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.LayoutInflater} for inflating layout resources in this + * context. + * + * @see #getSystemService + * @see android.view.LayoutInflater + */ + public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.accounts.AccountManager} for receiving intents at a + * time of your choosing. + * + * @see #getSystemService + * @see android.accounts.AccountManager + */ + public static final String ACCOUNT_SERVICE = "account"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.ActivityManager} for interacting with the global + * system state. + * + * @see #getSystemService + * @see android.app.ActivityManager + */ + public static final String ACTIVITY_SERVICE = "activity"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.AlarmManager} for receiving intents at a + * time of your choosing. + * + * @see #getSystemService + * @see android.app.AlarmManager + */ + public static final String ALARM_SERVICE = "alarm"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.NotificationManager} for informing the user of + * background events. + * + * @see #getSystemService + * @see android.app.NotificationManager + */ + public static final String NOTIFICATION_SERVICE = "notification"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.accessibility.AccessibilityManager} for giving the user + * feedback for UI events through the registered event listeners. + * + * @see #getSystemService + * @see android.view.accessibility.AccessibilityManager + */ + public static final String ACCESSIBILITY_SERVICE = "accessibility"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.accessibility.CaptioningManager} for obtaining + * captioning properties and listening for changes in captioning + * preferences. + * + * @see #getSystemService + * @see android.view.accessibility.CaptioningManager + */ + public static final String CAPTIONING_SERVICE = "captioning"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.NotificationManager} for controlling keyguard. + * + * @see #getSystemService + * @see android.app.KeyguardManager + */ + public static final String KEYGUARD_SERVICE = "keyguard"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.location.LocationManager} for controlling location + * updates. + * + * @see #getSystemService + * @see android.location.LocationManager + */ + public static final String LOCATION_SERVICE = "location"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.location.CountryDetector} for detecting the country that + * the user is in. + * + * @hide + */ + public static final String COUNTRY_DETECTOR = "country_detector"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.SearchManager} for handling searches. + * + * @see #getSystemService + * @see android.app.SearchManager + */ + public static final String SEARCH_SERVICE = "search"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.hardware.SensorManager} for accessing sensors. + * + * @see #getSystemService + * @see android.hardware.SensorManager + */ + public static final String SENSOR_SERVICE = "sensor"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.os.storage.StorageManager} for accessing system storage + * functions. + * + * @see #getSystemService + * @see android.os.storage.StorageManager + */ + public static final String STORAGE_SERVICE = "storage"; + /** + * Use with {@link #getSystemService} to retrieve a + * com.android.server.WallpaperService for accessing wallpapers. + * + * @see #getSystemService + */ + public static final String WALLPAPER_SERVICE = "wallpaper"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.os.Vibrator} for interacting with the vibration hardware. + * + * @see #getSystemService + * @see android.os.Vibrator + */ + public static final String VIBRATOR_SERVICE = "vibrator"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.StatusBarManager} for interacting with the status bar. + * + * @see #getSystemService + * @see android.app.StatusBarManager + * @hide + */ + public static final String STATUS_BAR_SERVICE = "statusbar"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.ConnectivityManager} for handling management of + * network connections. + * + * @see #getSystemService + * @see android.net.ConnectivityManager + */ + public static final String CONNECTIVITY_SERVICE = "connectivity"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.IpSecManager} for encrypting Sockets or Networks with + * IPSec. + * + * @see #getSystemService + */ + public static final String IPSEC_SERVICE = "ipsec"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.os.IUpdateLock} for managing runtime sequences that + * must not be interrupted by headless OTA application or similar. + * + * @hide + * @see #getSystemService + * @see android.os.UpdateLock + */ + public static final String UPDATE_LOCK_SERVICE = "updatelock"; + /** + * Constant for the internal network management service, not really a Context service. + * @hide + */ + public static final String NETWORKMANAGEMENT_SERVICE = "network_management"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.usage.NetworkStatsManager} for querying network usage stats. + * + * @see #getSystemService + * @see android.app.usage.NetworkStatsManager + */ + public static final String NETWORK_STATS_SERVICE = "netstats"; + /** {@hide} */ + public static final String NETWORK_POLICY_SERVICE = "netpolicy"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.WifiManager} for handling management of + * Wi-Fi access. + * + * @see #getSystemService + * @see android.net.wifi.WifiManager + */ + public static final String WIFI_SERVICE = "wifi"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.p2p.WifiP2pManager} for handling management of + * Wi-Fi peer-to-peer connections. + * + * @see #getSystemService + * @see android.net.wifi.p2p.WifiP2pManager + */ + public static final String WIFI_P2P_SERVICE = "wifip2p"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.wifi.aware.WifiAwareManager} for handling management of + * Wi-Fi Aware. + * + * @see #getSystemService + * @see android.net.wifi.aware.WifiAwareManager + */ + public static final String WIFI_AWARE_SERVICE = "wifiaware"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.WifiScanner} for scanning the wifi universe + * + * @see #getSystemService + * @see android.net.wifi.WifiScanner + * @hide + */ + @SystemApi + public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.RttManager} for ranging devices with wifi + * + * @see #getSystemService + * @see android.net.wifi.RttManager + * @hide + */ + @SystemApi + public static final String WIFI_RTT_SERVICE = "rttmanager"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.EthernetManager} for handling management of + * Ethernet access. + * + * @see #getSystemService + * @see android.net.EthernetManager + * + * @hide + */ + public static final String ETHERNET_SERVICE = "ethernet"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.nsd.NsdManager} for handling management of network service + * discovery + * + * @see #getSystemService + * @see android.net.nsd.NsdManager + */ + public static final String NSD_SERVICE = "servicediscovery"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.AudioManager} for handling management of volume, + * ringer modes and audio routing. + * + * @see #getSystemService + * @see android.media.AudioManager + */ + public static final String AUDIO_SERVICE = "audio"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.fingerprint.FingerprintManager} for handling management + * of fingerprints. + * + * @see #getSystemService + * @see android.hardware.fingerprint.FingerprintManager + */ + public static final String FINGERPRINT_SERVICE = "fingerprint"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.MediaRouter} for controlling and managing + * routing of media. + * + * @see #getSystemService + * @see android.media.MediaRouter + */ + public static final String MEDIA_ROUTER_SERVICE = "media_router"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.session.MediaSessionManager} for managing media Sessions. + * + * @see #getSystemService + * @see android.media.session.MediaSessionManager + */ + public static final String MEDIA_SESSION_SERVICE = "media_session"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.telephony.TelephonyManager} for handling management the + * telephony features of the device. + * + * @see #getSystemService + * @see android.telephony.TelephonyManager + */ + public static final String TELEPHONY_SERVICE = "phone"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.telephony.SubscriptionManager} for handling management the + * telephony subscriptions of the device. + * + * @see #getSystemService + * @see android.telephony.SubscriptionManager + */ + public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.telecom.TelecomManager} to manage telecom-related features + * of the device. + * + * @see #getSystemService + * @see android.telecom.TelecomManager + */ + public static final String TELECOM_SERVICE = "telecom"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.telephony.CarrierConfigManager} for reading carrier configuration values. + * + * @see #getSystemService + * @see android.telephony.CarrierConfigManager + */ + public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.text.ClipboardManager} for accessing and modifying + * {@link android.content.ClipboardManager} for accessing and modifying + * the contents of the global clipboard. + * + * @see #getSystemService + * @see android.content.ClipboardManager + */ + public static final String CLIPBOARD_SERVICE = "clipboard"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.inputmethod.InputMethodManager} for accessing input + * methods. + * + * @see #getSystemService + */ + public static final String INPUT_METHOD_SERVICE = "input_method"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.view.textservice.TextServicesManager} for accessing + * text services. + * + * @see #getSystemService + */ + public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets. + * + * @see #getSystemService + */ + public static final String APPWIDGET_SERVICE = "appwidget"; + /** + * Official published name of the (internal) voice interaction manager service. + * + * @hide + * @see #getSystemService + */ + public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction"; + /** + * Use with {@link #getSystemService} to access the + * {@link com.android.server.voiceinteraction.SoundTriggerService}. + * + * @hide + * @see #getSystemService + */ + public static final String SOUND_TRIGGER_SERVICE = "soundtrigger"; + /** + * Use with {@link #getSystemService} to retrieve an + * {@link android.app.backup.IBackupManager IBackupManager} for communicating + * with the backup mechanism. + * @hide + * + * @see #getSystemService + */ + @SystemApi + public static final String BACKUP_SERVICE = "backup"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.DropBoxManager} instance for recording + * diagnostic logs. + * @see #getSystemService + */ + public static final String DROPBOX_SERVICE = "dropbox"; + /** + * System service name for the DeviceIdleController. There is no Java API for this. + * @see #getSystemService + * @hide + */ + public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.admin.DevicePolicyManager} for working with global + * device policy management. + * + * @see #getSystemService + */ + public static final String DEVICE_POLICY_SERVICE = "device_policy"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.UiModeManager} for controlling UI modes. + * + * @see #getSystemService + */ + public static final String UI_MODE_SERVICE = "uimode"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.DownloadManager} for requesting HTTP downloads. + * + * @see #getSystemService + */ + public static final String DOWNLOAD_SERVICE = "download"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.BatteryManager} for managing battery state. + * + * @see #getSystemService + */ + public static final String BATTERY_SERVICE = "batterymanager"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.nfc.NfcManager} for using NFC. + * + * @see #getSystemService + */ + public static final String NFC_SERVICE = "nfc"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.bluetooth.BluetoothManager} for using Bluetooth. + * + * @see #getSystemService + */ + public static final String BLUETOOTH_SERVICE = "bluetooth"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.sip.SipManager} for accessing the SIP related service. + * + * @see #getSystemService + */ + /** @hide */ + public static final String SIP_SERVICE = "sip"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.hardware.usb.UsbManager} for access to USB devices (as a USB host) + * and for controlling this device's behavior as a USB device. + * + * @see #getSystemService + * @see android.hardware.usb.UsbManager + */ + public static final String USB_SERVICE = "usb"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.hardware.SerialManager} for access to serial ports. + * + * @see #getSystemService + * @see android.hardware.SerialManager + * + * @hide + */ + public static final String SERIAL_SERVICE = "serial"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing + * HDMI-CEC protocol. + * + * @see #getSystemService + * @see android.hardware.hdmi.HdmiControlManager + * @hide + */ + @SystemApi + public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.input.InputManager} for interacting with input devices. + * + * @see #getSystemService + * @see android.hardware.input.InputManager + */ + public static final String INPUT_SERVICE = "input"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.display.DisplayManager} for interacting with display devices. + * + * @see #getSystemService + * @see android.hardware.display.DisplayManager + */ + public static final String DISPLAY_SERVICE = "display"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.UserManager} for managing users on devices that support multiple users. + * + * @see #getSystemService + * @see android.os.UserManager + */ + public static final String USER_SERVICE = "user"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.content.pm.LauncherApps} for querying and monitoring launchable apps across + * profiles of a user. + * + * @see #getSystemService + * @see android.content.pm.LauncherApps + */ + public static final String LAUNCHER_APPS_SERVICE = "launcherapps"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.content.RestrictionsManager} for retrieving application restrictions + * and requesting permissions for restricted operations. + * @see #getSystemService + * @see android.content.RestrictionsManager + */ + public static final String RESTRICTIONS_SERVICE = "restrictions"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.AppOpsManager} for tracking application operations + * on the device. + * + * @see #getSystemService + * @see android.app.AppOpsManager + */ + public static final String APP_OPS_SERVICE = "appops"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.camera2.CameraManager} for interacting with + * camera devices. + * + * @see #getSystemService + * @see android.hardware.camera2.CameraManager + */ + public static final String CAMERA_SERVICE = "camera"; + /** + * {@link android.print.PrintManager} for printing and managing + * printers and print tasks. + * + * @see #getSystemService + * @see android.print.PrintManager + */ + public static final String PRINT_SERVICE = "print"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.ConsumerIrManager} for transmitting infrared + * signals from the device. + * + * @see #getSystemService + * @see android.hardware.ConsumerIrManager + */ + public static final String CONSUMER_IR_SERVICE = "consumer_ir"; + /** + * {@link android.app.trust.TrustManager} for managing trust agents. + * @see #getSystemService + * @see android.app.trust.TrustManager + * @hide + */ + public static final String TRUST_SERVICE = "trust"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.tv.TvInputManager} for interacting with TV inputs + * on the device. + * + * @see #getSystemService + * @see android.media.tv.TvInputManager + */ + public static final String TV_INPUT_SERVICE = "tv_input"; + /** + * {@link android.net.NetworkScoreManager} for managing network scoring. + * @see #getSystemService + * @see android.net.NetworkScoreManager + * @hide + */ + @SystemApi + public static final String NETWORK_SCORE_SERVICE = "network_score"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.usage.UsageStatsManager} for querying device usage stats. + * + * @see #getSystemService + * @see android.app.usage.UsageStatsManager + */ + public static final String USAGE_STATS_SERVICE = "usagestats"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.job.JobScheduler} instance for managing occasional + * background tasks. + * @see #getSystemService + * @see android.app.job.JobScheduler + */ + public static final String JOB_SCHEDULER_SERVICE = "jobscheduler"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.service.persistentdata.PersistentDataBlockManager} instance + * for interacting with a storage device that lives across factory resets. + * + * @see #getSystemService + * @see android.service.persistentdata.PersistentDataBlockManager + * @hide + */ + @SystemApi + public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.media.projection.MediaProjectionManager} instance for managing + * media projection sessions. + * @see #getSystemService + * @see android.media.projection.MediaProjectionManager + */ + public static final String MEDIA_PROJECTION_SERVICE = "media_projection"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.midi.MidiManager} for accessing the MIDI service. + * + * @see #getSystemService + */ + public static final String MIDI_SERVICE = "midi"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service. + * + * @see #getSystemService + * @hide + */ + public static final String RADIO_SERVICE = "radio"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.HardwarePropertiesManager} for accessing the hardware properties service. + * + * @see #getSystemService + */ + public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service. + * + * @see #getSystemService + * @see android.content.pm.ShortcutManager + */ + public static final String SHORTCUT_SERVICE = "shortcut"; + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.hardware.location.ContextHubManager} for accessing context hubs. + * + * @see #getSystemService + * @see android.hardware.location.ContextHubManager + * + * @hide + */ + @SystemApi + public static final String CONTEXTHUB_SERVICE = "contexthub"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.health.SystemHealthManager} for accessing system health (battery, power, + * memory, etc) metrics. + * + * @see #getSystemService + */ + public static final String SYSTEM_HEALTH_SERVICE = "systemhealth"; + /** + * Gatekeeper Service. + * @hide + */ + public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService"; + /** + * Determine whether the given permission is allowed for a particular + * process and user ID running in the system. + * + * @param permission The name of the permission being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the given + * pid/uid is allowed that permission, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkCallingPermission + */ + @CheckResult(suggest="#enforcePermission(String,int,int,String)") + @PackageManager.PermissionResult + public abstract int checkPermission(@NonNull String permission, int pid, int uid); + /** @hide */ + @PackageManager.PermissionResult + public abstract int checkPermission(@NonNull String permission, int pid, int uid, + IBinder callerToken); + /** + * Determine whether the calling process of an IPC you are handling has been + * granted a particular permission. This is basically the same as calling + * {@link #checkPermission(String, int, int)} with the pid and uid returned + * by {@link android.os.Binder#getCallingPid} and + * {@link android.os.Binder#getCallingUid}. One important difference + * is that if you are not currently processing an IPC, this function + * will always fail. This is done to protect against accidentally + * leaking permissions; you can use {@link #checkCallingOrSelfPermission} + * to avoid this protection. + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the calling + * pid/uid is allowed that permission, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkPermission + * @see #checkCallingOrSelfPermission + */ + @CheckResult(suggest="#enforceCallingPermission(String,String)") + @PackageManager.PermissionResult + public abstract int checkCallingPermission(@NonNull String permission); + /** + * Determine whether the calling process of an IPC or you have been + * granted a particular permission. This is the same as + * {@link #checkCallingPermission}, except it grants your own permissions + * if you are not currently processing an IPC. Use with care! + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the calling + * pid/uid is allowed that permission, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkPermission + * @see #checkCallingPermission + */ + @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") + @PackageManager.PermissionResult + public abstract int checkCallingOrSelfPermission(@NonNull String permission); + /** + * Determine whether you have been granted a particular permission. + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if you have the + * permission, or {@link PackageManager#PERMISSION_DENIED} if not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkCallingPermission(String) + */ + @PackageManager.PermissionResult + public abstract int checkSelfPermission(@NonNull String permission); + /** + * If the given permission is not allowed for a particular process + * and user ID running in the system, throw a {@link SecurityException}. + * + * @param permission The name of the permission being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkPermission(String, int, int) + */ + public abstract void enforcePermission( + @NonNull String permission, int pid, int uid, @Nullable String message); + /** + * If the calling process of an IPC you are handling has not been + * granted a particular permission, throw a {@link + * SecurityException}. This is basically the same as calling + * {@link #enforcePermission(String, int, int, String)} with the + * pid and uid returned by {@link android.os.Binder#getCallingPid} + * and {@link android.os.Binder#getCallingUid}. One important + * difference is that if you are not currently processing an IPC, + * this function will always throw the SecurityException. This is + * done to protect against accidentally leaking permissions; you + * can use {@link #enforceCallingOrSelfPermission} to avoid this + * protection. + * + * @param permission The name of the permission being checked. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingPermission(String) + */ + public abstract void enforceCallingPermission( + @NonNull String permission, @Nullable String message); + /** + * If neither you nor the calling process of an IPC you are + * handling has been granted a particular permission, throw a + * {@link SecurityException}. This is the same as {@link + * #enforceCallingPermission}, except it grants your own + * permissions if you are not currently processing an IPC. Use + * with care! + * + * @param permission The name of the permission being checked. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingOrSelfPermission(String) + */ + public abstract void enforceCallingOrSelfPermission( + @NonNull String permission, @Nullable String message); + /** + * Grant permission to access a specific Uri to another package, regardless + * of whether that package has general permission to access the Uri's + * content provider. This can be used to grant specific, temporary + * permissions, typically in response to user interaction (such as the + * user opening an attachment that you would like someone else to + * display). + * + *

Normally you should use {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} with the Intent being used to + * start an activity instead of this function directly. If you use this + * function directly, you should be sure to call + * {@link #revokeUriPermission} when the target should no longer be allowed + * to access it. + * + *

To succeed, the content provider owning the Uri must have set the + * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions + * grantUriPermissions} attribute in its manifest or included the + * {@link android.R.styleable#AndroidManifestGrantUriPermission + * <grant-uri-permissions>} tag. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to grant access to. + * @param modeFlags The desired access modes. Any combination of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION}, + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}, + * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION + * Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, or + * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION + * Intent.FLAG_GRANT_PREFIX_URI_PERMISSION}. + * + * @see #revokeUriPermission + */ + public abstract void grantUriPermission(String toPackage, Uri uri, + int modeFlags); + /** + * Remove all permissions to access a particular content provider Uri + * that were previously added with {@link #grantUriPermission}. The given + * Uri will match all previously granted Uris that are the same or a + * sub-path of the given Uri. That is, revoking "content://foo/target" will + * revoke both "content://foo/target" and "content://foo/target/sub", but not + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. + * + *

Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, if you did not have + * regular permission access to a Uri, but had received access to it through + * a specific Uri permission grant, you could not revoke that grant with this + * function and a {@link SecurityException} would be thrown. As of + * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this function will not throw a security exception, + * but will remove whatever permission grants to the Uri had been given to the app + * (or none).

+ * + * @param uri The Uri you would like to revoke access to. + * @param modeFlags The desired access modes. Any combination of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * @see #grantUriPermission + */ + public abstract void revokeUriPermission(Uri uri, int modeFlags); + /** + * Determine whether a particular process and user ID has been granted + * permission to access a specific URI. This only checks for permissions + * that have been explicitly granted -- if the given process/uid has + * more general access to the URI's content provider then this check will + * always fail. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the given + * pid/uid is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkCallingUriPermission + */ + @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") + public abstract int checkUriPermission(Uri uri, int pid, int uid, + int modeFlags); + /** @hide */ + public abstract int checkUriPermission(Uri uri, int pid, int uid, + int modeFlags, IBinder callerToken); + /** + * Determine whether the calling process and user ID has been + * granted permission to access a specific URI. This is basically + * the same as calling {@link #checkUriPermission(Uri, int, int, + * int)} with the pid and uid returned by {@link + * android.os.Binder#getCallingPid} and {@link + * android.os.Binder#getCallingUid}. One important difference is + * that if you are not currently processing an IPC, this function + * will always fail. + * + * @param uri The uri that is being checked. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkUriPermission(Uri, int, int, int) + */ + @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") + public abstract int checkCallingUriPermission(Uri uri, int modeFlags); + /** + * Determine whether the calling process of an IPC or you has been granted + * permission to access a specific URI. This is the same as + * {@link #checkCallingUriPermission}, except it grants your own permissions + * if you are not currently processing an IPC. Use with care! + * + * @param uri The uri that is being checked. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkCallingUriPermission + */ + @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") + public abstract int checkCallingOrSelfUriPermission(Uri uri, + int modeFlags); + /** + * Check both a Uri and normal permission. This allows you to perform + * both {@link #checkPermission} and {@link #checkUriPermission} in one + * call. + * + * @param uri The Uri whose permission is to be checked, or null to not + * do this check. + * @param readPermission The permission that provides overall read access, + * or null to not do this check. + * @param writePermission The permission that provides overall write + * access, or null to not do this check. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri or holds one of the given permissions, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + */ + @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)") + public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, + int modeFlags); + /** + * If a particular process and user ID has not been granted + * permission to access a specific URI, throw {@link + * SecurityException}. This only checks for permissions that have + * been explicitly granted -- if the given process/uid has more + * general access to the URI's content provider then this check + * will always fail. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkUriPermission(Uri, int, int, int) + */ + public abstract void enforceUriPermission( + Uri uri, int pid, int uid, int modeFlags, String message); + /** + * If the calling process and user ID has not been granted + * permission to access a specific URI, throw {@link + * SecurityException}. This is basically the same as calling + * {@link #enforceUriPermission(Uri, int, int, int, String)} with + * the pid and uid returned by {@link + * android.os.Binder#getCallingPid} and {@link + * android.os.Binder#getCallingUid}. One important difference is + * that if you are not currently processing an IPC, this function + * will always throw a SecurityException. + * + * @param uri The uri that is being checked. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingUriPermission(Uri, int) + */ + public abstract void enforceCallingUriPermission( + Uri uri, int modeFlags, String message); + /** + * If the calling process of an IPC or you has not been + * granted permission to access a specific URI, throw {@link + * SecurityException}. This is the same as {@link + * #enforceCallingUriPermission}, except it grants your own + * permissions if you are not currently processing an IPC. Use + * with care! + * + * @param uri The uri that is being checked. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingOrSelfUriPermission(Uri, int) + */ + public abstract void enforceCallingOrSelfUriPermission( + Uri uri, int modeFlags, String message); + /** + * Enforce both a Uri and normal permission. This allows you to perform + * both {@link #enforcePermission} and {@link #enforceUriPermission} in one + * call. + * + * @param uri The Uri whose permission is to be checked, or null to not + * do this check. + * @param readPermission The permission that provides overall read access, + * or null to not do this check. + * @param writePermission The permission that provides overall write + * access, or null to not do this check. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The type of access to grant. May be one or both of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION}. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkUriPermission(Uri, String, String, int, int, int) + */ + public abstract void enforceUriPermission( + @Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, int modeFlags, + @Nullable String message); + /** @hide */ + @IntDef(flag = true, + value = {CONTEXT_INCLUDE_CODE, CONTEXT_IGNORE_SECURITY, CONTEXT_RESTRICTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface CreatePackageOptions {} + /** + * Flag for use with {@link #createPackageContext}: include the application + * code with the context. This means loading code into the caller's + * process, so that {@link #getClassLoader()} can be used to instantiate + * the application's classes. Setting this flags imposes security + * restrictions on what application context you can access; if the + * requested application can not be safely loaded into your process, + * java.lang.SecurityException will be thrown. If this flag is not set, + * there will be no restrictions on the packages that can be loaded, + * but {@link #getClassLoader} will always return the default system + * class loader. + */ + public static final int CONTEXT_INCLUDE_CODE = 0x00000001; + /** + * Flag for use with {@link #createPackageContext}: ignore any security + * restrictions on the Context being requested, allowing it to always + * be loaded. For use with {@link #CONTEXT_INCLUDE_CODE} to allow code + * to be loaded into a process even when it isn't safe to do so. Use + * with extreme care! + */ + public static final int CONTEXT_IGNORE_SECURITY = 0x00000002; + /** + * Flag for use with {@link #createPackageContext}: a restricted context may + * disable specific features. For instance, a View associated with a restricted + * context would ignore particular XML attributes. + */ + public static final int CONTEXT_RESTRICTED = 0x00000004; + /** + * Flag for use with {@link #createPackageContext}: point all file APIs at + * device-protected storage. + * + * @hide + */ + public static final int CONTEXT_DEVICE_PROTECTED_STORAGE = 0x00000008; + /** + * Flag for use with {@link #createPackageContext}: point all file APIs at + * credential-protected storage. + * + * @hide + */ + public static final int CONTEXT_CREDENTIAL_PROTECTED_STORAGE = 0x00000010; + /** + * @hide Used to indicate we should tell the activity manager about the process + * loading this code. + */ + public static final int CONTEXT_REGISTER_PACKAGE = 0x40000000; + /** + * Return a new Context object for the given application name. This + * Context is the same as what the named application gets when it is + * launched, containing the same resources and class loader. Each call to + * this method returns a new instance of a Context object; Context objects + * are not shared, however they share common state (Resources, ClassLoader, + * etc) so the Context instance itself is fairly lightweight. + * + *

Throws {@link android.content.pm.PackageManager.NameNotFoundException} if there is no + * application with the given package name. + * + *

Throws {@link java.lang.SecurityException} if the Context requested + * can not be loaded into the caller's process for security reasons (see + * {@link #CONTEXT_INCLUDE_CODE} for more information}. + * + * @param packageName Name of the application's package. + * @param flags Option flags, one of {@link #CONTEXT_INCLUDE_CODE} + * or {@link #CONTEXT_IGNORE_SECURITY}. + * + * @return A {@link Context} for the application. + * + * @throws SecurityException   + * @throws PackageManager.NameNotFoundException if there is no application with + * the given package name. + */ + public abstract Context createPackageContext(String packageName, + @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException; + /** + * Similar to {@link #createPackageContext(String, int)}, but with a + * different {@link UserHandle}. For example, {@link #getContentResolver()} + * will open any {@link Uri} as the given user. + * + * @hide + */ + public abstract Context createPackageContextAsUser( + String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException; + /** + * Creates a context given an {@link android.content.pm.ApplicationInfo}. + * + * @hide + */ + public abstract Context createApplicationContext(ApplicationInfo application, + int flags) throws PackageManager.NameNotFoundException; + /** + * Get the userId associated with this context + * @return user id + * + * @hide + */ + @TestApi + public abstract @UserIdInt int getUserId(); + /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the given Configuration. Each call to this method + * returns a new instance of a Context object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * @param overrideConfiguration A {@link Configuration} specifying what + * values to modify in the base Configuration of the original Context's + * resources. If the base configuration changes (such as due to an + * orientation change), the resources of this context will also change except + * for those that have been explicitly overridden with a value here. + * + * @return A {@link Context} with the given configuration override. + */ + public abstract Context createConfigurationContext( + @NonNull Configuration overrideConfiguration); + /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the metrics of the given Display. Each call to this method + * returns a new instance of a Context object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * The returned display Context provides a {@link WindowManager} + * (see {@link #getSystemService(String)}) that is configured to show windows + * on the given display. The WindowManager's {@link WindowManager#getDefaultDisplay} + * method can be used to retrieve the Display from the returned Context. + * + * @param display A {@link Display} object specifying the display + * for whose metrics the Context's resources should be tailored and upon which + * new windows should be shown. + * + * @return A {@link Context} for the display. + */ + public abstract Context createDisplayContext(@NonNull Display display); + /** + * Return a new Context object for the current Context but whose storage + * APIs are backed by device-protected storage. + *

+ * On devices with direct boot, data stored in this location is encrypted + * with a key tied to the physical device, and it can be accessed + * immediately after the device has booted successfully, both + * before and after the user has authenticated with their + * credentials (such as a lock pattern or PIN). + *

+ * Because device-protected data is available without user authentication, + * you should carefully limit the data you store using this Context. For + * example, storing sensitive authentication tokens or passwords in the + * device-protected area is strongly discouraged. + *

+ * If the underlying device does not have the ability to store + * device-protected and credential-protected data using different keys, then + * both storage areas will become available at the same time. They remain as + * two distinct storage locations on disk, and only the window of + * availability changes. + *

+ * Each call to this method returns a new instance of a Context object; + * Context objects are not shared, however common state (ClassLoader, other + * Resources for the same configuration) may be so the Context itself can be + * fairly lightweight. + * + * @see #isDeviceProtectedStorage() + */ + public abstract Context createDeviceProtectedStorageContext(); + /** @removed */ + @Deprecated + public Context createDeviceEncryptedStorageContext() { + return createDeviceProtectedStorageContext(); + } + /** + * Return a new Context object for the current Context but whose storage + * APIs are backed by credential-protected storage. This is the default + * storage area for apps unless + * {@link android.R.attr#defaultToDeviceProtectedStorage} was requested. + *

+ * On devices with direct boot, data stored in this location is encrypted + * with a key tied to user credentials, which can be accessed + * only after the user has entered their credentials (such as a + * lock pattern or PIN). + *

+ * If the underlying device does not have the ability to store + * device-protected and credential-protected data using different keys, then + * both storage areas will become available at the same time. They remain as + * two distinct storage locations on disk, and only the window of + * availability changes. + *

+ * Each call to this method returns a new instance of a Context object; + * Context objects are not shared, however common state (ClassLoader, other + * Resources for the same configuration) may be so the Context itself can be + * fairly lightweight. + * + * @see #isCredentialProtectedStorage() + * @hide + */ + @SystemApi + public abstract Context createCredentialProtectedStorageContext(); + /** @removed */ + @Deprecated + public Context createCredentialEncryptedStorageContext() { + return createCredentialProtectedStorageContext(); + } + /** + * Gets the display adjustments holder for this context. This information + * is provided on a per-application or activity basis and is used to simulate lower density + * display metrics for legacy applications and restricted screen sizes. + * + * @param displayId The display id for which to get compatibility info. + * @return The compatibility info holder, or null if not required by the application. + * @hide + */ + public abstract DisplayAdjustments getDisplayAdjustments(int displayId); + /** + * @hide + */ + public abstract Display getDisplay(); + /** + * Indicates whether this Context is restricted. + * + * @return {@code true} if this Context is restricted, {@code false} otherwise. + * + * @see #CONTEXT_RESTRICTED + */ + public boolean isRestricted() { + return false; + } + /** + * Indicates if the storage APIs of this Context are backed by + * device-protected storage. + * + * @see #createDeviceProtectedStorageContext() + */ + public abstract boolean isDeviceProtectedStorage(); + /** @removed */ + @Deprecated + public boolean isDeviceEncryptedStorage() { + return isDeviceProtectedStorage(); + } + /** + * Indicates if the storage APIs of this Context are backed by + * credential-protected storage. + * + * @see #createCredentialProtectedStorageContext() + * @hide + */ + @SystemApi + public abstract boolean isCredentialProtectedStorage(); + /** @removed */ + @Deprecated + public boolean isCredentialEncryptedStorage() { + return isCredentialProtectedStorage(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/ContextWrapper.java b/AndroidCompat/src/main/java/android/content/ContextWrapper.java new file mode 100644 index 00000000..96951fcb --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/ContextWrapper.java @@ -0,0 +1,720 @@ +/* + * 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 android.content; +import android.annotation.SystemApi; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.view.Display; +import android.view.DisplayAdjustments; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +/** + * Proxying implementation of Context that simply delegates all of its calls to + * another Context. Can be subclassed to modify behavior without changing + * the original Context. + */ +public class ContextWrapper extends Context { + Context mBase; + public ContextWrapper(Context base) { + mBase = base; + } + + /** + * Set the base context for this ContextWrapper. All calls will then be + * delegated to the base context. Throws + * IllegalStateException if a base context has already been set. + * + * @param base The new base context for this wrapper. + */ + protected void attachBaseContext(Context base) { + if (mBase != null) { + throw new IllegalStateException("Base context already set"); + } + mBase = base; + } + /** + * @return the base context as set by the constructor or setBaseContext + */ + public Context getBaseContext() { + return mBase; + } + @Override + public AssetManager getAssets() { + return mBase.getAssets(); + } + @Override + public Resources getResources() { + return mBase.getResources(); + } + @Override + public PackageManager getPackageManager() { + return mBase.getPackageManager(); + } + @Override + public ContentResolver getContentResolver() { + return mBase.getContentResolver(); + } + @Override + public Looper getMainLooper() { + return mBase.getMainLooper(); + } + + @Override + public Context getApplicationContext() { + return mBase.getApplicationContext(); + } + + @Override + public void setTheme(int resid) { + mBase.setTheme(resid); + } + /** @hide */ + @Override + public int getThemeResId() { + return mBase.getThemeResId(); + } + @Override + public Resources.Theme getTheme() { + return mBase.getTheme(); + } + @Override + public ClassLoader getClassLoader() { + return mBase.getClassLoader(); + } + @Override + public String getPackageName() { + return mBase.getPackageName(); + } + /** @hide */ + @Override + public String getBasePackageName() { + return mBase.getBasePackageName(); + } + /** @hide */ + @Override + public String getOpPackageName() { + return mBase.getOpPackageName(); + } + @Override + public ApplicationInfo getApplicationInfo() { + return mBase.getApplicationInfo(); + } + + @Override + public String getPackageResourcePath() { + return mBase.getPackageResourcePath(); + } + @Override + public String getPackageCodePath() { + return mBase.getPackageCodePath(); + } + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + return mBase.getSharedPreferences(name, mode); + } + /** @removed */ + @Override + public SharedPreferences getSharedPreferences(File file, int mode) { + return mBase.getSharedPreferences(file, mode); + } + @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + return mBase.moveSharedPreferencesFrom(sourceContext, name); + } + @Override + public boolean deleteSharedPreferences(String name) { + return mBase.deleteSharedPreferences(name); + } + @Override + public FileInputStream openFileInput(String name) + throws FileNotFoundException { + return mBase.openFileInput(name); + } + @Override + public FileOutputStream openFileOutput(String name, int mode) + throws FileNotFoundException { + return mBase.openFileOutput(name, mode); + } + @Override + public boolean deleteFile(String name) { + return mBase.deleteFile(name); + } + @Override + public File getFileStreamPath(String name) { + return mBase.getFileStreamPath(name); + } + /** @removed */ + @Override + public File getSharedPreferencesPath(String name) { + return mBase.getSharedPreferencesPath(name); + } + @Override + public String[] fileList() { + return mBase.fileList(); + } + @Override + public File getDataDir() { + return mBase.getDataDir(); + } + @Override + public File getFilesDir() { + return mBase.getFilesDir(); + } + @Override + public File getNoBackupFilesDir() { + return mBase.getNoBackupFilesDir(); + } + @Override + public File getExternalFilesDir(String type) { + return mBase.getExternalFilesDir(type); + } + @Override + public File[] getExternalFilesDirs(String type) { + return mBase.getExternalFilesDirs(type); + } + @Override + public File getObbDir() { + return mBase.getObbDir(); + } + @Override + public File[] getObbDirs() { + return mBase.getObbDirs(); + } + @Override + public File getCacheDir() { + return mBase.getCacheDir(); + } + @Override + public File getCodeCacheDir() { + return mBase.getCodeCacheDir(); + } + @Override + public File getExternalCacheDir() { + return mBase.getExternalCacheDir(); + } + @Override + public File[] getExternalCacheDirs() { + return mBase.getExternalCacheDirs(); + } + @Override + public File[] getExternalMediaDirs() { + return mBase.getExternalMediaDirs(); + } + @Override + public File getDir(String name, int mode) { + return mBase.getDir(name, mode); + } + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { + return mBase.openOrCreateDatabase(name, mode, factory); + } + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, + DatabaseErrorHandler errorHandler) { + return mBase.openOrCreateDatabase(name, mode, factory, errorHandler); + } + @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + return mBase.moveDatabaseFrom(sourceContext, name); + } + @Override + public boolean deleteDatabase(String name) { + return mBase.deleteDatabase(name); + } + @Override + public File getDatabasePath(String name) { + return mBase.getDatabasePath(name); + } + @Override + public String[] databaseList() { + return mBase.databaseList(); + } + @Override + @Deprecated + public Drawable getWallpaper() { + return mBase.getWallpaper(); + } + @Override + @Deprecated + public Drawable peekWallpaper() { + return mBase.peekWallpaper(); + } + @Override + @Deprecated + public int getWallpaperDesiredMinimumWidth() { + return mBase.getWallpaperDesiredMinimumWidth(); + } + @Override + @Deprecated + public int getWallpaperDesiredMinimumHeight() { + return mBase.getWallpaperDesiredMinimumHeight(); + } + @Override + @Deprecated + public void setWallpaper(Bitmap bitmap) throws IOException { + mBase.setWallpaper(bitmap); + } + @Override + @Deprecated + public void setWallpaper(InputStream data) throws IOException { + mBase.setWallpaper(data); + } + @Override + @Deprecated + public void clearWallpaper() throws IOException { + mBase.clearWallpaper(); + } + @Override + public void startActivity(Intent intent) { + mBase.startActivity(intent); + } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, UserHandle user) { + mBase.startActivityAsUser(intent, user); + } + /** @hide **/ + public void startActivityForResult( + String who, Intent intent, int requestCode, Bundle options) { + mBase.startActivityForResult(who, intent, requestCode, options); + } + /** @hide **/ + public boolean canStartActivityForResult() { + return mBase.canStartActivityForResult(); + } + @Override + public void startActivity(Intent intent, Bundle options) { + mBase.startActivity(intent, options); + } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + mBase.startActivityAsUser(intent, options, user); + } + @Override + public void startActivities(Intent[] intents) { + mBase.startActivities(intents); + } + @Override + public void startActivities(Intent[] intents, Bundle options) { + mBase.startActivities(intents, options); + } + /** @hide */ + @Override + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + mBase.startActivitiesAsUser(intents, options, userHandle); + } + @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + mBase.startIntentSender(intent, fillInIntent, flagsMask, + flagsValues, extraFlags); + } + @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + mBase.startIntentSender(intent, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } + + @Override + public void sendBroadcast(Intent intent) { + mBase.sendBroadcast(intent); + } + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + mBase.sendBroadcast(intent, receiverPermission); + } + /** @hide */ + @Override + public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) { + mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions); + } + /** @hide */ + @SystemApi + @Override + public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { + mBase.sendBroadcast(intent, receiverPermission, options); + } + /** @hide */ + @Override + public void sendBroadcast(Intent intent, String receiverPermission, int appOp) { + mBase.sendBroadcast(intent, receiverPermission, appOp); + } + @Override + public void sendOrderedBroadcast(Intent intent, + String receiverPermission) { + mBase.sendOrderedBroadcast(intent, receiverPermission); + } + @Override + public void sendOrderedBroadcast( + Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendOrderedBroadcast(intent, receiverPermission, + resultReceiver, scheduler, initialCode, + initialData, initialExtras); + } + /** @hide */ + @SystemApi + @Override + public void sendOrderedBroadcast( + Intent intent, String receiverPermission, Bundle options, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendOrderedBroadcast(intent, receiverPermission, + options, resultReceiver, scheduler, initialCode, + initialData, initialExtras); + } + /** @hide */ + @Override + public void sendOrderedBroadcast( + Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendOrderedBroadcast(intent, receiverPermission, appOp, + resultReceiver, scheduler, initialCode, + initialData, initialExtras); + } + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + mBase.sendBroadcastAsUser(intent, user); + } + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission) { + mBase.sendBroadcastAsUser(intent, user, receiverPermission); + } + /** @hide */ + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp) { + mBase.sendBroadcastAsUser(intent, user, receiverPermission, appOp); + } + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + /** @hide */ + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + /** @hide */ + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, options, + resultReceiver, scheduler, initialCode, initialData, initialExtras); + } + @Override + @Deprecated + public void sendStickyBroadcast(Intent intent) { + mBase.sendStickyBroadcast(intent); + } + @Override + @Deprecated + public void sendStickyOrderedBroadcast( + Intent intent, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendStickyOrderedBroadcast(intent, + resultReceiver, scheduler, initialCode, + initialData, initialExtras); + } + @Override + @Deprecated + public void removeStickyBroadcast(Intent intent) { + mBase.removeStickyBroadcast(intent); + } + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + mBase.sendStickyBroadcastAsUser(intent, user); + } + /** @hide */ + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + mBase.sendStickyBroadcastAsUser(intent, user, options); + } + @Override + @Deprecated + public void sendStickyOrderedBroadcastAsUser(Intent intent, + UserHandle user, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + @Override + @Deprecated + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + mBase.removeStickyBroadcastAsUser(intent, user); + } + @Override + public Intent registerReceiver( + BroadcastReceiver receiver, IntentFilter filter) { + return mBase.registerReceiver(receiver, filter); + } + @Override + public Intent registerReceiver( + BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + return mBase.registerReceiver(receiver, filter, broadcastPermission, + scheduler); + } + /** @hide */ + @Override + public Intent registerReceiverAsUser( + BroadcastReceiver receiver, UserHandle user, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + return mBase.registerReceiverAsUser(receiver, user, filter, broadcastPermission, + scheduler); + } + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + mBase.unregisterReceiver(receiver); + } + @Override + public ComponentName startService(Intent service) { + return mBase.startService(service); + } + @Override + public boolean stopService(Intent name) { + return mBase.stopService(name); + } + /** @hide */ + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { + return mBase.startServiceAsUser(service, user); + } + /** @hide */ + @Override + public boolean stopServiceAsUser(Intent name, UserHandle user) { + return mBase.stopServiceAsUser(name, user); + } + @Override + public boolean bindService(Intent service, ServiceConnection conn, + int flags) { + return mBase.bindService(service, conn, flags); + } + /** @hide */ + @Override + public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, + UserHandle user) { + return mBase.bindServiceAsUser(service, conn, flags, user); + } + @Override + public void unbindService(ServiceConnection conn) { + mBase.unbindService(conn); + } + @Override + public boolean startInstrumentation(ComponentName className, + String profileFile, Bundle arguments) { + return mBase.startInstrumentation(className, profileFile, arguments); + } + @Override + public Object getSystemService(String name) { + return mBase.getSystemService(name); + } + @Override + public String getSystemServiceName(Class serviceClass) { + return mBase.getSystemServiceName(serviceClass); + } + @Override + public int checkPermission(String permission, int pid, int uid) { + return mBase.checkPermission(permission, pid, uid); + } + /** @hide */ + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + return mBase.checkPermission(permission, pid, uid, callerToken); + } + @Override + public int checkCallingPermission(String permission) { + return mBase.checkCallingPermission(permission); + } + @Override + public int checkCallingOrSelfPermission(String permission) { + return mBase.checkCallingOrSelfPermission(permission); + } + @Override + public int checkSelfPermission(String permission) { + return mBase.checkSelfPermission(permission); + } + @Override + public void enforcePermission( + String permission, int pid, int uid, String message) { + mBase.enforcePermission(permission, pid, uid, message); + } + @Override + public void enforceCallingPermission(String permission, String message) { + mBase.enforceCallingPermission(permission, message); + } + @Override + public void enforceCallingOrSelfPermission( + String permission, String message) { + mBase.enforceCallingOrSelfPermission(permission, message); + } + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + mBase.grantUriPermission(toPackage, uri, modeFlags); + } + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + mBase.revokeUriPermission(uri, modeFlags); + } + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + return mBase.checkUriPermission(uri, pid, uid, modeFlags); + } + /** @hide */ + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + return mBase.checkUriPermission(uri, pid, uid, modeFlags, callerToken); + } + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + return mBase.checkCallingUriPermission(uri, modeFlags); + } + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + return mBase.checkCallingOrSelfUriPermission(uri, modeFlags); + } + @Override + public int checkUriPermission(Uri uri, String readPermission, + String writePermission, int pid, int uid, int modeFlags) { + return mBase.checkUriPermission(uri, readPermission, writePermission, + pid, uid, modeFlags); + } + @Override + public void enforceUriPermission( + Uri uri, int pid, int uid, int modeFlags, String message) { + mBase.enforceUriPermission(uri, pid, uid, modeFlags, message); + } + @Override + public void enforceCallingUriPermission( + Uri uri, int modeFlags, String message) { + mBase.enforceCallingUriPermission(uri, modeFlags, message); + } + @Override + public void enforceCallingOrSelfUriPermission( + Uri uri, int modeFlags, String message) { + mBase.enforceCallingOrSelfUriPermission(uri, modeFlags, message); + } + @Override + public void enforceUriPermission( + Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags, String message) { + mBase.enforceUriPermission( + uri, readPermission, writePermission, pid, uid, modeFlags, + message); + } + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + return mBase.createPackageContext(packageName, flags); + } + /** @hide */ + @Override + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException { + return mBase.createPackageContextAsUser(packageName, flags, user); + } + /** @hide */ + @Override + public Context createApplicationContext(ApplicationInfo application, + int flags) throws PackageManager.NameNotFoundException { + return mBase.createApplicationContext(application, flags); + } + /** @hide */ + @Override + public int getUserId() { + return mBase.getUserId(); + } + @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + return mBase.createConfigurationContext(overrideConfiguration); + } + @Override + public Context createDisplayContext(Display display) { + return mBase.createDisplayContext(display); + } + @Override + public boolean isRestricted() { + return mBase.isRestricted(); + } + /** @hide */ + @Override + public DisplayAdjustments getDisplayAdjustments(int displayId) { + return mBase.getDisplayAdjustments(displayId); + } + /** + * @hide + */ + @Override + public Display getDisplay() { + return mBase.getDisplay(); + } + @Override + public Context createDeviceProtectedStorageContext() { + return mBase.createDeviceProtectedStorageContext(); + } + /** {@hide} */ + @SystemApi + @Override + public Context createCredentialProtectedStorageContext() { + return mBase.createCredentialProtectedStorageContext(); + } + @Override + public boolean isDeviceProtectedStorage() { + return mBase.isDeviceProtectedStorage(); + } + /** {@hide} */ + @SystemApi + @Override + public boolean isCredentialProtectedStorage() { + return mBase.isCredentialProtectedStorage(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/Intent.java b/AndroidCompat/src/main/java/android/content/Intent.java new file mode 100644 index 00000000..5f4074dd --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/Intent.java @@ -0,0 +1,7625 @@ +/* + * 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 android.content; + +import android.annotation.AnyRes; +import android.annotation.IntDef; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; +import android.content.pm.*; +import android.graphics.Rect; +import android.net.Uri; +import android.os.*; +import android.provider.DocumentsContract; +import android.provider.DocumentsProvider; +import android.provider.OpenableColumns; +import android.util.ArraySet; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.URISyntaxException; +import java.util.*; +/** + * An intent is an abstract description of an operation to be performed. It + * can be used with {@link Context#startActivity(Intent) startActivity} to + * launch an {@link android.app.Activity}, + * {@link android.content.Context#sendBroadcast(Intent) broadcastIntent} to + * send it to any interested {@link BroadcastReceiver BroadcastReceiver} components, + * and {@link android.content.Context#startService} or + * {@link android.content.Context#bindService} to communicate with a + * background {@link android.app.Service}. + * + *

An Intent provides a facility for performing late runtime binding between the code in + * different applications. Its most significant use is in the launching of activities, where it + * can be thought of as the glue between activities. It is basically a passive data structure + * holding an abstract description of an action to be performed.

+ * + *
+ *

Developer Guides

+ *

For information about how to create and resolve intents, read the + * Intents and Intent Filters + * developer guide.

+ *
+ * + * + *

Intent Structure

+ *

The primary pieces of information in an intent are:

+ * + *
    + *
  • action -- The general action to be performed, such as + * {@link #ACTION_VIEW}, {@link #ACTION_EDIT}, {@link #ACTION_MAIN}, + * etc.

    + *
  • + *
  • data -- The data to operate on, such as a person record + * in the contacts database, expressed as a {@link android.net.Uri}.

    + *
  • + *
+ * + * + *

Some examples of action/data pairs are:

+ * + *
    + *
  • {@link #ACTION_VIEW} content://contacts/people/1 -- Display + * information about the person whose identifier is "1".

    + *
  • + *
  • {@link #ACTION_DIAL} content://contacts/people/1 -- Display + * the phone dialer with the person filled in.

    + *
  • + *
  • {@link #ACTION_VIEW} tel:123 -- Display + * the phone dialer with the given number filled in. Note how the + * VIEW action does what is considered the most reasonable thing for + * a particular URI.

    + *
  • + *
  • {@link #ACTION_DIAL} tel:123 -- Display + * the phone dialer with the given number filled in.

    + *
  • + *
  • {@link #ACTION_EDIT} content://contacts/people/1 -- Edit + * information about the person whose identifier is "1".

    + *
  • + *
  • {@link #ACTION_VIEW} content://contacts/people/ -- Display + * a list of people, which the user can browse through. This example is a + * typical top-level entry into the Contacts application, showing you the + * list of people. Selecting a particular person to view would result in a + * new intent { {@link #ACTION_VIEW} content://contacts/people/N } + * being used to start an activity to display that person.

    + *
  • + *
+ * + *

In addition to these primary attributes, there are a number of secondary + * attributes that you can also include with an intent:

+ * + *
    + *
  • category -- Gives additional information about the action + * to execute. For example, {@link #CATEGORY_LAUNCHER} means it should + * appear in the Launcher as a top-level application, while + * {@link #CATEGORY_ALTERNATIVE} means it should be included in a list + * of alternative actions the user can perform on a piece of data.

    + *
  • type -- Specifies an explicit type (a MIME type) of the + * intent data. Normally the type is inferred from the data itself. + * By setting this attribute, you disable that evaluation and force + * an explicit type.

    + *
  • component -- Specifies an explicit name of a component + * class to use for the intent. Normally this is determined by looking + * at the other information in the intent (the action, data/type, and + * categories) and matching that with a component that can handle it. + * If this attribute is set then none of the evaluation is performed, + * and this component is used exactly as is. By specifying this attribute, + * all of the other Intent attributes become optional.

    + *
  • extras -- This is a {@link Bundle} of any additional information. + * This can be used to provide extended information to the component. + * For example, if we have a action to send an e-mail message, we could + * also include extra pieces of data here to supply a subject, body, + * etc.

    + *
+ * + *

Here are some examples of other operations you can specify as intents + * using these additional parameters:

+ * + *
    + *
  • {@link #ACTION_MAIN} with category {@link #CATEGORY_HOME} -- + * Launch the home screen.

    + *
  • + *
  • {@link #ACTION_GET_CONTENT} with MIME type + * {@link android.provider.Contacts.Phones#CONTENT_URI + * vnd.android.cursor.item/phone} + * -- Display the list of people's phone numbers, allowing the user to + * browse through them and pick one and return it to the parent activity.

    + *
  • + *
  • {@link #ACTION_GET_CONTENT} with MIME type + * *{@literal /}* and category {@link #CATEGORY_OPENABLE} + * -- Display all pickers for data that can be opened with + * {@link ContentResolver#openInputStream(Uri) ContentResolver.openInputStream()}, + * allowing the user to pick one of them and then some data inside of it + * and returning the resulting URI to the caller. This can be used, + * for example, in an e-mail application to allow the user to pick some + * data to include as an attachment.

    + *
  • + *
+ * + *

There are a variety of standard Intent action and category constants + * defined in the Intent class, but applications can also define their own. + * These strings use Java-style scoping, to ensure they are unique -- for + * example, the standard {@link #ACTION_VIEW} is called + * "android.intent.action.VIEW".

+ * + *

Put together, the set of actions, data types, categories, and extra data + * defines a language for the system allowing for the expression of phrases + * such as "call john smith's cell". As applications are added to the system, + * they can extend this language by adding new actions, types, and categories, or + * they can modify the behavior of existing phrases by supplying their own + * activities that handle them.

+ * + * + *

Intent Resolution

+ * + *

There are two primary forms of intents you will use. + * + *

    + *
  • Explicit Intents have specified a component (via + * {@link #setComponent} or {@link #setClass}), which provides the exact + * class to be run. Often these will not include any other information, + * simply being a way for an application to launch various internal + * activities it has as the user interacts with the application. + * + *

  • Implicit Intents have not specified a component; + * instead, they must include enough information for the system to + * determine which of the available components is best to run for that + * intent. + *

+ * + *

When using implicit intents, given such an arbitrary intent we need to + * know what to do with it. This is handled by the process of Intent + * resolution, which maps an Intent to an {@link android.app.Activity}, + * {@link BroadcastReceiver}, or {@link android.app.Service} (or sometimes two or + * more activities/receivers) that can handle it.

+ * + *

The intent resolution mechanism basically revolves around matching an + * Intent against all of the <intent-filter> descriptions in the + * installed application packages. (Plus, in the case of broadcasts, any {@link BroadcastReceiver} + * objects explicitly registered with {@link Context#registerReceiver}.) More + * details on this can be found in the documentation on the {@link + * IntentFilter} class.

+ * + *

There are three pieces of information in the Intent that are used for + * resolution: the action, type, and category. Using this information, a query + * is done on the {@link PackageManager} for a component that can handle the + * intent. The appropriate component is determined based on the intent + * information supplied in the AndroidManifest.xml file as + * follows:

+ * + *
    + *
  • The action, if given, must be listed by the component as + * one it handles.

    + *
  • The type is retrieved from the Intent's data, if not + * already supplied in the Intent. Like the action, if a type is + * included in the intent (either explicitly or implicitly in its + * data), then this must be listed by the component as one it handles.

    + *
  • For data that is not a content: URI and where no explicit + * type is included in the Intent, instead the scheme of the + * intent data (such as http: or mailto:) is + * considered. Again like the action, if we are matching a scheme it + * must be listed by the component as one it can handle. + *
  • The categories, if supplied, must all be listed + * by the activity as categories it handles. That is, if you include + * the categories {@link #CATEGORY_LAUNCHER} and + * {@link #CATEGORY_ALTERNATIVE}, then you will only resolve to components + * with an intent that lists both of those categories. + * Activities will very often need to support the + * {@link #CATEGORY_DEFAULT} so that they can be found by + * {@link Context#startActivity Context.startActivity()}.

    + *
+ * + *

For example, consider the Note Pad sample application that + * allows user to browse through a list of notes data and view details about + * individual items. Text in italics indicate places were you would replace a + * name with one specific to your own package.

+ * + *
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ *       package="com.android.notepad">
+ *     <application android:icon="@drawable/app_notes"
+ *             android:label="@string/app_name">
+ *
+ *         <provider class=".NotePadProvider"
+ *                 android:authorities="com.google.provider.NotePad" />
+ *
+ *         <activity class=".NotesList" android:label="@string/title_notes_list">
+ *             <intent-filter>
+ *                 <action android:name="android.intent.action.MAIN" />
+ *                 <category android:name="android.intent.category.LAUNCHER" />
+ *             </intent-filter>
+ *             <intent-filter>
+ *                 <action android:name="android.intent.action.VIEW" />
+ *                 <action android:name="android.intent.action.EDIT" />
+ *                 <action android:name="android.intent.action.PICK" />
+ *                 <category android:name="android.intent.category.DEFAULT" />
+ *                 <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
+ *             </intent-filter>
+ *             <intent-filter>
+ *                 <action android:name="android.intent.action.GET_CONTENT" />
+ *                 <category android:name="android.intent.category.DEFAULT" />
+ *                 <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
+ *             </intent-filter>
+ *         </activity>
+ *
+ *         <activity class=".NoteEditor" android:label="@string/title_note">
+ *             <intent-filter android:label="@string/resolve_edit">
+ *                 <action android:name="android.intent.action.VIEW" />
+ *                 <action android:name="android.intent.action.EDIT" />
+ *                 <category android:name="android.intent.category.DEFAULT" />
+ *                 <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
+ *             </intent-filter>
+ *
+ *             <intent-filter>
+ *                 <action android:name="android.intent.action.INSERT" />
+ *                 <category android:name="android.intent.category.DEFAULT" />
+ *                 <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
+ *             </intent-filter>
+ *
+ *         </activity>
+ *
+ *         <activity class=".TitleEditor" android:label="@string/title_edit_title"
+ *                 android:theme="@android:style/Theme.Dialog">
+ *             <intent-filter android:label="@string/resolve_title">
+ *                 <action android:name="com.android.notepad.action.EDIT_TITLE" />
+ *                 <category android:name="android.intent.category.DEFAULT" />
+ *                 <category android:name="android.intent.category.ALTERNATIVE" />
+ *                 <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+ *                 <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
+ *             </intent-filter>
+ *         </activity>
+ *
+ *     </application>
+ * </manifest>
+ * + *

The first activity, + * com.android.notepad.NotesList, serves as our main + * entry into the app. It can do three things as described by its three intent + * templates: + *

    + *
  1. + * <intent-filter>
    + *     <action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" />
    + *     <category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" />
    + * </intent-filter>
    + *

    This provides a top-level entry into the NotePad application: the standard + * MAIN action is a main entry point (not requiring any other information in + * the Intent), and the LAUNCHER category says that this entry point should be + * listed in the application launcher.

    + *
  2. + * <intent-filter>
    + *     <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
    + *     <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
    + *     <action android:name="{@link #ACTION_PICK android.intent.action.PICK}" />
    + *     <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
    + *     <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
    + * </intent-filter>
    + *

    This declares the things that the activity can do on a directory of + * notes. The type being supported is given with the <type> tag, where + * vnd.android.cursor.dir/vnd.google.note is a URI from which + * a Cursor of zero or more items (vnd.android.cursor.dir) can + * be retrieved which holds our note pad data (vnd.google.note). + * The activity allows the user to view or edit the directory of data (via + * the VIEW and EDIT actions), or to pick a particular note and return it + * to the caller (via the PICK action). Note also the DEFAULT category + * supplied here: this is required for the + * {@link Context#startActivity Context.startActivity} method to resolve your + * activity when its component name is not explicitly specified.

    + *
  3. + * <intent-filter>
    + *     <action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" />
    + *     <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
    + *     <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
    + * </intent-filter>
    + *

    This filter describes the ability to return to the caller a note selected by + * the user without needing to know where it came from. The data type + * vnd.android.cursor.item/vnd.google.note is a URI from which + * a Cursor of exactly one (vnd.android.cursor.item) item can + * be retrieved which contains our note pad data (vnd.google.note). + * The GET_CONTENT action is similar to the PICK action, where the activity + * will return to its caller a piece of data selected by the user. Here, + * however, the caller specifies the type of data they desire instead of + * the type of data the user will be picking from.

    + *
+ * + *

Given these capabilities, the following intents will resolve to the + * NotesList activity:

+ * + *
    + *
  • { action=android.app.action.MAIN } matches all of the + * activities that can be used as top-level entry points into an + * application.

    + *
  • { action=android.app.action.MAIN, + * category=android.app.category.LAUNCHER } is the actual intent + * used by the Launcher to populate its top-level list.

    + *
  • { action=android.intent.action.VIEW + * data=content://com.google.provider.NotePad/notes } + * displays a list of all the notes under + * "content://com.google.provider.NotePad/notes", which + * the user can browse through and see the details on.

    + *
  • { action=android.app.action.PICK + * data=content://com.google.provider.NotePad/notes } + * provides a list of the notes under + * "content://com.google.provider.NotePad/notes", from which + * the user can pick a note whose data URL is returned back to the caller.

    + *
  • { action=android.app.action.GET_CONTENT + * type=vnd.android.cursor.item/vnd.google.note } + * is similar to the pick action, but allows the caller to specify the + * kind of data they want back so that the system can find the appropriate + * activity to pick something of that data type.

    + *
+ * + *

The second activity, + * com.android.notepad.NoteEditor, shows the user a single + * note entry and allows them to edit it. It can do two things as described + * by its two intent templates: + *

    + *
  1. + * <intent-filter android:label="@string/resolve_edit">
    + *     <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
    + *     <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
    + *     <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
    + *     <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
    + * </intent-filter>
    + *

    The first, primary, purpose of this activity is to let the user interact + * with a single note, as decribed by the MIME type + * vnd.android.cursor.item/vnd.google.note. The activity can + * either VIEW a note or allow the user to EDIT it. Again we support the + * DEFAULT category to allow the activity to be launched without explicitly + * specifying its component.

    + *
  2. + * <intent-filter>
    + *     <action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" />
    + *     <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
    + *     <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
    + * </intent-filter>
    + *

    The secondary use of this activity is to insert a new note entry into + * an existing directory of notes. This is used when the user creates a new + * note: the INSERT action is executed on the directory of notes, causing + * this activity to run and have the user create the new note data which + * it then adds to the content provider.

    + *
+ * + *

Given these capabilities, the following intents will resolve to the + * NoteEditor activity:

+ * + *
    + *
  • { action=android.intent.action.VIEW + * data=content://com.google.provider.NotePad/notes/{ID} } + * shows the user the content of note {ID}.

    + *
  • { action=android.app.action.EDIT + * data=content://com.google.provider.NotePad/notes/{ID} } + * allows the user to edit the content of note {ID}.

    + *
  • { action=android.app.action.INSERT + * data=content://com.google.provider.NotePad/notes } + * creates a new, empty note in the notes list at + * "content://com.google.provider.NotePad/notes" + * and allows the user to edit it. If they keep their changes, the URI + * of the newly created note is returned to the caller.

    + *
+ * + *

The last activity, + * com.android.notepad.TitleEditor, allows the user to + * edit the title of a note. This could be implemented as a class that the + * application directly invokes (by explicitly setting its component in + * the Intent), but here we show a way you can publish alternative + * operations on existing data:

+ * + *
+ * <intent-filter android:label="@string/resolve_title">
+ *     <action android:name="com.android.notepad.action.EDIT_TITLE" />
+ *     <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ *     <category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" />
+ *     <category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" />
+ *     <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
+ * </intent-filter>
+ * + *

In the single intent template here, we + * have created our own private action called + * com.android.notepad.action.EDIT_TITLE which means to + * edit the title of a note. It must be invoked on a specific note + * (data type vnd.android.cursor.item/vnd.google.note) like the previous + * view and edit actions, but here displays and edits the title contained + * in the note data. + * + *

In addition to supporting the default category as usual, our title editor + * also supports two other standard categories: ALTERNATIVE and + * SELECTED_ALTERNATIVE. Implementing + * these categories allows others to find the special action it provides + * without directly knowing about it, through the + * {@link android.content.pm.PackageManager#queryIntentActivityOptions} method, or + * more often to build dynamic menu items with + * {@link android.view.Menu#addIntentOptions}. Note that in the intent + * template here was also supply an explicit name for the template + * (via android:label="@string/resolve_title") to better control + * what the user sees when presented with this activity as an alternative + * action to the data they are viewing. + * + *

Given these capabilities, the following intent will resolve to the + * TitleEditor activity:

+ * + *
    + *
  • { action=com.android.notepad.action.EDIT_TITLE + * data=content://com.google.provider.NotePad/notes/{ID} } + * displays and allows the user to edit the title associated + * with note {ID}.

    + *
+ * + *

Standard Activity Actions

+ * + *

These are the current standard actions that Intent defines for launching + * activities (usually through {@link Context#startActivity}. The most + * important, and by far most frequently used, are {@link #ACTION_MAIN} and + * {@link #ACTION_EDIT}. + * + *

    + *
  • {@link #ACTION_MAIN} + *
  • {@link #ACTION_VIEW} + *
  • {@link #ACTION_ATTACH_DATA} + *
  • {@link #ACTION_EDIT} + *
  • {@link #ACTION_PICK} + *
  • {@link #ACTION_CHOOSER} + *
  • {@link #ACTION_GET_CONTENT} + *
  • {@link #ACTION_DIAL} + *
  • {@link #ACTION_CALL} + *
  • {@link #ACTION_SEND} + *
  • {@link #ACTION_SENDTO} + *
  • {@link #ACTION_ANSWER} + *
  • {@link #ACTION_INSERT} + *
  • {@link #ACTION_DELETE} + *
  • {@link #ACTION_RUN} + *
  • {@link #ACTION_SYNC} + *
  • {@link #ACTION_PICK_ACTIVITY} + *
  • {@link #ACTION_SEARCH} + *
  • {@link #ACTION_WEB_SEARCH} + *
  • {@link #ACTION_FACTORY_TEST} + *
+ * + *

Standard Broadcast Actions

+ * + *

These are the current standard actions that Intent defines for receiving + * broadcasts (usually through {@link Context#registerReceiver} or a + * <receiver> tag in a manifest). + * + *

    + *
  • {@link #ACTION_TIME_TICK} + *
  • {@link #ACTION_TIME_CHANGED} + *
  • {@link #ACTION_TIMEZONE_CHANGED} + *
  • {@link #ACTION_BOOT_COMPLETED} + *
  • {@link #ACTION_PACKAGE_ADDED} + *
  • {@link #ACTION_PACKAGE_CHANGED} + *
  • {@link #ACTION_PACKAGE_REMOVED} + *
  • {@link #ACTION_PACKAGE_RESTARTED} + *
  • {@link #ACTION_PACKAGE_DATA_CLEARED} + *
  • {@link #ACTION_PACKAGES_SUSPENDED} + *
  • {@link #ACTION_PACKAGES_UNSUSPENDED} + *
  • {@link #ACTION_UID_REMOVED} + *
  • {@link #ACTION_BATTERY_CHANGED} + *
  • {@link #ACTION_POWER_CONNECTED} + *
  • {@link #ACTION_POWER_DISCONNECTED} + *
  • {@link #ACTION_SHUTDOWN} + *
+ * + *

Standard Categories

+ * + *

These are the current standard categories that can be used to further + * clarify an Intent via {@link #addCategory}. + * + *

    + *
  • {@link #CATEGORY_DEFAULT} + *
  • {@link #CATEGORY_BROWSABLE} + *
  • {@link #CATEGORY_TAB} + *
  • {@link #CATEGORY_ALTERNATIVE} + *
  • {@link #CATEGORY_SELECTED_ALTERNATIVE} + *
  • {@link #CATEGORY_LAUNCHER} + *
  • {@link #CATEGORY_INFO} + *
  • {@link #CATEGORY_HOME} + *
  • {@link #CATEGORY_PREFERENCE} + *
  • {@link #CATEGORY_TEST} + *
  • {@link #CATEGORY_CAR_DOCK} + *
  • {@link #CATEGORY_DESK_DOCK} + *
  • {@link #CATEGORY_LE_DESK_DOCK} + *
  • {@link #CATEGORY_HE_DESK_DOCK} + *
  • {@link #CATEGORY_CAR_MODE} + *
  • {@link #CATEGORY_APP_MARKET} + *
+ * + *

Standard Extra Data

+ * + *

These are the current standard fields that can be used as extra data via + * {@link #putExtra}. + * + *

    + *
  • {@link #EXTRA_ALARM_COUNT} + *
  • {@link #EXTRA_BCC} + *
  • {@link #EXTRA_CC} + *
  • {@link #EXTRA_CHANGED_COMPONENT_NAME} + *
  • {@link #EXTRA_DATA_REMOVED} + *
  • {@link #EXTRA_DOCK_STATE} + *
  • {@link #EXTRA_DOCK_STATE_HE_DESK} + *
  • {@link #EXTRA_DOCK_STATE_LE_DESK} + *
  • {@link #EXTRA_DOCK_STATE_CAR} + *
  • {@link #EXTRA_DOCK_STATE_DESK} + *
  • {@link #EXTRA_DOCK_STATE_UNDOCKED} + *
  • {@link #EXTRA_DONT_KILL_APP} + *
  • {@link #EXTRA_EMAIL} + *
  • {@link #EXTRA_INITIAL_INTENTS} + *
  • {@link #EXTRA_INTENT} + *
  • {@link #EXTRA_KEY_EVENT} + *
  • {@link #EXTRA_ORIGINATING_URI} + *
  • {@link #EXTRA_PHONE_NUMBER} + *
  • {@link #EXTRA_REFERRER} + *
  • {@link #EXTRA_REMOTE_INTENT_TOKEN} + *
  • {@link #EXTRA_REPLACING} + *
  • {@link #EXTRA_SHORTCUT_ICON} + *
  • {@link #EXTRA_SHORTCUT_ICON_RESOURCE} + *
  • {@link #EXTRA_SHORTCUT_INTENT} + *
  • {@link #EXTRA_STREAM} + *
  • {@link #EXTRA_SHORTCUT_NAME} + *
  • {@link #EXTRA_SUBJECT} + *
  • {@link #EXTRA_TEMPLATE} + *
  • {@link #EXTRA_TEXT} + *
  • {@link #EXTRA_TITLE} + *
  • {@link #EXTRA_UID} + *
+ * + *

Flags

+ * + *

These are the possible flags that can be used in the Intent via + * {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list + * of all possible flags. + */ +public class Intent implements Parcelable, Cloneable { + private static final String ATTR_ACTION = "action"; + private static final String TAG_CATEGORIES = "categories"; + private static final String ATTR_CATEGORY = "category"; + private static final String TAG_EXTRA = "extra"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_COMPONENT = "component"; + private static final String ATTR_DATA = "data"; + private static final String ATTR_FLAGS = "flags"; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Standard intent activity actions (see action variable). + /** + * Activity Action: Start as a main entry point, does not expect to + * receive data. + *

Input: nothing + *

Output: nothing + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MAIN = "android.intent.action.MAIN"; + /** + * Activity Action: Display the data to the user. This is the most common + * action performed on data -- it is the generic action you can use on + * a piece of data to get the most reasonable thing to occur. For example, + * when used on a contacts entry it will view the entry; when used on a + * mailto: URI it will bring up a compose window filled with the information + * supplied by the URI; when used with a tel: URI it will invoke the + * dialer. + *

Input: {@link #getData} is URI from which to retrieve data. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VIEW = "android.intent.action.VIEW"; + /** + * A synonym for {@link #ACTION_VIEW}, the "standard" action that is + * performed on a piece of data. + */ + public static final String ACTION_DEFAULT = ACTION_VIEW; + /** + * Activity Action: Quick view the data. Launches a quick viewer for + * a URI or a list of URIs. + *

Activities handling this intent action should handle the vast majority of + * MIME types rather than only specific ones. + *

Input: {@link #getData} is a mandatory content URI of the item to + * preview. {@link #getClipData} contains an optional list of content URIs + * if there is more than one item to preview. {@link #EXTRA_INDEX} is an + * optional index of the URI in the clip data to show first. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW"; + /** + * Used to indicate that some piece of data should be attached to some other + * place. For example, image data could be attached to a contact. It is up + * to the recipient to decide where the data should be attached; the intent + * does not specify the ultimate destination. + *

Input: {@link #getData} is URI of data to be attached. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA"; + /** + * Activity Action: Provide explicit editable access to the given data. + *

Input: {@link #getData} is URI of data to be edited. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_EDIT = "android.intent.action.EDIT"; + /** + * Activity Action: Pick an existing item, or insert a new item, and then edit it. + *

Input: {@link #getType} is the desired MIME type of the item to create or edit. + * The extras can contain type specific data to pass through to the editing/creating + * activity. + *

Output: The URI of the item that was picked. This must be a content: + * URI so that any receiver can access it. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT"; + /** + * Activity Action: Pick an item from the data, returning what was selected. + *

Input: {@link #getData} is URI containing a directory of data + * (vnd.android.cursor.dir/*) from which to pick an item. + *

Output: The URI of the item that was picked. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICK = "android.intent.action.PICK"; + /** + * Activity Action: Creates a shortcut. + *

Input: Nothing.

+ *

Output: An Intent representing the shortcut. The intent must contain three + * extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String), + * and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE + * (value: ShortcutIconResource).

+ * + * @see #EXTRA_SHORTCUT_INTENT + * @see #EXTRA_SHORTCUT_NAME + * @see #EXTRA_SHORTCUT_ICON + * @see #EXTRA_SHORTCUT_ICON_RESOURCE + * @see android.content.Intent.ShortcutIconResource + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT"; + /** + * The name of the extra used to define the Intent of a shortcut. + * + * @see #ACTION_CREATE_SHORTCUT + */ + public static final String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT"; + /** + * The name of the extra used to define the name of a shortcut. + * + * @see #ACTION_CREATE_SHORTCUT + */ + public static final String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME"; + /** + * The name of the extra used to define the icon, as a Bitmap, of a shortcut. + * + * @see #ACTION_CREATE_SHORTCUT + */ + public static final String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON"; + /** + * The name of the extra used to define the icon, as a ShortcutIconResource, of a shortcut. + * + * @see #ACTION_CREATE_SHORTCUT + * @see android.content.Intent.ShortcutIconResource + */ + public static final String EXTRA_SHORTCUT_ICON_RESOURCE = + "android.intent.extra.shortcut.ICON_RESOURCE"; + /** + * An activity that provides a user interface for adjusting application preferences. + * Optional but recommended settings for all applications which have settings. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APPLICATION_PREFERENCES + = "android.intent.action.APPLICATION_PREFERENCES"; + /** + * Activity Action: Launch an activity showing the app information. + * For applications which install other applications (such as app stores), it is recommended + * to handle this action for providing the app information to the user. + * + *

Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose information needs + * to be displayed. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SHOW_APP_INFO + = "android.intent.action.SHOW_APP_INFO"; + /** + * Represents a shortcut/live folder icon resource. + * + * @see Intent#ACTION_CREATE_SHORTCUT + * @see Intent#EXTRA_SHORTCUT_ICON_RESOURCE + * @see android.provider.LiveFolders#ACTION_CREATE_LIVE_FOLDER + * @see android.provider.LiveFolders#EXTRA_LIVE_FOLDER_ICON + */ + public static class ShortcutIconResource implements Parcelable { + /** + * The package name of the application containing the icon. + */ + public String packageName; + /** + * The resource name of the icon, including package, name and type. + */ + public String resourceName; + /** + * Creates a new ShortcutIconResource for the specified context and resource + * identifier. + * + * @param context The context of the application. + * @param resourceId The resource identifier for the icon. + * @return A new ShortcutIconResource with the specified's context package name + * and icon resource identifier.`` + */ + public static ShortcutIconResource fromContext(Context context, @AnyRes int resourceId) { + ShortcutIconResource icon = new ShortcutIconResource(); + icon.packageName = context.getPackageName(); + icon.resourceName = context.getResources().getResourceName(resourceId); + return icon; + } + /** + * Used to read a ShortcutIconResource from a Parcel. + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ShortcutIconResource createFromParcel(Parcel source) { + ShortcutIconResource icon = new ShortcutIconResource(); + icon.packageName = source.readString(); + icon.resourceName = source.readString(); + return icon; + } + public ShortcutIconResource[] newArray(int size) { + return new ShortcutIconResource[size]; + } + }; + /** + * No special parcel contents. + */ + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(packageName); + dest.writeString(resourceName); + } + @Override + public String toString() { + return resourceName; + } + } + /** + * Activity Action: Display an activity chooser, allowing the user to pick + * what they want to before proceeding. This can be used as an alternative + * to the standard activity picker that is displayed by the system when + * you try to start an activity with multiple possible matches, with these + * differences in behavior: + *

    + *
  • You can specify the title that will appear in the activity chooser. + *
  • The user does not have the option to make one of the matching + * activities a preferred activity, and all possible activities will + * always be shown even if one of them is currently marked as the + * preferred activity. + *
+ *

+ * This action should be used when the user will naturally expect to + * select an activity in order to proceed. An example if when not to use + * it is when the user clicks on a "mailto:" link. They would naturally + * expect to go directly to their mail app, so startActivity() should be + * called directly: it will + * either launch the current preferred app, or put up a dialog allowing the + * user to pick an app to use and optionally marking that as preferred. + *

+ * In contrast, if the user is selecting a menu item to send a picture + * they are viewing to someone else, there are many different things they + * may want to do at this point: send it through e-mail, upload it to a + * web service, etc. In this case the CHOOSER action should be used, to + * always present to the user a list of the things they can do, with a + * nice title given by the caller such as "Send this photo with:". + *

+ * If you need to grant URI permissions through a chooser, you must specify + * the permissions to be granted on the ACTION_CHOOSER Intent + * in addition to the EXTRA_INTENT inside. This means using + * {@link #setClipData} to specify the URIs to be granted as well as + * {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION} as appropriate. + *

+ * As a convenience, an Intent of this form can be created with the + * {@link #createChooser} function. + *

+ * Input: No data should be specified. get*Extra must have + * a {@link #EXTRA_INTENT} field containing the Intent being executed, + * and can optionally have a {@link #EXTRA_TITLE} field containing the + * title text to display in the chooser. + *

+ * Output: Depends on the protocol of {@link #EXTRA_INTENT}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHOOSER = "android.intent.action.CHOOSER"; + /** + * Convenience function for creating a {@link #ACTION_CHOOSER} Intent. + * + *

Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given + * target intent, also optionally supplying a title. If the target + * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be + * set in the returned chooser intent, with its ClipData set appropriately: + * either a direct reflection of {@link #getClipData()} if that is non-null, + * or a new ClipData built from {@link #getData()}. + * + * @param target The Intent that the user will be selecting an activity + * to perform. + * @param title Optional title that will be displayed in the chooser. + * @return Return a new Intent object that you can hand to + * {@link Context#startActivity(Intent) Context.startActivity()} and + * related methods. + */ + public static Intent createChooser(Intent target, CharSequence title) { + return createChooser(target, title, null); + } + /** + * Convenience function for creating a {@link #ACTION_CHOOSER} Intent. + * + *

Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given + * target intent, also optionally supplying a title. If the target + * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be + * set in the returned chooser intent, with its ClipData set appropriately: + * either a direct reflection of {@link #getClipData()} if that is non-null, + * or a new ClipData built from {@link #getData()}.

+ * + *

The caller may optionally supply an {@link IntentSender} to receive a callback + * when the user makes a choice. This can be useful if the calling application wants + * to remember the last chosen target and surface it as a more prominent or one-touch + * affordance elsewhere in the UI for next time.

+ * + * @param target The Intent that the user will be selecting an activity + * to perform. + * @param title Optional title that will be displayed in the chooser. + * @param sender Optional IntentSender to be called when a choice is made. + * @return Return a new Intent object that you can hand to + * {@link Context#startActivity(Intent) Context.startActivity()} and + * related methods. + */ + public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) { + Intent intent = new Intent(ACTION_CHOOSER); + intent.putExtra(EXTRA_INTENT, target); + if (title != null) { + intent.putExtra(EXTRA_TITLE, title); + } + if (sender != null) { + intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender); + } + // Migrate any clip data and flags from target. + int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION + | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | FLAG_GRANT_PREFIX_URI_PERMISSION); + if (permFlags != 0) { + ClipData targetClipData = target.getClipData(); + if (targetClipData == null && target.getData() != null) { + ClipData.Item item = new ClipData.Item(target.getData()); + String[] mimeTypes; + if (target.getType() != null) { + mimeTypes = new String[] { target.getType() }; + } else { + mimeTypes = new String[] { }; + } + targetClipData = new ClipData(null, mimeTypes, item); + } + if (targetClipData != null) { + intent.setClipData(targetClipData); + intent.addFlags(permFlags); + } + } + return intent; + } + /** + * Activity Action: Allow the user to select a particular kind of data and + * return it. This is different than {@link #ACTION_PICK} in that here we + * just say what kind of data is desired, not a URI of existing data from + * which the user can pick. An ACTION_GET_CONTENT could allow the user to + * create the data as it runs (for example taking a picture or recording a + * sound), let them browse over the web and download the desired data, + * etc. + *

+ * There are two main ways to use this action: if you want a specific kind + * of data, such as a person contact, you set the MIME type to the kind of + * data you want and launch it with {@link Context#startActivity(Intent)}. + * The system will then launch the best application to select that kind + * of data for you. + *

+ * You may also be interested in any of a set of types of content the user + * can pick. For example, an e-mail application that wants to allow the + * user to add an attachment to an e-mail message can use this action to + * bring up a list of all of the types of content the user can attach. + *

+ * In this case, you should wrap the GET_CONTENT intent with a chooser + * (through {@link #createChooser}), which will give the proper interface + * for the user to pick how to send your data and allow you to specify + * a prompt indicating what they are doing. You will usually specify a + * broad MIME type (such as image/* or {@literal *}/*), resulting in a + * broad range of content types the user can select from. + *

+ * When using such a broad GET_CONTENT action, it is often desirable to + * only pick from data that can be represented as a stream. This is + * accomplished by requiring the {@link #CATEGORY_OPENABLE} in the Intent. + *

+ * Callers can optionally specify {@link #EXTRA_LOCAL_ONLY} to request that + * the launched content chooser only returns results representing data that + * is locally available on the device. For example, if this extra is set + * to true then an image picker should not show any pictures that are available + * from a remote server but not already on the local device (thus requiring + * they be downloaded when opened). + *

+ * If the caller can handle multiple returned items (the user performing + * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} + * to indicate this. + *

+ * Input: {@link #getType} is the desired MIME type to retrieve. Note + * that no URI is supplied in the intent, as there are no constraints on + * where the returned data originally comes from. You may also include the + * {@link #CATEGORY_OPENABLE} if you can only accept data that can be + * opened as a stream. You may use {@link #EXTRA_LOCAL_ONLY} to limit content + * selection to local data. You may use {@link #EXTRA_ALLOW_MULTIPLE} to + * allow the user to select multiple items. + *

+ * Output: The URI of the item that was picked. This must be a content: + * URI so that any receiver can access it. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT"; + /** + * Activity Action: Dial a number as specified by the data. This shows a + * UI with the number being dialed, allowing the user to explicitly + * initiate the call. + *

Input: If nothing, an empty dialer is started; else {@link #getData} + * is URI of a phone number to be dialed or a tel: URI of an explicit phone + * number. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_DIAL = "android.intent.action.DIAL"; + /** + * Activity Action: Perform a call to someone specified by the data. + *

Input: If nothing, an empty dialer is started; else {@link #getData} + * is URI of a phone number to be dialed or a tel: URI of an explicit phone + * number. + *

Output: nothing. + * + *

Note: there will be restrictions on which applications can initiate a + * call; most applications should use the {@link #ACTION_DIAL}. + *

Note: this Intent cannot be used to call emergency + * numbers. Applications can dial emergency numbers using + * {@link #ACTION_DIAL}, however. + * + *

Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} + * and above and declares as using the {@link android.Manifest.permission#CALL_PHONE} + * permission which is not granted, then attempting to use this action will + * result in a {@link java.lang.SecurityException}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CALL = "android.intent.action.CALL"; + /** + * Activity Action: Perform a call to an emergency number specified by the + * data. + *

Input: {@link #getData} is URI of a phone number to be dialed or a + * tel: URI of an explicit phone number. + *

Output: nothing. + * @hide + */ + public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY"; + /** + * Activity action: Perform a call to any number (emergency or not) + * specified by the data. + *

Input: {@link #getData} is URI of a phone number to be dialed or a + * tel: URI of an explicit phone number. + *

Output: nothing. + * @hide + */ + public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED"; + /** + * Activity Action: Main entry point for carrier setup apps. + *

Carrier apps that provide an implementation for this action may be invoked to configure + * carrier service and typically require + * {@link android.telephony.TelephonyManager#hasCarrierPrivileges() carrier privileges} to + * fulfill their duties. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP"; + /** + * Activity Action: Send a message to someone specified by the data. + *

Input: {@link #getData} is URI describing the target. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SENDTO = "android.intent.action.SENDTO"; + /** + * Activity Action: Deliver some data to someone else. Who the data is + * being delivered to is not specified; it is up to the receiver of this + * action to ask the user where the data should be sent. + *

+ * When launching a SEND intent, you should usually wrap it in a chooser + * (through {@link #createChooser}), which will give the proper interface + * for the user to pick how to send your data and allow you to specify + * a prompt indicating what they are doing. + *

+ * Input: {@link #getType} is the MIME type of the data being sent. + * get*Extra can have either a {@link #EXTRA_TEXT} + * or {@link #EXTRA_STREAM} field, containing the data to be sent. If + * using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it + * should be the MIME type of the data in EXTRA_STREAM. Use {@literal *}/* + * if the MIME type is unknown (this will only allow senders that can + * handle generic data streams). If using {@link #EXTRA_TEXT}, you can + * also optionally supply {@link #EXTRA_HTML_TEXT} for clients to retrieve + * your text with HTML formatting. + *

+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data + * being sent can be supplied through {@link #setClipData(ClipData)}. This + * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing + * content: URIs and other advanced features of {@link ClipData}. If + * using this approach, you still must supply the same data through the + * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below + * for compatibility with old applications. If you don't set a ClipData, + * it will be copied there for you when calling {@link Context#startActivity(Intent)}. + *

+ * Optional standard extras, which may be interpreted by some recipients as + * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, + * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. + *

+ * Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEND = "android.intent.action.SEND"; + /** + * Activity Action: Deliver multiple data to someone else. + *

+ * Like {@link #ACTION_SEND}, except the data is multiple. + *

+ * Input: {@link #getType} is the MIME type of the data being sent. + * get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link + * #EXTRA_STREAM} field, containing the data to be sent. If using + * {@link #EXTRA_TEXT}, you can also optionally supply {@link #EXTRA_HTML_TEXT} + * for clients to retrieve your text with HTML formatting. + *

+ * Multiple types are supported, and receivers should handle mixed types + * whenever possible. The right way for the receiver to check them is to + * use the content resolver on each URI. The intent sender should try to + * put the most concrete mime type in the intent type, but it can fall + * back to {@literal /*} or {@literal *}/* as needed. + *

+ * e.g. if you are sending image/jpg and image/jpg, the intent's type can + * be image/jpg, but if you are sending image/jpg and image/png, then the + * intent's type should be image/*. + *

+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data + * being sent can be supplied through {@link #setClipData(ClipData)}. This + * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing + * content: URIs and other advanced features of {@link ClipData}. If + * using this approach, you still must supply the same data through the + * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below + * for compatibility with old applications. If you don't set a ClipData, + * it will be copied there for you when calling {@link Context#startActivity(Intent)}. + *

+ * Optional standard extras, which may be interpreted by some recipients as + * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, + * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. + *

+ * Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE"; + /** + * Activity Action: Handle an incoming phone call. + *

Input: nothing. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ANSWER = "android.intent.action.ANSWER"; + /** + * Activity Action: Insert an empty item into the given container. + *

Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*) + * in which to place the data. + *

Output: URI of the new data that was created. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSERT = "android.intent.action.INSERT"; + /** + * Activity Action: Create a new item in the given container, initializing it + * from the current contents of the clipboard. + *

Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*) + * in which to place the data. + *

Output: URI of the new data that was created. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PASTE = "android.intent.action.PASTE"; + /** + * Activity Action: Delete the given data from its container. + *

Input: {@link #getData} is URI of data to be deleted. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_DELETE = "android.intent.action.DELETE"; + /** + * Activity Action: Run the data, whatever that means. + *

Input: ? (Note: this is currently specific to the test harness.) + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_RUN = "android.intent.action.RUN"; + /** + * Activity Action: Perform a data synchronization. + *

Input: ? + *

Output: ? + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SYNC = "android.intent.action.SYNC"; + /** + * Activity Action: Pick an activity given an intent, returning the class + * selected. + *

Input: get*Extra field {@link #EXTRA_INTENT} is an Intent + * used with {@link PackageManager#queryIntentActivities} to determine the + * set of activities from which to pick. + *

Output: Class name of the activity that was selected. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY"; + /** + * Activity Action: Perform a search. + *

Input: {@link android.app.SearchManager#QUERY getStringExtra(SearchManager.QUERY)} + * is the text to search for. If empty, simply + * enter your search results Activity with the search UI activated. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEARCH = "android.intent.action.SEARCH"; + /** + * Activity Action: Start the platform-defined tutorial + *

Input: {@link android.app.SearchManager#QUERY getStringExtra(SearchManager.QUERY)} + * is the text to search for. If empty, simply + * enter your search results Activity with the search UI activated. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SYSTEM_TUTORIAL = "android.intent.action.SYSTEM_TUTORIAL"; + /** + * Activity Action: Perform a web search. + *

+ * Input: {@link android.app.SearchManager#QUERY + * getStringExtra(SearchManager.QUERY)} is the text to search for. If it is + * a url starts with http or https, the site will be opened. If it is plain + * text, Google search will be applied. + *

+ * Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH"; + /** + * Activity Action: Perform assist action. + *

+ * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide + * additional optional contextual information about where the user was when they + * requested the assist; {@link #EXTRA_REFERRER} may be set with additional referrer + * information. + * Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ASSIST = "android.intent.action.ASSIST"; + /** + * Activity Action: Perform voice assist action. + *

+ * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide + * additional optional contextual information about where the user was when they + * requested the voice assist. + * Output: nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; + /** + * An optional field on {@link #ACTION_ASSIST} containing the name of the current foreground + * application package at the time the assist was invoked. + */ + public static final String EXTRA_ASSIST_PACKAGE + = "android.intent.extra.ASSIST_PACKAGE"; + /** + * An optional field on {@link #ACTION_ASSIST} containing the uid of the current foreground + * application package at the time the assist was invoked. + */ + public static final String EXTRA_ASSIST_UID + = "android.intent.extra.ASSIST_UID"; + /** + * An optional field on {@link #ACTION_ASSIST} and containing additional contextual + * information supplied by the current foreground app at the time of the assist request. + * This is a {@link Bundle} of additional data. + */ + public static final String EXTRA_ASSIST_CONTEXT + = "android.intent.extra.ASSIST_CONTEXT"; + /** + * An optional field on {@link #ACTION_ASSIST} suggesting that the user will likely use a + * keyboard as the primary input device for assistance. + */ + public static final String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = + "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD"; + /** + * An optional field on {@link #ACTION_ASSIST} containing the InputDevice id + * that was used to invoke the assist. + */ + public static final String EXTRA_ASSIST_INPUT_DEVICE_ID = + "android.intent.extra.ASSIST_INPUT_DEVICE_ID"; + /** + * Activity Action: List all available applications. + *

Input: Nothing. + *

Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ALL_APPS = "android.intent.action.ALL_APPS"; + /** + * Activity Action: Show settings for choosing wallpaper. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER"; + /** + * Activity Action: Show activity for reporting a bug. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BUG_REPORT = "android.intent.action.BUG_REPORT"; + /** + * Activity Action: Main entry point for factory tests. Only used when + * the device is booting in factory test node. The implementing package + * must be installed in the system image. + *

Input: nothing + *

Output: nothing + */ + public static final String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST"; + /** + * Activity Action: The user pressed the "call" button to go to the dialer + * or other appropriate UI for placing a call. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON"; + /** + * Activity Action: Start Voice Command. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND"; + /** + * Activity Action: Start action associated with long pressing on the + * search key. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; + /** + * Activity Action: The user pressed the "Report" button in the crash/ANR dialog. + * This intent is delivered to the package which installed the application, usually + * Google Play. + *

Input: No data is specified. The bug report is passed in using + * an {@link #EXTRA_BUG_REPORT} field. + *

Output: Nothing. + * + * @see #EXTRA_BUG_REPORT + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR"; + /** + * Activity Action: Show power usage information to the user. + *

Input: Nothing. + *

Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY"; + /** + * Activity Action: Setup wizard to launch after a platform update. This + * activity should have a string meta-data field associated with it, + * {@link #METADATA_SETUP_VERSION}, which defines the current version of + * the platform for setup. The activity will be launched only if + * {@link android.provider.Settings.Secure#LAST_SETUP_SHOWN} is not the + * same value. + *

Input: Nothing. + *

Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; + /** + * Activity Action: Start the Keyboard Shortcuts Helper screen. + *

Input: Nothing. + *

Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS = + "android.intent.action.SHOW_KEYBOARD_SHORTCUTS"; + /** + * Activity Action: Dismiss the Keyboard Shortcuts Helper screen. + *

Input: Nothing. + *

Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DISMISS_KEYBOARD_SHORTCUTS = + "android.intent.action.DISMISS_KEYBOARD_SHORTCUTS"; + /** + * Activity Action: Show settings for managing network data usage of a + * specific application. Applications should define an activity that offers + * options to control data usage. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_NETWORK_USAGE = + "android.intent.action.MANAGE_NETWORK_USAGE"; + /** + * Activity Action: Launch application installer. + *

+ * Input: The data must be a content: or file: URI at which the application + * can be retrieved. As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, + * you can also use "package:" to install an application for the + * current user that is already installed for another user. You can optionally supply + * {@link #EXTRA_INSTALLER_PACKAGE_NAME}, {@link #EXTRA_NOT_UNKNOWN_SOURCE}, + * {@link #EXTRA_ALLOW_REPLACE}, and {@link #EXTRA_RETURN_RESULT}. + *

+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install + * succeeded. + *

+ * Note:If your app is targeting API level higher than 22 you + * need to hold {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} + * in order to launch the application installer. + *

+ * + * @see #EXTRA_INSTALLER_PACKAGE_NAME + * @see #EXTRA_NOT_UNKNOWN_SOURCE + * @see #EXTRA_RETURN_RESULT + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE"; + /** + * Activity Action: Launch ephemeral installer. + *

+ * Input: The data must be a http: URI that the ephemeral application is registered + * to handle. + *

+ * This is a protected intent that can only be sent by the system. + *

+ * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE + = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; + /** + * Service Action: Resolve ephemeral application. + *

+ * The system will have a persistent connection to this service. + * This is a protected intent that can only be sent by the system. + *

+ * + * @hide + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE + = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; + /** + * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a + * package. Specifies the installer package name; this package will receive the + * {@link #ACTION_APP_ERROR} intent. + */ + public static final String EXTRA_INSTALLER_PACKAGE_NAME + = "android.intent.extra.INSTALLER_PACKAGE_NAME"; + /** + * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a + * package. Specifies that the application being installed should not be + * treated as coming from an unknown source, but as coming from the app + * invoking the Intent. For this to work you must start the installer with + * startActivityForResult(). + */ + public static final String EXTRA_NOT_UNKNOWN_SOURCE + = "android.intent.extra.NOT_UNKNOWN_SOURCE"; + /** + * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and + * {@link #ACTION_VIEW} to indicate the URI from which the local APK in the Intent + * data field originated from. + */ + public static final String EXTRA_ORIGINATING_URI + = "android.intent.extra.ORIGINATING_URI"; + /** + * This extra can be used with any Intent used to launch an activity, supplying information + * about who is launching that activity. This field contains a {@link android.net.Uri} + * object, typically an http: or https: URI of the web site that the referral came from; + * it can also use the {@link #URI_ANDROID_APP_SCHEME android-app:} scheme to identify + * a native application that it came from. + * + *

To retrieve this value in a client, use {@link android.app.Activity#getReferrer} + * instead of directly retrieving the extra. It is also valid for applications to + * instead supply {@link #EXTRA_REFERRER_NAME} for cases where they can only create + * a string, not a Uri; the field here, if supplied, will always take precedence, + * however.

+ * + * @see #EXTRA_REFERRER_NAME + */ + public static final String EXTRA_REFERRER + = "android.intent.extra.REFERRER"; + /** + * Alternate version of {@link #EXTRA_REFERRER} that supplies the URI as a String rather + * than a {@link android.net.Uri} object. Only for use in cases where Uri objects can + * not be created, in particular when Intent extras are supplied through the + * {@link #URI_INTENT_SCHEME intent:} or {@link #URI_ANDROID_APP_SCHEME android-app:} + * schemes. + * + * @see #EXTRA_REFERRER + */ + public static final String EXTRA_REFERRER_NAME + = "android.intent.extra.REFERRER_NAME"; + /** + * Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and + * {@link #ACTION_VIEW} to indicate the uid of the package that initiated the install + * @hide + */ + @SystemApi + public static final String EXTRA_ORIGINATING_UID + = "android.intent.extra.ORIGINATING_UID"; + /** + * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a + * package. Tells the installer UI to skip the confirmation with the user + * if the .apk is replacing an existing one. + * @deprecated As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, Android + * will no longer show an interstitial message about updating existing + * applications so this is no longer needed. + */ + @Deprecated + public static final String EXTRA_ALLOW_REPLACE + = "android.intent.extra.ALLOW_REPLACE"; + /** + * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} or + * {@link #ACTION_UNINSTALL_PACKAGE}. Specifies that the installer UI should + * return to the application the result code of the install/uninstall. The returned result + * code will be {@link android.app.Activity#RESULT_OK} on success or + * {@link android.app.Activity#RESULT_FIRST_USER} on failure. + */ + public static final String EXTRA_RETURN_RESULT + = "android.intent.extra.RETURN_RESULT"; + /** + * Package manager install result code. @hide because result codes are not + * yet ready to be exposed. + */ + public static final String EXTRA_INSTALL_RESULT + = "android.intent.extra.INSTALL_RESULT"; + /** + * Activity Action: Launch application uninstaller. + *

+ * Input: The data must be a package: URI whose scheme specific part is + * the package name of the current installed package to be uninstalled. + * You can optionally supply {@link #EXTRA_RETURN_RESULT}. + *

+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install + * succeeded. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE"; + /** + * Specify whether the package should be uninstalled for all users. + * @hide because these should not be part of normal application flow. + */ + public static final String EXTRA_UNINSTALL_ALL_USERS + = "android.intent.extra.UNINSTALL_ALL_USERS"; + /** + * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity + * describing the last run version of the platform that was setup. + * @hide + */ + public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; + /** + * Activity action: Launch UI to manage the permissions of an app. + *

+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose permissions + * will be managed by the launched UI. + *

+ *

+ * Output: Nothing. + *

+ * + * @see #EXTRA_PACKAGE_NAME + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_PERMISSIONS = + "android.intent.action.MANAGE_APP_PERMISSIONS"; + /** + * Activity action: Launch UI to manage permissions. + *

+ * Input: Nothing. + *

+ *

+ * Output: Nothing. + *

+ * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_PERMISSIONS = + "android.intent.action.MANAGE_PERMISSIONS"; + /** + * Activity action: Launch UI to review permissions for an app. + * The system uses this intent if permission review for apps not + * supporting the new runtime permissions model is enabled. In + * this mode a permission review is required before any of the + * app components can run. + *

+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose + * permissions will be reviewed (mandatory). + *

+ *

+ * Input: {@link #EXTRA_INTENT} specifies a pending intent to + * be fired after the permission review (optional). + *

+ *

+ * Input: {@link #EXTRA_REMOTE_CALLBACK} specifies a callback to + * be invoked after the permission review (optional). + *

+ *

+ * Input: {@link #EXTRA_RESULT_NEEDED} specifies whether the intent + * passed via {@link #EXTRA_INTENT} needs a result (optional). + *

+ *

+ * Output: Nothing. + *

+ * + * @see #EXTRA_PACKAGE_NAME + * @see #EXTRA_INTENT + * @see #EXTRA_REMOTE_CALLBACK + * @see #EXTRA_RESULT_NEEDED + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REVIEW_PERMISSIONS = + "android.intent.action.REVIEW_PERMISSIONS"; + /** + * Intent extra: A callback for reporting remote result as a bundle. + *

+ * Type: IRemoteCallback + *

+ * + * @hide + */ + @SystemApi + public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; + /** + * Intent extra: An app package name. + *

+ * Type: String + *

+ * + */ + public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"; + /** + * Intent extra: An extra for specifying whether a result is needed. + *

+ * Type: boolean + *

+ * + * @hide + */ + @SystemApi + public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; + /** + * Activity action: Launch UI to manage which apps have a given permission. + *

+ * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access + * to which will be managed by the launched UI. + *

+ *

+ * Output: Nothing. + *

+ * + * @see #EXTRA_PERMISSION_NAME + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_PERMISSION_APPS = + "android.intent.action.MANAGE_PERMISSION_APPS"; + /** + * Intent extra: The name of a permission. + *

+ * Type: String + *

+ * + * @hide + */ + @SystemApi + public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Standard intent broadcast actions (see action variable). + /** + * Broadcast Action: Sent when the device goes to sleep and becomes non-interactive. + *

+ * For historical reasons, the name of this broadcast action refers to the power + * state of the screen but it is actually sent in response to changes in the + * overall interactive state of the device. + *

+ * This broadcast is sent when the device becomes non-interactive which may have + * nothing to do with the screen turning off. To determine the + * actual state of the screen, use {@link android.view.Display#getState}. + *

+ * See {@link android.os.PowerManager#isInteractive} for details. + *

+ * You cannot receive this through components declared in + * manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF"; + /** + * Broadcast Action: Sent when the device wakes up and becomes interactive. + *

+ * For historical reasons, the name of this broadcast action refers to the power + * state of the screen but it is actually sent in response to changes in the + * overall interactive state of the device. + *

+ * This broadcast is sent when the device becomes interactive which may have + * nothing to do with the screen turning on. To determine the + * actual state of the screen, use {@link android.view.Display#getState}. + *

+ * See {@link android.os.PowerManager#isInteractive} for details. + *

+ * You cannot receive this through components declared in + * manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; + /** + * Broadcast Action: Sent after the system stops dreaming. + * + *

This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED"; + /** + * Broadcast Action: Sent after the system starts dreaming. + * + *

This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED"; + /** + * Broadcast Action: Sent when the user is present after device wakes up (e.g when the + * keyguard is gone). + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT"; + /** + * Broadcast Action: The current time has changed. Sent every + * minute. You cannot receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TIME_TICK = "android.intent.action.TIME_TICK"; + /** + * Broadcast Action: The time was set. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TIME_CHANGED = "android.intent.action.TIME_SET"; + /** + * Broadcast Action: The date has changed. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED"; + /** + * Broadcast Action: The timezone has changed. The intent will have the following extra values:

+ *
    + *
  • time-zone - The java.util.TimeZone.getID() value identifying the new time zone.
  • + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED"; + /** + * Clear DNS Cache Action: This is broadcast when networks have changed and old + * DNS entries should be tossed. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CLEAR_DNS_CACHE = "android.intent.action.CLEAR_DNS_CACHE"; + /** + * Alarm Changed Action: This is broadcast when the AlarmClock + * application's alarm is set or unset. It is used by the + * AlarmClock application and the StatusBar service. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ALARM_CHANGED = "android.intent.action.ALARM_CHANGED"; + /** + * Broadcast Action: This is broadcast once, after the user has finished + * booting, but while still in the "locked" state. It can be used to perform + * application-specific initialization, such as installing alarms. You must + * hold the {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} + * permission in order to receive this broadcast. + *

+ * This broadcast is sent immediately at boot by all devices (regardless of + * direct boot support) running {@link android.os.Build.VERSION_CODES#N} or + * higher. Upon receipt of this broadcast, the user is still locked and only + * device-protected storage can be accessed safely. If you want to access + * credential-protected storage, you need to wait for the user to be + * unlocked (typically by entering their lock pattern or PIN for the first + * time), after which the {@link #ACTION_USER_UNLOCKED} and + * {@link #ACTION_BOOT_COMPLETED} broadcasts are sent. + *

+ * To receive this broadcast, your receiver component must be marked as + * being {@link ComponentInfo#directBootAware}. + *

+ * This is a protected intent that can only be sent by the system. + * + * @see Context#createDeviceProtectedStorageContext() + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED"; + /** + * Broadcast Action: This is broadcast once, after the user has finished + * booting. It can be used to perform application-specific initialization, + * such as installing alarms. You must hold the + * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission in + * order to receive this broadcast. + *

+ * This broadcast is sent at boot by all devices (both with and without + * direct boot support). Upon receipt of this broadcast, the user is + * unlocked and both device-protected and credential-protected storage can + * accessed safely. + *

+ * If you need to run while the user is still locked (before they've entered + * their lock pattern or PIN for the first time), you can listen for the + * {@link #ACTION_LOCKED_BOOT_COMPLETED} broadcast. + *

+ * This is a protected intent that can only be sent by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + /** + * Broadcast Action: This is broadcast when a user action should request a + * temporary system dialog to dismiss. Some examples of temporary system + * dialogs are the notification window-shade and the recent tasks dialog. + */ + public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; + /** + * Broadcast Action: Trigger the download and eventual installation + * of a package. + *

Input: {@link #getData} is the URI of the package file to download. + * + *

This is a protected intent that can only be sent + * by the system. + * + * @deprecated This constant has never been used. + */ + @Deprecated + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL"; + /** + * Broadcast Action: A new application package has been installed on the + * device. The data contains the name of the package. Note that the + * newly installed package does not receive this broadcast. + *

May include the following extras: + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the new package. + *
  • {@link #EXTRA_REPLACING} is set to true if this is following + * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED"; + /** + * Broadcast Action: A new version of an application package has been + * installed, replacing an existing version that was previously installed. + * The data contains the name of the package. + *

May include the following extras: + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the new package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED"; + /** + * Broadcast Action: A new version of your application has been installed + * over an existing one. This is only sent to the application that was + * replaced. It does not contain any additional data; to receive it, just + * use an intent filter for this action. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED"; + /** + * Broadcast Action: An existing application package has been removed from + * the device. The data contains the name of the package. The package + * that is being removed does not receive this Intent. + *

    + *
  • {@link #EXTRA_UID} containing the integer uid previously assigned + * to the package. + *
  • {@link #EXTRA_DATA_REMOVED} is set to true if the entire + * application -- data and code -- is being removed. + *
  • {@link #EXTRA_REPLACING} is set to true if this will be followed + * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED"; + /** + * Broadcast Action: An existing application package has been completely + * removed from the device. The data contains the name of the package. + * This is like {@link #ACTION_PACKAGE_REMOVED}, but only set when + * {@link #EXTRA_DATA_REMOVED} is true and + * {@link #EXTRA_REPLACING} is false of that broadcast. + * + *

    + *
  • {@link #EXTRA_UID} containing the integer uid previously assigned + * to the package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_FULLY_REMOVED + = "android.intent.action.PACKAGE_FULLY_REMOVED"; + /** + * Broadcast Action: An existing application package has been changed (for + * example, a component has been enabled or disabled). The data contains + * the name of the package. + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the package. + *
  • {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name + * of the changed components (or the package name itself). + *
  • {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the + * default action of restarting the application. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED"; + /** + * @hide + * Broadcast Action: Ask system services if there is any reason to + * restart the given package. The data contains the name of the + * package. + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the package. + *
  • {@link #EXTRA_PACKAGES} String array of all packages to check. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; + /** + * Broadcast Action: The user has restarted a package, and all of its + * processes have been killed. All runtime state + * associated with it (processes, alarms, notifications, etc) should + * be removed. Note that the restarted package does not + * receive this broadcast. + * The data contains the name of the package. + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED"; + /** + * Broadcast Action: The user has cleared the data of a package. This should + * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of + * its persistent data is erased and this broadcast sent. + * Note that the cleared package does not + * receive this broadcast. The data contains the name of the package. + *

    + *
  • {@link #EXTRA_UID} containing the integer uid assigned to the package. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED"; + /** + * Broadcast Action: Packages have been suspended. + *

Includes the following extras: + *

    + *
  • {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been suspended + *
+ * + *

This is a protected intent that can only be sent + * by the system. It is only sent to registered receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED"; + /** + * Broadcast Action: Packages have been unsuspended. + *

Includes the following extras: + *

    + *
  • {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been unsuspended + *
+ * + *

This is a protected intent that can only be sent + * by the system. It is only sent to registered receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED"; + /** + * Broadcast Action: A user ID has been removed from the system. The user + * ID number is stored in the extra data under {@link #EXTRA_UID}. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED"; + /** + * Broadcast Action: Sent to the installer package of an application when + * that application is first launched (that is the first time it is moved + * out of the stopped state). The data contains the name of the package. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH"; + /** + * Broadcast Action: Sent to the system package verifier when a package + * needs to be verified. The data contains the package URI. + *

+ * This is a protected intent that can only be sent by the system. + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION"; + /** + * Broadcast Action: Sent to the system package verifier when a package is + * verified. The data contains the package URI. + *

+ * This is a protected intent that can only be sent by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED"; + /** + * Broadcast Action: Sent to the system intent filter verifier when an + * intent filter needs to be verified. The data contains the filter data + * hosts to be verified against. + *

+ * This is a protected intent that can only be sent by the system. + *

+ * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; + /** + * Broadcast Action: Resources for a set of packages (which were + * previously unavailable) are currently + * available since the media on which they exist is available. + * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a + * list of packages whose availability changed. + * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a + * list of uids of packages whose availability changed. + * Note that the + * packages in this list do not receive this broadcast. + * The specified set of packages are now available on the system. + *

Includes the following extras: + *

    + *
  • {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages + * whose resources(were previously unavailable) are currently available. + * {@link #EXTRA_CHANGED_UID_LIST} is the set of uids of the + * packages whose resources(were previously unavailable) + * are currently available. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = + "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE"; + /** + * Broadcast Action: Resources for a set of packages are currently + * unavailable since the media on which they exist is unavailable. + * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a + * list of packages whose availability changed. + * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a + * list of uids of packages whose availability changed. + * The specified set of packages can no longer be + * launched and are practically unavailable on the system. + *

Inclues the following extras: + *

    + *
  • {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages + * whose resources are no longer available. + * {@link #EXTRA_CHANGED_UID_LIST} is the set of packages + * whose resources are no longer available. + *
+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = + "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE"; + /** + * Broadcast Action: preferred activities have changed *explicitly*. + * + *

Note there are cases where a preferred activity is invalidated *implicitly*, e.g. + * when an app is installed or uninstalled, but in such cases this broadcast will *not* + * be sent. + * + * {@link #EXTRA_USER_HANDLE} contains the user ID in question. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PREFERRED_ACTIVITY_CHANGED = + "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED"; + /** + * Broadcast Action: The current system wallpaper has changed. See + * {@link android.app.WallpaperManager} for retrieving the new wallpaper. + * This should only be used to determine when the wallpaper + * has changed to show the new wallpaper to the user. You should certainly + * never, in response to this, change the wallpaper or other attributes of + * it such as the suggested size. That would be crazy, right? You'd cause + * all kinds of loops, especially if other apps are doing similar things, + * right? Of course. So please don't do this. + * + * @deprecated Modern applications should use + * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER + * WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER} to have the wallpaper + * shown behind their UI, rather than watching for this broadcast and + * rendering the wallpaper on their own. + */ + @Deprecated @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED"; + /** + * Broadcast Action: The current device {@link android.content.res.Configuration} + * (orientation, locale, etc) has changed. When such a change happens, the + * UIs (view hierarchy) will need to be rebuilt based on this new + * information; for the most part, applications don't need to worry about + * this, because the system will take care of stopping and restarting the + * application to make sure it sees the new changes. Some system code that + * can not be restarted will need to watch for this action and handle it + * appropriately. + * + *

+ * You cannot receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + *

This is a protected intent that can only be sent + * by the system. + * + * @see android.content.res.Configuration + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; + /** + * Broadcast Action: The current device's locale has changed. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED"; + /** + * Broadcast Action: This is a sticky broadcast containing the + * charging state, level, and other information about the battery. + * See {@link android.os.BatteryManager} for documentation on the + * contents of the Intent. + * + *

+ * You cannot receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. See {@link #ACTION_BATTERY_LOW}, + * {@link #ACTION_BATTERY_OKAY}, {@link #ACTION_POWER_CONNECTED}, + * and {@link #ACTION_POWER_DISCONNECTED} for distinct battery-related + * broadcasts that are sent and can be received through manifest + * receivers. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED"; + /** + * Broadcast Action: Indicates low battery condition on the device. + * This broadcast corresponds to the "Low battery warning" system dialog. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW"; + /** + * Broadcast Action: Indicates the battery is now okay after being low. + * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has + * gone back up to an okay state. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY"; + /** + * Broadcast Action: External power has been connected to the device. + * This is intended for applications that wish to register specifically to this notification. + * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to + * stay active to receive this notification. This action can be used to implement actions + * that wait until power is available to trigger. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED"; + /** + * Broadcast Action: External power has been removed from the device. + * This is intended for applications that wish to register specifically to this notification. + * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to + * stay active to receive this notification. This action can be used to implement actions + * that wait until power is available to trigger. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_DISCONNECTED = + "android.intent.action.ACTION_POWER_DISCONNECTED"; + /** + * Broadcast Action: Device is shutting down. + * This is broadcast when the device is being shut down (completely turned + * off, not sleeping). Once the broadcast is complete, the final shutdown + * will proceed and all unsaved data lost. Apps will not normally need + * to handle this, since the foreground activity will be paused as well. + * + *

This is a protected intent that can only be sent + * by the system. + *

May include the following extras: + *

    + *
  • {@link #EXTRA_SHUTDOWN_USERSPACE_ONLY} a boolean that is set to true if this + * shutdown is only for userspace processes. If not set, assumed to be false. + *
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + /** + * Activity Action: Start this activity to request system shutdown. + * The optional boolean extra field {@link #EXTRA_KEY_CONFIRM} can be set to true + * to request confirmation from the user before shutting down. The optional boolean + * extra field {@link #EXTRA_USER_REQUESTED_SHUTDOWN} can be set to true to + * indicate that the shutdown is requested by the user. + * + *

This is a protected intent that can only be sent + * by the system. + * + * {@hide} + */ + public static final String ACTION_REQUEST_SHUTDOWN = "android.intent.action.ACTION_REQUEST_SHUTDOWN"; + /** + * Broadcast Action: A sticky broadcast that indicates low memory + * condition on the device + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW"; + /** + * Broadcast Action: Indicates low memory condition on the device no longer exists + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK"; + /** + * Broadcast Action: A sticky broadcast that indicates a memory full + * condition on the device. This is intended for activities that want + * to be able to fill the data partition completely, leaving only + * enough free space to prevent system-wide SQLite failures. + * + *

This is a protected intent that can only be sent + * by the system. + * + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL"; + /** + * Broadcast Action: Indicates memory full condition on the device + * no longer exists. + * + *

This is a protected intent that can only be sent + * by the system. + * + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL"; + /** + * Broadcast Action: Indicates low memory condition notification acknowledged by user + * and package management should be started. + * This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW + * notification. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE"; + /** + * Broadcast Action: The device has entered USB Mass Storage mode. + * This is used mainly for the USB Settings panel. + * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified + * when the SD card file system is mounted or unmounted + * @deprecated replaced by android.os.storage.StorageEventListener + */ + @Deprecated + public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED"; + /** + * Broadcast Action: The device has exited USB Mass Storage mode. + * This is used mainly for the USB Settings panel. + * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified + * when the SD card file system is mounted or unmounted + * @deprecated replaced by android.os.storage.StorageEventListener + */ + @Deprecated + public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED"; + /** + * Broadcast Action: External media has been removed. + * The path to the mount point for the removed media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED"; + /** + * Broadcast Action: External media is present, but not mounted at its mount point. + * The path to the mount point for the unmounted media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED"; + /** + * Broadcast Action: External media is present, and being disk-checked + * The path to the mount point for the checking media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING"; + /** + * Broadcast Action: External media is present, but is using an incompatible fs (or is blank) + * The path to the mount point for the checking media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_NOFS = "android.intent.action.MEDIA_NOFS"; + /** + * Broadcast Action: External media is present and mounted at its mount point. + * The path to the mount point for the mounted media is contained in the Intent.mData field. + * The Intent contains an extra with name "read-only" and Boolean value to indicate if the + * media was mounted read only. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_MOUNTED = "android.intent.action.MEDIA_MOUNTED"; + /** + * Broadcast Action: External media is unmounted because it is being shared via USB mass storage. + * The path to the mount point for the shared media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED"; + /** + * Broadcast Action: External media is no longer being shared via USB mass storage. + * The path to the mount point for the previously shared media is contained in the Intent.mData field. + * + * @hide + */ + public static final String ACTION_MEDIA_UNSHARED = "android.intent.action.MEDIA_UNSHARED"; + /** + * Broadcast Action: External media was removed from SD card slot, but mount point was not unmounted. + * The path to the mount point for the removed media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL"; + /** + * Broadcast Action: External media is present but cannot be mounted. + * The path to the mount point for the unmountable media is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE"; + /** + * Broadcast Action: User has expressed the desire to remove the external storage media. + * Applications should close all files they have open within the mount point when they receive this intent. + * The path to the mount point for the media to be ejected is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_EJECT = "android.intent.action.MEDIA_EJECT"; + /** + * Broadcast Action: The media scanner has started scanning a directory. + * The path to the directory being scanned is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED"; + /** + * Broadcast Action: The media scanner has finished scanning a directory. + * The path to the scanned directory is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED"; + /** + * Broadcast Action: Request the media scanner to scan a file and add it to the media database. + * The path to the file is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE"; + /** + * Broadcast Action: The "Media Button" was pressed. Includes a single + * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that + * caused the broadcast. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON"; + /** + * Broadcast Action: The "Camera Button" was pressed. Includes a single + * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that + * caused the broadcast. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON"; + // *** NOTE: @todo(*) The following really should go into a more domain-specific + // location; they are not general-purpose actions. + /** + * Broadcast Action: A GTalk connection has been established. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_GTALK_SERVICE_CONNECTED = + "android.intent.action.GTALK_CONNECTED"; + /** + * Broadcast Action: A GTalk connection has been disconnected. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_GTALK_SERVICE_DISCONNECTED = + "android.intent.action.GTALK_DISCONNECTED"; + /** + * Broadcast Action: An input method has been changed. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_INPUT_METHOD_CHANGED = + "android.intent.action.INPUT_METHOD_CHANGED"; + /** + *

Broadcast Action: The user has switched the phone into or out of Airplane Mode. One or + * more radios have been turned off or on. The intent will have the following extra value:

+ *
    + *
  • state - A boolean value indicating whether Airplane Mode is on. If true, + * then cell radio and possibly other radios such as bluetooth or WiFi may have also been + * turned off
  • + *
+ * + *

This is a protected intent that can only be sent by the system.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE"; + /** + * Broadcast Action: Some content providers have parts of their namespace + * where they publish new events or items that the user may be especially + * interested in. For these things, they may broadcast this action when the + * set of interesting items change. + * + * For example, GmailProvider sends this notification when the set of unread + * mail in the inbox changes. + * + *

The data of the intent identifies which part of which provider + * changed. When queried through the content resolver, the data URI will + * return the data set in question. + * + *

The intent will have the following extra values: + *

    + *
  • count - The number of items in the data set. This is the + * same as the number of items in the cursor returned by querying the + * data URI.
  • + *
+ * + * This intent will be sent at boot (if the count is non-zero) and when the + * data set changes. It is possible for the data set to change without the + * count changing (for example, if a new unread message arrives in the same + * sync operation in which a message is archived). The phone should still + * ring/vibrate/etc as normal in this case. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PROVIDER_CHANGED = + "android.intent.action.PROVIDER_CHANGED"; + /** + * Broadcast Action: Wired Headset plugged in or unplugged. + * + * Same as {@link android.media.AudioManager#ACTION_HEADSET_PLUG}, to be consulted for value + * and documentation. + *

If the minimum SDK version of your application is + * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, it is recommended to refer + * to the AudioManager constant in your receiver registration code instead. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_HEADSET_PLUG = android.media.AudioManager.ACTION_HEADSET_PLUG; + /** + *

Broadcast Action: The user has switched on advanced settings in the settings app:

+ *
    + *
  • state - A boolean value indicating whether the settings is on or off.
  • + *
+ * + *

This is a protected intent that can only be sent + * by the system. + * + * @hide + */ + //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ADVANCED_SETTINGS_CHANGED + = "android.intent.action.ADVANCED_SETTINGS"; + /** + * Broadcast Action: Sent after application restrictions are changed. + * + *

This is a protected intent that can only be sent + * by the system.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED = + "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED"; + /** + * Broadcast Action: An outgoing call is about to be placed. + * + *

The Intent will have the following extra value:

+ *
    + *
  • {@link android.content.Intent#EXTRA_PHONE_NUMBER} - + * the phone number originally intended to be dialed.
  • + *
+ *

Once the broadcast is finished, the resultData is used as the actual + * number to call. If null, no call will be placed.

+ *

It is perfectly acceptable for multiple receivers to process the + * outgoing call in turn: for example, a parental control application + * might verify that the user is authorized to place the call at that + * time, then a number-rewriting application might add an area code if + * one was not specified.

+ *

For consistency, any receiver whose purpose is to prohibit phone + * calls should have a priority of 0, to ensure it will see the final + * phone number to be dialed. + * Any receiver whose purpose is to rewrite phone numbers to be called + * should have a positive priority. + * Negative priorities are reserved for the system for this broadcast; + * using them may cause problems.

+ *

Any BroadcastReceiver receiving this Intent must not + * abort the broadcast.

+ *

Emergency calls cannot be intercepted using this mechanism, and + * other calls cannot be modified to call emergency numbers using this + * mechanism. + *

Some apps (such as VoIP apps) may want to redirect the outgoing + * call to use their own service instead. Those apps should first prevent + * the call from being placed by setting resultData to null + * and then start their own app to make the call. + *

You must hold the + * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS} + * permission to receive this Intent.

+ * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NEW_OUTGOING_CALL = + "android.intent.action.NEW_OUTGOING_CALL"; + /** + * Broadcast Action: Have the device reboot. This is only for use by + * system code. + * + *

This is a protected intent that can only be sent + * by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_REBOOT = + "android.intent.action.REBOOT"; + /** + * Broadcast Action: A sticky broadcast for changes in the physical + * docking state of the device. + * + *

The intent will have the following extra values: + *

    + *
  • {@link #EXTRA_DOCK_STATE} - the current dock + * state, indicating which dock the device is physically in.
  • + *
+ *

This is intended for monitoring the current physical dock state. + * See {@link android.app.UiModeManager} for the normal API dealing with + * dock mode changes. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DOCK_EVENT = + "android.intent.action.DOCK_EVENT"; + /** + * Broadcast Action: A broadcast when idle maintenance can be started. + * This means that the user is not interacting with the device and is + * not expected to do so soon. Typical use of the idle maintenance is + * to perform somehow expensive tasks that can be postponed at a moment + * when they will not degrade user experience. + *

+ *

In order to keep the device responsive in case of an + * unexpected user interaction, implementations of a maintenance task + * should be interruptible. In such a scenario a broadcast with action + * {@link #ACTION_IDLE_MAINTENANCE_END} will be sent. In other words, you + * should not do the maintenance work in + * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather start a + * maintenance service by {@link Context#startService(Intent)}. Also + * you should hold a wake lock while your maintenance service is running + * to prevent the device going to sleep. + *

+ *

+ *

This is a protected intent that can only be sent by + * the system. + *

+ * + * @see #ACTION_IDLE_MAINTENANCE_END + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_IDLE_MAINTENANCE_START = + "android.intent.action.ACTION_IDLE_MAINTENANCE_START"; + /** + * Broadcast Action: A broadcast when idle maintenance should be stopped. + * This means that the user was not interacting with the device as a result + * of which a broadcast with action {@link #ACTION_IDLE_MAINTENANCE_START} + * was sent and now the user started interacting with the device. Typical + * use of the idle maintenance is to perform somehow expensive tasks that + * can be postponed at a moment when they will not degrade user experience. + *

+ *

In order to keep the device responsive in case of an + * unexpected user interaction, implementations of a maintenance task + * should be interruptible. Hence, on receiving a broadcast with this + * action, the maintenance task should be interrupted as soon as possible. + * In other words, you should not do the maintenance work in + * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather stop the + * maintenance service that was started on receiving of + * {@link #ACTION_IDLE_MAINTENANCE_START}.Also you should release the wake + * lock you acquired when your maintenance service started. + *

+ *

This is a protected intent that can only be sent + * by the system. + * + * @see #ACTION_IDLE_MAINTENANCE_START + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_IDLE_MAINTENANCE_END = + "android.intent.action.ACTION_IDLE_MAINTENANCE_END"; + /** + * Broadcast Action: a remote intent is to be broadcasted. + * + * A remote intent is used for remote RPC between devices. The remote intent + * is serialized and sent from one device to another device. The receiving + * device parses the remote intent and broadcasts it. Note that anyone can + * broadcast a remote intent. However, if the intent receiver of the remote intent + * does not trust intent broadcasts from arbitrary intent senders, it should require + * the sender to hold certain permissions so only trusted sender's broadcast will be + * let through. + * @hide + */ + public static final String ACTION_REMOTE_INTENT = + "com.google.android.c2dm.intent.RECEIVE"; + /** + * Broadcast Action: This is broadcast once when the user is booting after a + * system update. It can be used to perform cleanup or upgrades after a + * system update. + *

+ * This broadcast is sent after the {@link #ACTION_LOCKED_BOOT_COMPLETED} + * broadcast but before the {@link #ACTION_BOOT_COMPLETED} broadcast. It's + * only sent when the {@link Build#FINGERPRINT} has changed, and it's only + * sent to receivers in the system image. + * + * @hide + */ + public static final String ACTION_PRE_BOOT_COMPLETED = + "android.intent.action.PRE_BOOT_COMPLETED"; + /** + * Broadcast to a specific application to query any supported restrictions to impose + * on restricted users. The broadcast intent contains an extra + * {@link #EXTRA_RESTRICTIONS_BUNDLE} with the currently persisted + * restrictions as a Bundle of key/value pairs. The value types can be Boolean, String or + * String[] depending on the restriction type.

+ * The response should contain an extra {@link #EXTRA_RESTRICTIONS_LIST}, + * which is of type ArrayList<RestrictionEntry>. It can also + * contain an extra {@link #EXTRA_RESTRICTIONS_INTENT}, which is of type Intent. + * The activity specified by that intent will be launched for a result which must contain + * one of the extras {@link #EXTRA_RESTRICTIONS_LIST} or {@link #EXTRA_RESTRICTIONS_BUNDLE}. + * The keys and values of the returned restrictions will be persisted. + * @see RestrictionEntry + */ + public static final String ACTION_GET_RESTRICTION_ENTRIES = + "android.intent.action.GET_RESTRICTION_ENTRIES"; + /** + * Sent the first time a user is starting, to allow system apps to + * perform one time initialization. (This will not be seen by third + * party applications because a newly initialized user does not have any + * third party applications installed for it.) This is sent early in + * starting the user, around the time the home app is started, before + * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_INITIALIZE = + "android.intent.action.USER_INITIALIZE"; + /** + * Sent when a user switch is happening, causing the process's user to be + * brought to the foreground. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * foreground. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_FOREGROUND = + "android.intent.action.USER_FOREGROUND"; + /** + * Sent when a user switch is happening, causing the process's user to be + * sent to the background. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * background. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_BACKGROUND = + "android.intent.action.USER_BACKGROUND"; + /** + * Broadcast sent to the system when a user is added. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the new user. It is sent to + * all running users. You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. + * @hide + */ + public static final String ACTION_USER_ADDED = + "android.intent.action.USER_ADDED"; + /** + * Broadcast sent by the system when a user is started. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the user. This is only sent to + * registered receivers, not manifest receivers. It is sent to the user + * that has been started. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + * @hide + */ + public static final String ACTION_USER_STARTED = + "android.intent.action.USER_STARTED"; + /** + * Broadcast sent when a user is in the process of starting. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the user. This is only + * sent to registered receivers, not manifest receivers. It is sent to all + * users (including the one that is being started). You must hold + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive + * this broadcast. This is sent as a background broadcast, since + * its result is not part of the primary UX flow; to safely keep track of + * started/stopped state of a user you can use this in conjunction with + * {@link #ACTION_USER_STOPPING}. It is not generally safe to use with + * other user state broadcasts since those are foreground broadcasts so can + * execute in a different order. + * @hide + */ + public static final String ACTION_USER_STARTING = + "android.intent.action.USER_STARTING"; + /** + * Broadcast sent when a user is going to be stopped. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the user. This is only + * sent to registered receivers, not manifest receivers. It is sent to all + * users (including the one that is being stopped). You must hold + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive + * this broadcast. The user will not stop until all receivers have + * handled the broadcast. This is sent as a background broadcast, since + * its result is not part of the primary UX flow; to safely keep track of + * started/stopped state of a user you can use this in conjunction with + * {@link #ACTION_USER_STARTING}. It is not generally safe to use with + * other user state broadcasts since those are foreground broadcasts so can + * execute in a different order. + * @hide + */ + public static final String ACTION_USER_STOPPING = + "android.intent.action.USER_STOPPING"; + /** + * Broadcast sent to the system when a user is stopped. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the user. This is similar to + * {@link #ACTION_PACKAGE_RESTARTED}, but for an entire user instead of a + * specific package. This is only sent to registered receivers, not manifest + * receivers. It is sent to all running users except the one that + * has just been stopped (which is no longer running). + * @hide + */ + public static final String ACTION_USER_STOPPED = + "android.intent.action.USER_STOPPED"; + /** + * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. It is sent to all running users except the + * one that has been removed. The user will not be completely removed until all receivers have + * handled the broadcast. You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. + * @hide + */ + public static final String ACTION_USER_REMOVED = + "android.intent.action.USER_REMOVED"; + /** + * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user to become the current one. This is only sent to + * registered receivers, not manifest receivers. It is sent to all running users. + * You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. + * @hide + */ + public static final String ACTION_USER_SWITCHED = + "android.intent.action.USER_SWITCHED"; + /** + * Broadcast Action: Sent when the credential-encrypted private storage has + * become unlocked for the target user. This is only sent to registered + * receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED"; + /** + * Broadcast sent to the system when a user's information changes. Carries an extra + * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed. + * This is only sent to registered receivers, not manifest receivers. It is sent to all users. + * @hide + */ + public static final String ACTION_USER_INFO_CHANGED = + "android.intent.action.USER_INFO_CHANGED"; + /** + * Broadcast sent to the primary user when an associated managed profile is added (the profile + * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies + * the UserHandle of the profile that was added. Only applications (for example Launchers) + * that need to display merged content across both primary and managed profiles need to + * worry about this broadcast. This is only sent to registered receivers, + * not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_ADDED = + "android.intent.action.MANAGED_PROFILE_ADDED"; + /** + * Broadcast sent to the primary user when an associated managed profile is removed. Carries an + * extra {@link #EXTRA_USER} that specifies the UserHandle of the profile that was removed. + * Only applications (for example Launchers) that need to display merged content across both + * primary and managed profiles need to worry about this broadcast. This is only sent to + * registered receivers, not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_REMOVED = + "android.intent.action.MANAGED_PROFILE_REMOVED"; + /** + * Broadcast sent to the primary user when the credential-encrypted private storage for + * an associated managed profile is unlocked. Carries an extra {@link #EXTRA_USER} that + * specifies the UserHandle of the profile that was unlocked. Only applications (for example + * Launchers) that need to display merged content across both primary and managed profiles + * need to worry about this broadcast. This is only sent to registered receivers, + * not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_UNLOCKED = + "android.intent.action.MANAGED_PROFILE_UNLOCKED"; + /** + * Broadcast sent to the primary user when an associated managed profile has become available. + * Currently this includes when the user disables quiet mode for the profile. Carries an extra + * {@link #EXTRA_USER} that specifies the UserHandle of the profile. When quiet mode is changed, + * this broadcast will carry a boolean extra {@link #EXTRA_QUIET_MODE} indicating the new state + * of quiet mode. This is only sent to registered receivers, not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_AVAILABLE = + "android.intent.action.MANAGED_PROFILE_AVAILABLE"; + /** + * Broadcast sent to the primary user when an associated managed profile has become unavailable. + * Currently this includes when the user enables quiet mode for the profile. Carries an extra + * {@link #EXTRA_USER} that specifies the UserHandle of the profile. When quiet mode is changed, + * this broadcast will carry a boolean extra {@link #EXTRA_QUIET_MODE} indicating the new state + * of quiet mode. This is only sent to registered receivers, not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_UNAVAILABLE = + "android.intent.action.MANAGED_PROFILE_UNAVAILABLE"; + /** + * Sent when the user taps on the clock widget in the system's "quick settings" area. + */ + public static final String ACTION_QUICK_CLOCK = + "android.intent.action.QUICK_CLOCK"; + /** + * Activity Action: Shows the brightness setting dialog. + * @hide + */ + public static final String ACTION_SHOW_BRIGHTNESS_DIALOG = + "android.intent.action.SHOW_BRIGHTNESS_DIALOG"; + /** + * Broadcast Action: A global button was pressed. Includes a single + * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that + * caused the broadcast. + * @hide + */ + public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + /** + * Broadcast Action: Sent when media resource is granted. + *

+ * {@link #EXTRA_PACKAGES} specifies the packages on the process holding the media resource + * granted. + *

+ *

+ * This is a protected intent that can only be sent by the system. + *

+ *

+ * This requires {@link android.Manifest.permission#RECEIVE_MEDIA_RESOURCE_USAGE} permission. + *

+ * + * @hide + */ + public static final String ACTION_MEDIA_RESOURCE_GRANTED = + "android.intent.action.MEDIA_RESOURCE_GRANTED"; + /** + * Activity Action: Allow the user to select and return one or more existing + * documents. When invoked, the system will display the various + * {@link DocumentsProvider} instances installed on the device, letting the + * user interactively navigate through them. These documents include local + * media, such as photos and video, and documents provided by installed + * cloud storage providers. + *

+ * Each document is represented as a {@code content://} URI backed by a + * {@link DocumentsProvider}, which can be opened as a stream with + * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for + * {@link android.provider.DocumentsContract.Document} metadata. + *

+ * All selected documents are returned to the calling application with + * persistable read and write permission grants. If you want to maintain + * access to the documents across device reboots, you need to explicitly + * take the persistable permissions using + * {@link ContentResolver#takePersistableUriPermission(Uri, int)}. + *

+ * Callers must indicate the acceptable document MIME types through + * {@link #setType(String)}. For example, to select photos, use + * {@code image/*}. If multiple disjoint MIME types are acceptable, define + * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to + * {@literal *}/*. + *

+ * If the caller can handle multiple returned items (the user performing + * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE} + * to indicate this. + *

+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain + * URIs that can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + *

+ * Output: The URI of the item that was picked, returned in + * {@link #getData()}. This must be a {@code content://} URI so that any + * receiver can access it. If multiple documents were selected, they are + * returned in {@link #getClipData()}. + * + * @see DocumentsContract + * @see #ACTION_OPEN_DOCUMENT_TREE + * @see #ACTION_CREATE_DOCUMENT + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"; + /** + * Activity Action: Allow the user to create a new document. When invoked, + * the system will display the various {@link DocumentsProvider} instances + * installed on the device, letting the user navigate through them. The + * returned document may be a newly created document with no content, or it + * may be an existing document with the requested MIME type. + *

+ * Each document is represented as a {@code content://} URI backed by a + * {@link DocumentsProvider}, which can be opened as a stream with + * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for + * {@link android.provider.DocumentsContract.Document} metadata. + *

+ * Callers must indicate the concrete MIME type of the document being + * created by setting {@link #setType(String)}. This MIME type cannot be + * changed after the document is created. + *

+ * Callers can provide an initial display name through {@link #EXTRA_TITLE}, + * but the user may change this value before creating the file. + *

+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain + * URIs that can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + *

+ * Output: The URI of the item that was created. This must be a + * {@code content://} URI so that any receiver can access it. + * + * @see DocumentsContract + * @see #ACTION_OPEN_DOCUMENT + * @see #ACTION_OPEN_DOCUMENT_TREE + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + /** + * Activity Action: Allow the user to pick a directory subtree. When + * invoked, the system will display the various {@link DocumentsProvider} + * instances installed on the device, letting the user navigate through + * them. Apps can fully manage documents within the returned directory. + *

+ * To gain access to descendant (child, grandchild, etc) documents, use + * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and + * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} + * with the returned URI. + *

+ * Output: The URI representing the selected directory tree. + * + * @see DocumentsContract + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String + ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE"; + /** + * Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or + * exisiting sensor being disconnected. + * + *

This is a protected intent that can only be sent by the system.

+ * + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String + ACTION_DYNAMIC_SENSOR_CHANGED = "android.intent.action.DYNAMIC_SENSOR_CHANGED"; + /** {@hide} */ + public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; + /** + * Boolean intent extra to be used with {@link ACTION_MASTER_CLEAR} in order to force a factory + * reset even if {@link android.os.UserManager.DISALLOW_FACTORY_RESET} is set. + * @hide + */ + public static final String EXTRA_FORCE_MASTER_CLEAR = + "android.intent.extra.FORCE_MASTER_CLEAR"; + /** + * Broadcast action: report that a settings element is being restored from backup. The intent + * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting, + * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE + * is the value of that settings entry prior to the restore operation. All of these values are + * represented as strings. + * + *

This broadcast is sent only for settings provider entries known to require special handling + * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within + * the provider's backup agent implementation. + * + * @see #EXTRA_SETTING_NAME + * @see #EXTRA_SETTING_PREVIOUS_VALUE + * @see #EXTRA_SETTING_NEW_VALUE + * {@hide} + */ + public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED"; + /** {@hide} */ + public static final String EXTRA_SETTING_NAME = "setting_name"; + /** {@hide} */ + public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value"; + /** {@hide} */ + public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; + /** + * Activity Action: Process a piece of text. + *

Input: {@link #EXTRA_PROCESS_TEXT} contains the text to be processed. + * {@link #EXTRA_PROCESS_TEXT_READONLY} states if the resulting text will be read-only.

+ *

Output: {@link #EXTRA_PROCESS_TEXT} contains the processed text.

+ */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT"; + /** + * The name of the extra used to define the text to be processed, as a + * CharSequence. Note that this may be a styled CharSequence, so you must use + * {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to retrieve it. + */ + public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; + /** + * The name of the boolean extra used to define if the processed text will be used as read-only. + */ + public static final String EXTRA_PROCESS_TEXT_READONLY = + "android.intent.extra.PROCESS_TEXT_READONLY"; + /** + * Broadcast action: reports when a new thermal event has been reached. When the device + * is reaching its maximum temperatue, the thermal level reported + * {@hide} + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_THERMAL_EVENT = "android.intent.action.THERMAL_EVENT"; + /** {@hide} */ + public static final String EXTRA_THERMAL_STATE = "android.intent.extra.THERMAL_STATE"; + /** + * Thermal state when the device is normal. This state is sent in the + * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. + * {@hide} + */ + public static final int EXTRA_THERMAL_STATE_NORMAL = 0; + /** + * Thermal state where the device is approaching its maximum threshold. This state is sent in + * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. + * {@hide} + */ + public static final int EXTRA_THERMAL_STATE_WARNING = 1; + /** + * Thermal state where the device has reached its maximum threshold. This state is sent in the + * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. + * {@hide} + */ + public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Standard intent categories (see addCategory()). + /** + * Set if the activity should be an option for the default action + * (center press) to perform on a piece of data. Setting this will + * hide from the user any activities without it set when performing an + * action on some data. Note that this is normally -not- set in the + * Intent when initiating an action -- it is for use in intent filters + * specified in packages. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_DEFAULT = "android.intent.category.DEFAULT"; + /** + * Activities that can be safely invoked from a browser must support this + * category. For example, if the user is viewing a web page or an e-mail + * and clicks on a link in the text, the Intent generated execute that + * link will require the BROWSABLE category, so that only activities + * supporting this category will be considered as possible actions. By + * supporting this category, you are promising that there is nothing + * damaging (without user intervention) that can happen by invoking any + * matching Intent. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE"; + /** + * Categories for activities that can participate in voice interaction. + * An activity that supports this category must be prepared to run with + * no UI shown at all (though in some case it may have a UI shown), and + * rely on {@link android.app.VoiceInteractor} to interact with the user. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_VOICE = "android.intent.category.VOICE"; + /** + * Set if the activity should be considered as an alternative action to + * the data the user is currently viewing. See also + * {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that + * applies to the selection in a list of items. + * + *

Supporting this category means that you would like your activity to be + * displayed in the set of alternative things the user can do, usually as + * part of the current activity's options menu. You will usually want to + * include a specific label in the <intent-filter> of this action + * describing to the user what it does. + * + *

The action of IntentFilter with this category is important in that it + * describes the specific action the target will perform. This generally + * should not be a generic action (such as {@link #ACTION_VIEW}, but rather + * a specific name such as "com.android.camera.action.CROP. Only one + * alternative of any particular action will be shown to the user, so using + * a specific action like this makes sure that your alternative will be + * displayed while also allowing other applications to provide their own + * overrides of that particular action. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE"; + /** + * Set if the activity should be considered as an alternative selection + * action to the data the user has currently selected. This is like + * {@link #CATEGORY_ALTERNATIVE}, but is used in activities showing a list + * of items from which the user can select, giving them alternatives to the + * default action that will be performed on it. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE"; + /** + * Intended to be used as a tab inside of a containing TabActivity. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_TAB = "android.intent.category.TAB"; + /** + * Should be displayed in the top-level launcher. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; + /** + * Indicates an activity optimized for Leanback mode, and that should + * be displayed in the Leanback launcher. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER"; + /** + * Indicates a Leanback settings activity to be displayed in the Leanback launcher. + * @hide + */ + @SystemApi + public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; + /** + * Provides information about the package it is in; typically used if + * a package does not contain a {@link #CATEGORY_LAUNCHER} to provide + * a front-door to the user without having to be shown in the all apps list. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_INFO = "android.intent.category.INFO"; + /** + * This is the home activity, that is the first activity that is displayed + * when the device boots. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_HOME = "android.intent.category.HOME"; + /** + * This is the home activity that is displayed when the device is finished setting up and ready + * for use. + * @hide + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN"; + /** + * This is the setup wizard activity, that is the first activity that is displayed + * when the user sets up the device for the first time. + * @hide + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SETUP_WIZARD = "android.intent.category.SETUP_WIZARD"; + /** + * This activity is a preference panel. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_PREFERENCE = "android.intent.category.PREFERENCE"; + /** + * This activity is a development preference panel. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_DEVELOPMENT_PREFERENCE = "android.intent.category.DEVELOPMENT_PREFERENCE"; + /** + * Capable of running inside a parent activity container. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EMBED = "android.intent.category.EMBED"; + /** + * This activity allows the user to browse and download new applications. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_MARKET = "android.intent.category.APP_MARKET"; + /** + * This activity may be exercised by the monkey or other automated test tools. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_MONKEY = "android.intent.category.MONKEY"; + /** + * To be used as a test (not part of the normal user experience). + */ + public static final String CATEGORY_TEST = "android.intent.category.TEST"; + /** + * To be used as a unit test (run through the Test Harness). + */ + public static final String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST"; + /** + * To be used as a sample code example (not part of the normal user + * experience). + */ + public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE"; + /** + * Used to indicate that an intent only wants URIs that can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. Openable URIs + * must support at least the columns defined in {@link OpenableColumns} when + * queried. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + * @see #ACTION_CREATE_DOCUMENT + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE"; + /** + * To be used as code under test for framework instrumentation tests. + */ + public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST = + "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"; + /** + * An activity to run when device is inserted into a car dock. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK"; + /** + * An activity to run when device is inserted into a car dock. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK"; + /** + * An activity to run when device is inserted into a analog (low end) dock. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_LE_DESK_DOCK = "android.intent.category.LE_DESK_DOCK"; + /** + * An activity to run when device is inserted into a digital (high end) dock. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_HE_DESK_DOCK = "android.intent.category.HE_DESK_DOCK"; + /** + * Used to indicate that the activity can be used in a car environment. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE"; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Application launch intent categories (see addCategory()). + /** + * Used with {@link #ACTION_MAIN} to launch the browser application. + * The activity should be able to browse the Internet. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER"; + /** + * Used with {@link #ACTION_MAIN} to launch the calculator application. + * The activity should be able to perform standard arithmetic operations. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_CALCULATOR = "android.intent.category.APP_CALCULATOR"; + /** + * Used with {@link #ACTION_MAIN} to launch the calendar application. + * The activity should be able to view and manipulate calendar entries. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_CALENDAR = "android.intent.category.APP_CALENDAR"; + /** + * Used with {@link #ACTION_MAIN} to launch the contacts application. + * The activity should be able to view and manipulate address book entries. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_CONTACTS = "android.intent.category.APP_CONTACTS"; + /** + * Used with {@link #ACTION_MAIN} to launch the email application. + * The activity should be able to send and receive email. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_EMAIL = "android.intent.category.APP_EMAIL"; + /** + * Used with {@link #ACTION_MAIN} to launch the gallery application. + * The activity should be able to view and manipulate image and video files + * stored on the device. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_GALLERY = "android.intent.category.APP_GALLERY"; + /** + * Used with {@link #ACTION_MAIN} to launch the maps application. + * The activity should be able to show the user's current location and surroundings. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_MAPS = "android.intent.category.APP_MAPS"; + /** + * Used with {@link #ACTION_MAIN} to launch the messaging application. + * The activity should be able to send and receive text messages. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_MESSAGING = "android.intent.category.APP_MESSAGING"; + /** + * Used with {@link #ACTION_MAIN} to launch the music application. + * The activity should be able to play, browse, or manipulate music files + * stored on the device. + *

NOTE: This should not be used as the primary key of an Intent, + * since it will not result in the app launching with the correct + * action and category. Instead, use this with + * {@link #makeMainSelectorActivity(String, String)} to generate a main + * Intent with this category in the selector.

+ */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC"; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Standard extra data keys. + /** + * The initial data to place in a newly created record. Use with + * {@link #ACTION_INSERT}. The data here is a Map containing the same + * fields as would be given to the underlying ContentProvider.insert() + * call. + */ + public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE"; + /** + * A constant CharSequence that is associated with the Intent, used with + * {@link #ACTION_SEND} to supply the literal data to be sent. Note that + * this may be a styled CharSequence, so you must use + * {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to + * retrieve it. + */ + public static final String EXTRA_TEXT = "android.intent.extra.TEXT"; + /** + * A constant String that is associated with the Intent, used with + * {@link #ACTION_SEND} to supply an alternative to {@link #EXTRA_TEXT} + * as HTML formatted text. Note that you must also supply + * {@link #EXTRA_TEXT}. + */ + public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT"; + /** + * A content: URI holding a stream of data associated with the Intent, + * used with {@link #ACTION_SEND} to supply the data being sent. + */ + public static final String EXTRA_STREAM = "android.intent.extra.STREAM"; + /** + * A String[] holding e-mail addresses that should be delivered to. + */ + public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL"; + /** + * A String[] holding e-mail addresses that should be carbon copied. + */ + public static final String EXTRA_CC = "android.intent.extra.CC"; + /** + * A String[] holding e-mail addresses that should be blind carbon copied. + */ + public static final String EXTRA_BCC = "android.intent.extra.BCC"; + /** + * A constant string holding the desired subject line of a message. + */ + public static final String EXTRA_SUBJECT = "android.intent.extra.SUBJECT"; + /** + * An Intent describing the choices you would like shown with + * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}. + */ + public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; + /** + * An int representing the user id to be used. + * + * @hide + */ + public static final String EXTRA_USER_ID = "android.intent.extra.USER_ID"; + /** + * An int representing the task id to be retrieved. This is used when a launch from recents is + * intercepted by another action such as credentials confirmation to remember which task should + * be resumed when complete. + * + * @hide + */ + public static final String EXTRA_TASK_ID = "android.intent.extra.TASK_ID"; + /** + * An Intent[] describing additional, alternate choices you would like shown with + * {@link #ACTION_CHOOSER}. + * + *

An app may be capable of providing several different payload types to complete a + * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos + * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer + * several different supported sending mechanisms for sharing, such as the actual "image/*" + * photo data or a hosted link where the photos can be viewed.

+ * + *

The intent present in {@link #EXTRA_INTENT} will be treated as the + * first/primary/preferred intent in the set. Additional intents specified in + * this extra are ordered; by default intents that appear earlier in the array will be + * preferred over intents that appear later in the array as matches for the same + * target component. To alter this preference, a calling app may also supply + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.

+ */ + public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; + /** + * A {@link ComponentName ComponentName[]} describing components that should be filtered out + * and omitted from a list of components presented to the user. + * + *

When used with {@link #ACTION_CHOOSER}, the chooser will omit any of the components + * in this array if it otherwise would have shown them. Useful for omitting specific targets + * from your own package or other apps from your organization if the idea of sending to those + * targets would be redundant with other app functionality. Filtered components will not + * be able to present targets from an associated ChooserTargetService.

+ */ + public static final String EXTRA_EXCLUDE_COMPONENTS + = "android.intent.extra.EXCLUDE_COMPONENTS"; + /** + * A {@link android.service.chooser.ChooserTarget ChooserTarget[]} for {@link #ACTION_CHOOSER} + * describing additional high-priority deep-link targets for the chooser to present to the user. + * + *

Targets provided in this way will be presented inline with all other targets provided + * by services from other apps. They will be prioritized before other service targets, but + * after those targets provided by sources that the user has manually pinned to the front.

+ * + * @see #ACTION_CHOOSER + */ + public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; + /** + * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection + * from the chooser activity presented by {@link #ACTION_CHOOSER}. + * + *

An app preparing an action for another app to complete may wish to allow the user to + * disambiguate between several options for completing the action based on the chosen target + * or otherwise refine the action before it is invoked. + *

+ * + *

When sent, this IntentSender may be filled in with the following extras:

+ *
    + *
  • {@link #EXTRA_INTENT} The first intent that matched the user's chosen target
  • + *
  • {@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's + * chosen target beyond the first
  • + *
  • {@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity + * should fill in and send once the disambiguation is complete
  • + *
+ */ + public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER + = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + /** + * A {@link ResultReceiver} used to return data back to the sender. + * + *

Used to complete an app-specific + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.

+ * + *

If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent + * used to start a {@link #ACTION_CHOOSER} activity this extra will be + * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent + * when the user selects a target component from the chooser. It is up to the recipient + * to send a result to this ResultReceiver to signal that disambiguation is complete + * and that the chooser should invoke the user's choice.

+ * + *

The disambiguator should provide a Bundle to the ResultReceiver with an intent + * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser + * to match and fill in the final Intent or ChooserTarget before starting it. + * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from + * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.

+ * + *

The result code passed to the ResultReceiver should be + * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's + * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if + * the chooser should finish without starting a target.

+ */ + public static final String EXTRA_RESULT_RECEIVER + = "android.intent.extra.RESULT_RECEIVER"; + /** + * A CharSequence dialog title to provide to the user when used with a + * {@link #ACTION_CHOOSER}. + */ + public static final String EXTRA_TITLE = "android.intent.extra.TITLE"; + /** + * A Parcelable[] of {@link Intent} or + * {@link android.content.pm.LabeledIntent} objects as set with + * {@link #putExtra(String, Parcelable[])} of additional activities to place + * a the front of the list of choices, when shown to the user with a + * {@link #ACTION_CHOOSER}. + */ + public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; + /** + * A {@link IntentSender} to start after ephemeral installation success. + * @hide + */ + public static final String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS"; + /** + * A {@link IntentSender} to start after ephemeral installation failure. + * @hide + */ + public static final String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE"; + /** + * A Bundle forming a mapping of potential target package names to different extras Bundles + * to add to the default intent extras in {@link #EXTRA_INTENT} when used with + * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not + * be currently installed on the device. + * + *

An application may choose to provide alternate extras for the case where a user + * selects an activity from a predetermined set of target packages. If the activity + * the user selects from the chooser belongs to a package with its package name as + * a key in this bundle, the corresponding extras for that package will be merged with + * the extras already present in the intent at {@link #EXTRA_INTENT}. If a replacement + * extra has the same key as an extra already present in the intent it will overwrite + * the extra from the intent.

+ * + *

Examples: + *

    + *
  • An application may offer different {@link #EXTRA_TEXT} to an application + * when sharing with it via {@link #ACTION_SEND}, augmenting a link with additional query + * parameters for that target.
  • + *
  • An application may offer additional metadata for known targets of a given intent + * to pass along information only relevant to that target such as account or content + * identifiers already known to that application.
  • + *

+ */ + public static final String EXTRA_REPLACEMENT_EXTRAS = + "android.intent.extra.REPLACEMENT_EXTRAS"; + /** + * An {@link IntentSender} that will be notified if a user successfully chooses a target + * component to handle an action in an {@link #ACTION_CHOOSER} activity. The IntentSender + * will have the extra {@link #EXTRA_CHOSEN_COMPONENT} appended to it containing the + * {@link ComponentName} of the chosen component. + * + *

In some situations this callback may never come, for example if the user abandons + * the chooser, switches to another task or any number of other reasons. Apps should not + * be written assuming that this callback will always occur.

+ */ + public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = + "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; + /** + * The {@link ComponentName} chosen by the user to complete an action. + * + * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER + */ + public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; + /** + * A {@link android.view.KeyEvent} object containing the event that + * triggered the creation of the Intent it is in. + */ + public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT"; + /** + * Set to true in {@link #ACTION_REQUEST_SHUTDOWN} to request confirmation from the user + * before shutting down. + * + * {@hide} + */ + public static final String EXTRA_KEY_CONFIRM = "android.intent.extra.KEY_CONFIRM"; + /** + * Set to true in {@link #ACTION_REQUEST_SHUTDOWN} to indicate that the shutdown is + * requested by the user. + * + * {@hide} + */ + public static final String EXTRA_USER_REQUESTED_SHUTDOWN = + "android.intent.extra.USER_REQUESTED_SHUTDOWN"; + /** + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or + * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} intents to override the default action + * of restarting the application. + */ + public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; + /** + * A String holding the phone number originally entered in + * {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL}, or the actual + * number to call in a {@link android.content.Intent#ACTION_CALL}. + */ + public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"; + /** + * Used as an int extra field in {@link android.content.Intent#ACTION_UID_REMOVED} + * intents to supply the uid the package had been assigned. Also an optional + * extra in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or + * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} for the same + * purpose. + */ + public static final String EXTRA_UID = "android.intent.extra.UID"; + /** + * @hide String array of package names. + */ + @SystemApi + public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; + /** + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} + * intents to indicate whether this represents a full uninstall (removing + * both the code and its data) or a partial uninstall (leaving its data, + * implying that this is an update). + */ + public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; + /** + * @hide + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} + * intents to indicate that at this point the package has been removed for + * all users on the device. + */ + public static final String EXTRA_REMOVED_FOR_ALL_USERS + = "android.intent.extra.REMOVED_FOR_ALL_USERS"; + /** + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} + * intents to indicate that this is a replacement of the package, so this + * broadcast will immediately be followed by an add broadcast for a + * different version of the same package. + */ + public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING"; + /** + * Used as an int extra field in {@link android.app.AlarmManager} intents + * to tell the application being invoked how many pending alarms are being + * delievered with the intent. For one-shot alarms this will always be 1. + * For recurring alarms, this might be greater than 1 if the device was + * asleep or powered off at the time an earlier alarm would have been + * delivered. + */ + public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; + /** + * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT} + * intents to request the dock state. Possible values are + * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED}, + * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or + * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}, or + * {@link android.content.Intent#EXTRA_DOCK_STATE_LE_DESK}, or + * {@link android.content.Intent#EXTRA_DOCK_STATE_HE_DESK}. + */ + public static final String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; + /** + * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE} + * to represent that the phone is not in any dock. + */ + public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; + /** + * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE} + * to represent that the phone is in a desk dock. + */ + public static final int EXTRA_DOCK_STATE_DESK = 1; + /** + * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE} + * to represent that the phone is in a car dock. + */ + public static final int EXTRA_DOCK_STATE_CAR = 2; + /** + * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE} + * to represent that the phone is in a analog (low end) dock. + */ + public static final int EXTRA_DOCK_STATE_LE_DESK = 3; + /** + * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE} + * to represent that the phone is in a digital (high end) dock. + */ + public static final int EXTRA_DOCK_STATE_HE_DESK = 4; + /** + * Boolean that can be supplied as meta-data with a dock activity, to + * indicate that the dock should take over the home key when it is active. + */ + public static final String METADATA_DOCK_HOME = "android.dock_home"; + /** + * Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing + * the bug report. + */ + public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT"; + /** + * Used in the extra field in the remote intent. It's astring token passed with the + * remote intent. + */ + public static final String EXTRA_REMOTE_INTENT_TOKEN = + "android.intent.extra.remote_intent_token"; + /** + * @deprecated See {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST}; this field + * will contain only the first name in the list. + */ + @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME = + "android.intent.extra.changed_component_name"; + /** + * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED}, + * and contains a string array of all of the components that have changed. If + * the state of the overall package has changed, then it will contain an entry + * with the package name itself. + */ + public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = + "android.intent.extra.changed_component_name_list"; + /** + * This field is part of + * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE}, + * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}, + * {@link android.content.Intent#ACTION_PACKAGES_SUSPENDED}, + * {@link android.content.Intent#ACTION_PACKAGES_UNSUSPENDED} + * and contains a string array of all of the components that have changed. + */ + public static final String EXTRA_CHANGED_PACKAGE_LIST = + "android.intent.extra.changed_package_list"; + /** + * This field is part of + * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE}, + * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE} + * and contains an integer array of uids of all of the components + * that have changed. + */ + public static final String EXTRA_CHANGED_UID_LIST = + "android.intent.extra.changed_uid_list"; + /** + * @hide + * Magic extra system code can use when binding, to give a label for + * who it is that has bound to a service. This is an integer giving + * a framework string resource that can be displayed to the user. + */ + public static final String EXTRA_CLIENT_LABEL = + "android.intent.extra.client_label"; + /** + * @hide + * Magic extra system code can use when binding, to give a PendingIntent object + * that can be launched for the user to disable the system's use of this + * service. + */ + public static final String EXTRA_CLIENT_INTENT = + "android.intent.extra.client_intent"; + /** + * Extra used to indicate that an intent should only return data that is on + * the local device. This is a boolean extra; the default is false. If true, + * an implementation should only allow the user to select data that is + * already on the device, not requiring it be downloaded from a remote + * service when opened. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + * @see #ACTION_OPEN_DOCUMENT_TREE + * @see #ACTION_CREATE_DOCUMENT + */ + public static final String EXTRA_LOCAL_ONLY = + "android.intent.extra.LOCAL_ONLY"; + /** + * Extra used to indicate that an intent can allow the user to select and + * return multiple items. This is a boolean extra; the default is false. If + * true, an implementation is allowed to present the user with a UI where + * they can pick multiple items that are all returned to the caller. When + * this happens, they should be returned as the {@link #getClipData()} part + * of the result Intent. + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + */ + public static final String EXTRA_ALLOW_MULTIPLE = + "android.intent.extra.ALLOW_MULTIPLE"; + /** + * The integer userHandle carried with broadcast intents related to addition, removal and + * switching of users and managed profiles - {@link #ACTION_USER_ADDED}, + * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}. + * + * @hide + */ + public static final String EXTRA_USER_HANDLE = + "android.intent.extra.user_handle"; + /** + * The UserHandle carried with broadcasts intents related to addition and removal of managed + * profiles - {@link #ACTION_MANAGED_PROFILE_ADDED} and {@link #ACTION_MANAGED_PROFILE_REMOVED}. + */ + public static final String EXTRA_USER = + "android.intent.extra.USER"; + /** + * Extra used in the response from a BroadcastReceiver that handles + * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is + * ArrayList<RestrictionEntry>. + */ + public static final String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list"; + /** + * Extra sent in the intent to the BroadcastReceiver that handles + * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is a Bundle containing + * the restrictions as key/value pairs. + */ + public static final String EXTRA_RESTRICTIONS_BUNDLE = + "android.intent.extra.restrictions_bundle"; + /** + * Extra used in the response from a BroadcastReceiver that handles + * {@link #ACTION_GET_RESTRICTION_ENTRIES}. + */ + public static final String EXTRA_RESTRICTIONS_INTENT = + "android.intent.extra.restrictions_intent"; + /** + * Extra used to communicate a set of acceptable MIME types. The type of the + * extra is {@code String[]}. Values may be a combination of concrete MIME + * types (such as "image/png") and/or partial MIME types (such as + * "audio/*"). + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + */ + public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; + /** + * Optional extra for {@link #ACTION_SHUTDOWN} that allows the sender to qualify that + * this shutdown is only for the user space of the system, not a complete shutdown. + * When this is true, hardware devices can use this information to determine that + * they shouldn't do a complete shutdown of their device since this is not a + * complete shutdown down to the kernel, but only user space restarting. + * The default if not supplied is false. + */ + public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY + = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY"; + /** + * Optional boolean extra for {@link #ACTION_TIME_CHANGED} that indicates the + * user has set their time format preferences to the 24 hour format. + * + * @hide for internal use only. + */ + public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT = + "android.intent.extra.TIME_PREF_24_HOUR_FORMAT"; + /** {@hide} */ + public static final String EXTRA_REASON = "android.intent.extra.REASON"; + /** {@hide} */ + public static final String EXTRA_WIPE_EXTERNAL_STORAGE = "android.intent.extra.WIPE_EXTERNAL_STORAGE"; + /** + * Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM + * activation request. + * TODO: Add information about the structure and response data used with the pending intent. + * @hide + */ + public static final String EXTRA_SIM_ACTIVATION_RESPONSE = + "android.intent.extra.SIM_ACTIVATION_RESPONSE"; + /** + * Optional index with semantics depending on the intent action. + * + *

The value must be an integer greater or equal to 0. + */ + public static final String EXTRA_INDEX = "android.intent.extra.INDEX"; + /** + * Optional boolean extra indicating whether quiet mode has been switched on or off. + * When a profile goes into quiet mode, all apps in the profile are killed and the + * profile user is stopped. Widgets originating from the profile are masked, and app + * launcher icons are grayed out. + */ + public static final String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE"; + /** + * Used as an int extra field in {@link #ACTION_MEDIA_RESOURCE_GRANTED} + * intents to specify the resource type granted. Possible values are + * {@link #EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC} or + * {@link #EXTRA_MEDIA_RESOURCE_TYPE_AUDIO_CODEC}. + * + * @hide + */ + public static final String EXTRA_MEDIA_RESOURCE_TYPE = + "android.intent.extra.MEDIA_RESOURCE_TYPE"; + /** + * Used as an int value for {@link #EXTRA_MEDIA_RESOURCE_TYPE} + * to represent that a video codec is allowed to use. + * + * @hide + */ + public static final int EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC = 0; + /** + * Used as an int value for {@link #EXTRA_MEDIA_RESOURCE_TYPE} + * to represent that a audio codec is allowed to use. + * + * @hide + */ + public static final int EXTRA_MEDIA_RESOURCE_TYPE_AUDIO_CODEC = 1; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Intent flags (see mFlags variable). + /** @hide */ + @IntDef(flag = true, value = { + FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION, + FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION }) + @Retention(RetentionPolicy.SOURCE) + public @interface GrantUriMode {} + /** @hide */ + @IntDef(flag = true, value = { + FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION }) + @Retention(RetentionPolicy.SOURCE) + public @interface AccessUriMode {} + /** + * Test if given mode flags specify an access mode, which must be at least + * read and/or write. + * + * @hide + */ + public static boolean isAccessUriMode(int modeFlags) { + return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0; + } + /** + * If set, the recipient of this Intent will be granted permission to + * perform read operations on the URI in the Intent's data and any URIs + * specified in its ClipData. When applying to an Intent's ClipData, + * all URIs as well as recursive traversals through data or other ClipData + * in Intent items will be granted; only the grant flags of the top-level + * Intent are used. + */ + public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001; + /** + * If set, the recipient of this Intent will be granted permission to + * perform write operations on the URI in the Intent's data and any URIs + * specified in its ClipData. When applying to an Intent's ClipData, + * all URIs as well as recursive traversals through data or other ClipData + * in Intent items will be granted; only the grant flags of the top-level + * Intent are used. + */ + public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002; + /** + * Can be set by the caller to indicate that this Intent is coming from + * a background operation, not from direct user interaction. + */ + public static final int FLAG_FROM_BACKGROUND = 0x00000004; + /** + * A flag you can enable for debugging: when set, log messages will be + * printed during the resolution of this intent to show you what has + * been found to create the final resolved list. + */ + public static final int FLAG_DEBUG_LOG_RESOLUTION = 0x00000008; + /** + * If set, this intent will not match any components in packages that + * are currently stopped. If this is not set, then the default behavior + * is to include such applications in the result. + */ + public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 0x00000010; + /** + * If set, this intent will always match any components in packages that + * are currently stopped. This is the default behavior when + * {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these + * flags are set, this one wins (it allows overriding of exclude for + * places where the framework may automatically set the exclude flag). + */ + public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020; + /** + * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant can be + * persisted across device reboots until explicitly revoked with + * {@link Context#revokeUriPermission(Uri, int)}. This flag only offers the + * grant for possible persisting; the receiving application must call + * {@link ContentResolver#takePersistableUriPermission(Uri, int)} to + * actually persist. + * + * @see ContentResolver#takePersistableUriPermission(Uri, int) + * @see ContentResolver#releasePersistableUriPermission(Uri, int) + * @see ContentResolver#getPersistedUriPermissions() + * @see ContentResolver#getOutgoingPersistedUriPermissions() + */ + public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040; + /** + * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant + * applies to any URI that is a prefix match against the original granted + * URI. (Without this flag, the URI must match exactly for access to be + * granted.) Another URI is considered a prefix match only when scheme, + * authority, and all path segments defined by the prefix are an exact + * match. + */ + public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080; + /** + * Internal flag used to indicate that a system component has done their + * homework and verified that they correctly handle packages and components + * that come and go over time. In particular: + *

    + *
  • Apps installed on external storage, which will appear to be + * uninstalled while the the device is ejected. + *
  • Apps with encryption unaware components, which will appear to not + * exist while the device is locked. + *
+ * + * @hide + */ + public static final int FLAG_DEBUG_TRIAGED_MISSING = 0x00000100; + /** + * Internal flag used to indicate ephemeral applications should not be + * considered when resolving the intent. + * + * @hide + */ + public static final int FLAG_IGNORE_EPHEMERAL = 0x00000200; + /** + * If set, the new activity is not kept in the history stack. As soon as + * the user navigates away from it, the activity is finished. This may also + * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory + * noHistory} attribute. + * + *

If set, {@link android.app.Activity#onActivityResult onActivityResult()} + * is never invoked when the current activity starts a new activity which + * sets a result and finishes. + */ + public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000; + /** + * If set, the activity will not be launched if it is already running + * at the top of the history stack. + */ + public static final int FLAG_ACTIVITY_SINGLE_TOP = 0x20000000; + /** + * If set, this activity will become the start of a new task on this + * history stack. A task (from the activity that started it to the + * next task activity) defines an atomic group of activities that the + * user can move to. Tasks can be moved to the foreground and background; + * all of the activities inside of a particular task always remain in + * the same order. See + * Tasks and Back + * Stack for more information about tasks. + * + *

This flag is generally used by activities that want + * to present a "launcher" style behavior: they give the user a list of + * separate things that can be done, which otherwise run completely + * independently of the activity launching them. + * + *

When using this flag, if a task is already running for the activity + * you are now starting, then a new activity will not be started; instead, + * the current task will simply be brought to the front of the screen with + * the state it was last in. See {@link #FLAG_ACTIVITY_MULTIPLE_TASK} for a flag + * to disable this behavior. + * + *

This flag can not be used when the caller is requesting a result from + * the activity being launched. + */ + public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000; + /** + * This flag is used to create a new task and launch an activity into it. + * This flag is always paired with either {@link #FLAG_ACTIVITY_NEW_DOCUMENT} + * or {@link #FLAG_ACTIVITY_NEW_TASK}. In both cases these flags alone would + * search through existing tasks for ones matching this Intent. Only if no such + * task is found would a new task be created. When paired with + * FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviors are modified to skip + * the search for a matching task and unconditionally start a new task. + * + * When used with {@link #FLAG_ACTIVITY_NEW_TASK} do not use this + * flag unless you are implementing your own + * top-level application launcher. Used in conjunction with + * {@link #FLAG_ACTIVITY_NEW_TASK} to disable the + * behavior of bringing an existing task to the foreground. When set, + * a new task is always started to host the Activity for the + * Intent, regardless of whether there is already an existing task running + * the same thing. + * + *

Because the default system does not include graphical task management, + * you should not use this flag unless you provide some way for a user to + * return back to the tasks you have launched. + * + * See {@link #FLAG_ACTIVITY_NEW_DOCUMENT} for details of this flag's use for + * creating new document tasks. + * + *

This flag is ignored if one of {@link #FLAG_ACTIVITY_NEW_TASK} or + * {@link #FLAG_ACTIVITY_NEW_DOCUMENT} is not also set. + * + *

See + * Tasks and Back + * Stack for more information about tasks. + * + * @see #FLAG_ACTIVITY_NEW_DOCUMENT + * @see #FLAG_ACTIVITY_NEW_TASK + */ + public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000; + /** + * If set, and the activity being launched is already running in the + * current task, then instead of launching a new instance of that activity, + * all of the other activities on top of it will be closed and this Intent + * will be delivered to the (now on top) old activity as a new Intent. + * + *

For example, consider a task consisting of the activities: A, B, C, D. + * If D calls startActivity() with an Intent that resolves to the component + * of activity B, then C and D will be finished and B receive the given + * Intent, resulting in the stack now being: A, B. + * + *

The currently running instance of activity B in the above example will + * either receive the new intent you are starting here in its + * onNewIntent() method, or be itself finished and restarted with the + * new intent. If it has declared its launch mode to be "multiple" (the + * default) and you have not set {@link #FLAG_ACTIVITY_SINGLE_TOP} in + * the same intent, then it will be finished and re-created; for all other + * launch modes or if {@link #FLAG_ACTIVITY_SINGLE_TOP} is set then this + * Intent will be delivered to the current instance's onNewIntent(). + * + *

This launch mode can also be used to good effect in conjunction with + * {@link #FLAG_ACTIVITY_NEW_TASK}: if used to start the root activity + * of a task, it will bring any currently running instance of that task + * to the foreground, and then clear it to its root state. This is + * especially useful, for example, when launching an activity from the + * notification manager. + * + *

See + * Tasks and Back + * Stack for more information about tasks. + */ + public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000; + /** + * If set and this intent is being used to launch a new activity from an + * existing one, then the reply target of the existing activity will be + * transfered to the new activity. This way the new activity can call + * {@link android.app.Activity#setResult} and have that result sent back to + * the reply target of the original activity. + */ + public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000; + /** + * If set and this intent is being used to launch a new activity from an + * existing one, the current activity will not be counted as the top + * activity for deciding whether the new intent should be delivered to + * the top instead of starting a new one. The previous activity will + * be used as the top, with the assumption being that the current activity + * will finish itself immediately. + */ + public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000; + /** + * If set, the new activity is not kept in the list of recently launched + * activities. + */ + public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000; + /** + * This flag is not normally set by application code, but set for you by + * the system as described in the + * {@link android.R.styleable#AndroidManifestActivity_launchMode + * launchMode} documentation for the singleTask mode. + */ + public static final int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000; + /** + * If set, and this activity is either being started in a new task or + * bringing to the top an existing task, then it will be launched as + * the front door of the task. This will result in the application of + * any affinities needed to have that task in the proper state (either + * moving activities to or from it), or simply resetting that task to + * its initial state if needed. + */ + public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000; + /** + * This flag is not normally set by application code, but set for you by + * the system if this activity is being launched from history + * (longpress home key). + */ + public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000; + /** + * @deprecated As of API 21 this performs identically to + * {@link #FLAG_ACTIVITY_NEW_DOCUMENT} which should be used instead of this. + */ + public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000; + /** + * This flag is used to open a document into a new task rooted at the activity launched + * by this Intent. Through the use of this flag, or its equivalent attribute, + * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity + * containing different documents will appear in the recent tasks list. + * + *

The use of the activity attribute form of this, + * {@link android.R.attr#documentLaunchMode}, is + * preferred over the Intent flag described here. The attribute form allows the + * Activity to specify multiple document behavior for all launchers of the Activity + * whereas using this flag requires each Intent that launches the Activity to specify it. + * + *

Note that the default semantics of this flag w.r.t. whether the recents entry for + * it is kept after the activity is finished is different than the use of + * {@link #FLAG_ACTIVITY_NEW_TASK} and {@link android.R.attr#documentLaunchMode} -- if + * this flag is being used to create a new recents entry, then by default that entry + * will be removed once the activity is finished. You can modify this behavior with + * {@link #FLAG_ACTIVITY_RETAIN_IN_RECENTS}. + * + *

FLAG_ACTIVITY_NEW_DOCUMENT may be used in conjunction with {@link + * #FLAG_ACTIVITY_MULTIPLE_TASK}. When used alone it is the + * equivalent of the Activity manifest specifying {@link + * android.R.attr#documentLaunchMode}="intoExisting". When used with + * FLAG_ACTIVITY_MULTIPLE_TASK it is the equivalent of the Activity manifest specifying + * {@link android.R.attr#documentLaunchMode}="always". + * + * Refer to {@link android.R.attr#documentLaunchMode} for more information. + * + * @see android.R.attr#documentLaunchMode + * @see #FLAG_ACTIVITY_MULTIPLE_TASK + */ + public static final int FLAG_ACTIVITY_NEW_DOCUMENT = FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; + /** + * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint} + * callback from occurring on the current frontmost activity before it is + * paused as the newly-started activity is brought to the front. + * + *

Typically, an activity can rely on that callback to indicate that an + * explicit user action has caused their activity to be moved out of the + * foreground. The callback marks an appropriate point in the activity's + * lifecycle for it to dismiss any notifications that it intends to display + * "until the user has seen them," such as a blinking LED. + * + *

If an activity is ever started via any non-user-driven events such as + * phone-call receipt or an alarm handler, this flag should be passed to {@link + * Context#startActivity Context.startActivity}, ensuring that the pausing + * activity does not think the user has acknowledged its notification. + */ + public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause the launched activity to be brought to the front of its + * task's history stack if it is already running. + * + *

For example, consider a task consisting of four activities: A, B, C, D. + * If D calls startActivity() with an Intent that resolves to the component + * of activity B, then B will be brought to the front of the history stack, + * with this resulting order: A, C, D, B. + * + * This flag will be ignored if {@link #FLAG_ACTIVITY_CLEAR_TOP} is also + * specified. + */ + public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 0X00020000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will prevent the system from applying an activity transition + * animation to go to the next activity state. This doesn't mean an + * animation will never run -- if another activity change happens that doesn't + * specify this flag before the activity started here is displayed, then + * that transition will be used. This flag can be put to good use + * when you are going to do a series of activity operations but the + * animation seen by the user shouldn't be driven by the first activity + * change but rather a later one. + */ + public static final int FLAG_ACTIVITY_NO_ANIMATION = 0X00010000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause any existing task that would be associated with the + * activity to be cleared before the activity is started. That is, the activity + * becomes the new root of an otherwise empty task, and any old activities + * are finished. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause a newly launching task to be placed on top of the current + * home activity task (if there is one). That is, pressing back from the task + * will always return the user to home even if that was not the last activity they + * saw. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000; + /** + * By default a document created by {@link #FLAG_ACTIVITY_NEW_DOCUMENT} will + * have its entry in recent tasks removed when the user closes it (with back + * or however else it may finish()). If you would like to instead allow the + * document to be kept in recents so that it can be re-launched, you can use + * this flag. When set and the task's activity is finished, the recents + * entry will remain in the interface for the user to re-launch it, like a + * recents entry for a top-level application. + *

+ * The receiving activity can override this request with + * {@link android.R.attr#autoRemoveFromRecents} or by explcitly calling + * {@link android.app.Activity#finishAndRemoveTask() + * Activity.finishAndRemoveTask()}. + */ + public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000; + /** + * This flag is only used in split-screen multi-window mode. The new activity will be displayed + * adjacent to the one launching it. This can only be used in conjunction with + * {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is + * required if you want a new instance of an existing activity to be created. + */ + public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; + /** + * If set, when sending a broadcast only registered receivers will be + * called -- no BroadcastReceiver components will be launched. + */ + public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000; + /** + * If set, when sending a broadcast the new broadcast will replace + * any existing pending broadcast that matches it. Matching is defined + * by {@link Intent#filterEquals(Intent) Intent.filterEquals} returning + * true for the intents of the two broadcasts. When a match is found, + * the new broadcast (and receivers associated with it) will replace the + * existing one in the pending broadcast list, remaining at the same + * position in the list. + * + *

This flag is most typically used with sticky broadcasts, which + * only care about delivering the most recent values of the broadcast + * to their receivers. + */ + public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000; + /** + * If set, when sending a broadcast the recipient is allowed to run at + * foreground priority, with a shorter timeout interval. During normal + * broadcasts the receivers are not automatically hoisted out of the + * background priority class. + */ + public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000; + /** + * If this is an ordered broadcast, don't allow receivers to abort the broadcast. + * They can still propagate results through to later receivers, but they can not prevent + * later receivers from seeing the broadcast. + */ + public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000; + /** + * If set, when sending a broadcast before boot has completed only + * registered receivers will be called -- no BroadcastReceiver components + * will be launched. Sticky intent state will be recorded properly even + * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY} + * is specified in the broadcast intent, this flag is unnecessary. + * + *

This flag is only for use by system sevices as a convenience to + * avoid having to implement a more complex mechanism around detection + * of boot completion. + * + * @hide + */ + public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000; + /** + * Set when this broadcast is for a boot upgrade, a special mode that + * allows the broadcast to be sent before the system is ready and launches + * the app process with no providers running in it. + * @hide + */ + public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x02000000; + /** + * If set, the broadcast will always go to manifest receivers in background (cached + * or not running) apps, regardless of whether that would be done by default. By + * default they will only receive broadcasts if the broadcast has specified an + * explicit component or package name. + * @hide + */ + public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; + /** + * If set, the broadcast will never go to manifest receivers in background (cached + * or not running) apps, regardless of whether that would be done by default. By + * default they will receive broadcasts if the broadcast has specified an + * explicit component or package name. + * @hide + */ + public static final int FLAG_RECEIVER_EXCLUDE_BACKGROUND = 0x00800000; + /** + * @hide Flags that can't be changed with PendingIntent. + */ + public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION + | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | FLAG_GRANT_PREFIX_URI_PERMISSION; + // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // toUri() and parseUri() options. + /** + * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string + * always has the "intent:" scheme. This syntax can be used when you want + * to later disambiguate between URIs that are intended to describe an + * Intent vs. all others that should be treated as raw URIs. When used + * with {@link #parseUri}, any other scheme will result in a generic + * VIEW action for that raw URI. + */ + public static final int URI_INTENT_SCHEME = 1<<0; + /** + * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string + * always has the "android-app:" scheme. This is a variation of + * {@link #URI_INTENT_SCHEME} whose format is simpler for the case of an + * http/https URI being delivered to a specific package name. The format + * is: + * + *

+     * android-app://{package_id}[/{scheme}[/{host}[/{path}]]][#Intent;{...}]
+ * + *

In this scheme, only the package_id is required. If you include a host, + * you must also include a scheme; including a path also requires both a host and a scheme. + * The final #Intent; fragment can be used without a scheme, host, or path. + * Note that this can not be + * used with intents that have a {@link #setSelector}, since the base intent + * will always have an explicit package name.

+ * + *

Some examples of how this scheme maps to Intent objects:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
URI Intent
android-app://com.example.app + * + * + *
Action: {@link #ACTION_MAIN}
Package: com.example.app
android-app://com.example.app/http/example.com + * + * + * + *
Action: {@link #ACTION_VIEW}
Data: http://example.com/
Package: com.example.app
android-app://com.example.app/http/example.com/foo?1234 + * + * + * + *
Action: {@link #ACTION_VIEW}
Data: http://example.com/foo?1234
Package: com.example.app
android-app://com.example.app/
#Intent;action=com.example.MY_ACTION;end
+ * + * + *
Action: com.example.MY_ACTION
Package: com.example.app
android-app://com.example.app/http/example.com/foo?1234
#Intent;action=com.example.MY_ACTION;end
+ * + * + * + *
Action: com.example.MY_ACTION
Data: http://example.com/foo?1234
Package: com.example.app
android-app://com.example.app/
#Intent;action=com.example.MY_ACTION;
i.some_int=100;S.some_str=hello;end
+ * + * + * + *
Action: com.example.MY_ACTION
Package: com.example.app
Extras: some_int=(int)100
some_str=(String)hello
+ */ + public static final int URI_ANDROID_APP_SCHEME = 1<<1; + /** + * Flag for use with {@link #toUri} and {@link #parseUri}: allow parsing + * of unsafe information. In particular, the flags {@link #FLAG_GRANT_READ_URI_PERMISSION}, + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, {@link #FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, + * and {@link #FLAG_GRANT_PREFIX_URI_PERMISSION} flags can not be set, so that the + * generated Intent can not cause unexpected data access to happen. + * + *

If you do not trust the source of the URI being parsed, you should still do further + * processing to protect yourself from it. In particular, when using it to start an + * activity you should usually add in {@link #CATEGORY_BROWSABLE} to limit the activities + * that can handle it.

+ */ + public static final int URI_ALLOW_UNSAFE = 1<<2; + // --------------------------------------------------------------------- + private String mAction; + private Uri mData; + private String mType; + private String mPackage; + private ComponentName mComponent; + private int mFlags; + private ArraySet mCategories; + private Bundle mExtras; + private Rect mSourceBounds; + private Intent mSelector; + private ClipData mClipData; + // --------------------------------------------------------------------- + /** + * Create an empty intent. + */ + public Intent() { + } + /** + * Copy constructor. + */ + public Intent(Intent o) { + this.mAction = o.mAction; + this.mData = o.mData; + this.mType = o.mType; + this.mPackage = o.mPackage; + this.mComponent = o.mComponent; + this.mFlags = o.mFlags; + if (o.mCategories != null) { + this.mCategories = new ArraySet(o.mCategories); + } + if (o.mExtras != null) { + this.mExtras = new Bundle(o.mExtras); + } + if (o.mSourceBounds != null) { + this.mSourceBounds = new Rect(o.mSourceBounds); + } + if (o.mSelector != null) { + this.mSelector = new Intent(o.mSelector); + } + if (o.mClipData != null) { + this.mClipData = new ClipData(o.mClipData); + } + } + @Override + public Object clone() { + return new Intent(this); + } + private Intent(Intent o, boolean all) { + this.mAction = o.mAction; + this.mData = o.mData; + this.mType = o.mType; + this.mPackage = o.mPackage; + this.mComponent = o.mComponent; + if (o.mCategories != null) { + this.mCategories = new ArraySet(o.mCategories); + } + } + /** + * Make a clone of only the parts of the Intent that are relevant for + * filter matching: the action, data, type, component, and categories. + */ + public Intent cloneFilter() { + return new Intent(this, false); + } + /** + * Create an intent with a given action. All other fields (data, type, + * class) are null. Note that the action must be in a + * namespace because Intents are used globally in the system -- for + * example the system VIEW action is android.intent.action.VIEW; an + * application's custom action would be something like + * com.google.app.myapp.CUSTOM_ACTION. + * + * @param action The Intent action, such as ACTION_VIEW. + */ + public Intent(String action) { + setAction(action); + } + /** + * Create an intent with a given action and for a given data url. Note + * that the action must be in a namespace because Intents are + * used globally in the system -- for example the system VIEW action is + * android.intent.action.VIEW; an application's custom action would be + * something like com.google.app.myapp.CUSTOM_ACTION. + * + *

Note: scheme and host name matching in the Android framework is + * case-sensitive, unlike the formal RFC. As a result, + * you should always ensure that you write your Uri with these elements + * using lower case letters, and normalize any Uris you receive from + * outside of Android to ensure the scheme and host is lower case.

+ * + * @param action The Intent action, such as ACTION_VIEW. + * @param uri The Intent data URI. + */ + public Intent(String action, Uri uri) { + setAction(action); + mData = uri; + } + /** + * Create an intent for a specific component. All other fields (action, data, + * type, class) are null, though they can be modified later with explicit + * calls. This provides a convenient way to create an intent that is + * intended to execute a hard-coded class name, rather than relying on the + * system to find an appropriate class for you; see {@link #setComponent} + * for more information on the repercussions of this. + * + * @param packageContext A Context of the application package implementing + * this class. + * @param cls The component class that is to be used for the intent. + * + * @see #setClass + * @see #setComponent + * @see #Intent(String, android.net.Uri , Context, Class) + */ + public Intent(Context packageContext, Class cls) { + mComponent = new ComponentName(packageContext, cls); + } + /** + * Create an intent for a specific component with a specified action and data. + * This is equivalent to using {@link #Intent(String, android.net.Uri)} to + * construct the Intent and then calling {@link #setClass} to set its + * class. + * + *

Note: scheme and host name matching in the Android framework is + * case-sensitive, unlike the formal RFC. As a result, + * you should always ensure that you write your Uri with these elements + * using lower case letters, and normalize any Uris you receive from + * outside of Android to ensure the scheme and host is lower case.

+ * + * @param action The Intent action, such as ACTION_VIEW. + * @param uri The Intent data URI. + * @param packageContext A Context of the application package implementing + * this class. + * @param cls The component class that is to be used for the intent. + * + * @see #Intent(String, android.net.Uri) + * @see #Intent(Context, Class) + * @see #setClass + * @see #setComponent + */ + public Intent(String action, Uri uri, + Context packageContext, Class cls) { + setAction(action); + mData = uri; + mComponent = new ComponentName(packageContext, cls); + } + /** + * Create an intent to launch the main (root) activity of a task. This + * is the Intent that is started when the application's is launched from + * Home. For anything else that wants to launch an application in the + * same way, it is important that they use an Intent structured the same + * way, and can use this function to ensure this is the case. + * + *

The returned Intent has the given Activity component as its explicit + * component, {@link #ACTION_MAIN} as its action, and includes the + * category {@link #CATEGORY_LAUNCHER}. This does not have + * {@link #FLAG_ACTIVITY_NEW_TASK} set, though typically you will want + * to do that through {@link #addFlags(int)} on the returned Intent. + * + * @param mainActivity The main activity component that this Intent will + * launch. + * @return Returns a newly created Intent that can be used to launch the + * activity as a main application entry. + * + * @see #setClass + * @see #setComponent + */ + public static Intent makeMainActivity(ComponentName mainActivity) { + Intent intent = new Intent(ACTION_MAIN); + intent.setComponent(mainActivity); + intent.addCategory(CATEGORY_LAUNCHER); + return intent; + } + /** + * Make an Intent for the main activity of an application, without + * specifying a specific activity to run but giving a selector to find + * the activity. This results in a final Intent that is structured + * the same as when the application is launched from + * Home. For anything else that wants to launch an application in the + * same way, it is important that they use an Intent structured the same + * way, and can use this function to ensure this is the case. + * + *

The returned Intent has {@link #ACTION_MAIN} as its action, and includes the + * category {@link #CATEGORY_LAUNCHER}. This does not have + * {@link #FLAG_ACTIVITY_NEW_TASK} set, though typically you will want + * to do that through {@link #addFlags(int)} on the returned Intent. + * + * @param selectorAction The action name of the Intent's selector. + * @param selectorCategory The name of a category to add to the Intent's + * selector. + * @return Returns a newly created Intent that can be used to launch the + * activity as a main application entry. + * + * @see #setSelector(Intent) + */ + public static Intent makeMainSelectorActivity(String selectorAction, + String selectorCategory) { + Intent intent = new Intent(ACTION_MAIN); + intent.addCategory(CATEGORY_LAUNCHER); + Intent selector = new Intent(); + selector.setAction(selectorAction); + selector.addCategory(selectorCategory); + intent.setSelector(selector); + return intent; + } + /** + * Make an Intent that can be used to re-launch an application's task + * in its base state. This is like {@link #makeMainActivity(ComponentName)}, + * but also sets the flags {@link #FLAG_ACTIVITY_NEW_TASK} and + * {@link #FLAG_ACTIVITY_CLEAR_TASK}. + * + * @param mainActivity The activity component that is the root of the + * task; this is the activity that has been published in the application's + * manifest as the main launcher icon. + * + * @return Returns a newly created Intent that can be used to relaunch the + * activity's task in its root state. + */ + public static Intent makeRestartActivityTask(ComponentName mainActivity) { + Intent intent = makeMainActivity(mainActivity); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return intent; + } + /** + * Call {@link #parseUri} with 0 flags. + * @deprecated Use {@link #parseUri} instead. + */ + @Deprecated + public static Intent getIntent(String uri) throws URISyntaxException { + return parseUri(uri, 0); + } + /** + * Create an intent from a URI. This URI may encode the action, + * category, and other intent fields, if it was returned by + * {@link #toUri}. If the Intent was not generate by toUri(), its data + * will be the entire URI and its action will be ACTION_VIEW. + * + *

The URI given here must not be relative -- that is, it must include + * the scheme and full path. + * + * @param uri The URI to turn into an Intent. + * @param flags Additional processing flags. Either 0, + * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}. + * + * @return Intent The newly created Intent object. + * + * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax + * it bad (as parsed by the Uri class) or the Intent data within the + * URI is invalid. + * + * @see #toUri + */ + public static Intent parseUri(String uri, int flags) throws URISyntaxException { + int i = 0; + try { + final boolean androidApp = uri.startsWith("android-app:"); + // Validate intent scheme if requested. + if ((flags&(URI_INTENT_SCHEME|URI_ANDROID_APP_SCHEME)) != 0) { + if (!uri.startsWith("intent:") && !androidApp) { + Intent intent = new Intent(ACTION_VIEW); + try { + intent.setData(Uri.parse(uri)); + } catch (IllegalArgumentException e) { + throw new URISyntaxException(uri, e.getMessage()); + } + return intent; + } + } + i = uri.lastIndexOf("#"); + // simple case + if (i == -1) { + if (!androidApp) { + return new Intent(ACTION_VIEW, Uri.parse(uri)); + } + // old format Intent URI + } else if (!uri.startsWith("#Intent;", i)) { + if (!androidApp) { + return getIntentOld(uri, flags); + } else { + i = -1; + } + } + // new format + Intent intent = new Intent(ACTION_VIEW); + Intent baseIntent = intent; + boolean explicitAction = false; + boolean inSelector = false; + // fetch data part, if present + String scheme = null; + String data; + if (i >= 0) { + data = uri.substring(0, i); + i += 8; // length of "#Intent;" + } else { + data = uri; + } + // loop over contents of Intent, all name=value; + while (i >= 0 && !uri.startsWith("end", i)) { + int eq = uri.indexOf('=', i); + if (eq < 0) eq = i-1; + int semi = uri.indexOf(';', i); + String value = eq < semi ? Uri.decode(uri.substring(eq + 1, semi)) : ""; + // action + if (uri.startsWith("action=", i)) { + intent.setAction(value); + if (!inSelector) { + explicitAction = true; + } + } + // categories + else if (uri.startsWith("category=", i)) { + intent.addCategory(value); + } + // type + else if (uri.startsWith("type=", i)) { + intent.mType = value; + } + // launch flags + else if (uri.startsWith("launchFlags=", i)) { + intent.mFlags = Integer.decode(value).intValue(); + if ((flags& URI_ALLOW_UNSAFE) == 0) { + intent.mFlags &= ~IMMUTABLE_FLAGS; + } + } + // package + else if (uri.startsWith("package=", i)) { + intent.mPackage = value; + } + // component + else if (uri.startsWith("component=", i)) { + intent.mComponent = ComponentName.unflattenFromString(value); + } + // scheme + else if (uri.startsWith("scheme=", i)) { + if (inSelector) { + intent.mData = Uri.parse(value + ":"); + } else { + scheme = value; + } + } + // source bounds + else if (uri.startsWith("sourceBounds=", i)) { + intent.mSourceBounds = Rect.unflattenFromString(value); + } + // selector + else if (semi == (i+3) && uri.startsWith("SEL", i)) { + intent = new Intent(); + inSelector = true; + } + // extra + else { + String key = Uri.decode(uri.substring(i + 2, eq)); + // create Bundle if it doesn't already exist + if (intent.mExtras == null) intent.mExtras = new Bundle(); + Bundle b = intent.mExtras; + // add EXTRA + if (uri.startsWith("S.", i)) b.putString(key, value); + else if (uri.startsWith("B.", i)) b.putBoolean(key, Boolean.parseBoolean(value)); + else if (uri.startsWith("b.", i)) b.putByte(key, Byte.parseByte(value)); + else if (uri.startsWith("c.", i)) b.putChar(key, value.charAt(0)); + else if (uri.startsWith("d.", i)) b.putDouble(key, Double.parseDouble(value)); + else if (uri.startsWith("f.", i)) b.putFloat(key, Float.parseFloat(value)); + else if (uri.startsWith("i.", i)) b.putInt(key, Integer.parseInt(value)); + else if (uri.startsWith("l.", i)) b.putLong(key, Long.parseLong(value)); + else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value)); + else throw new URISyntaxException(uri, "unknown EXTRA type", i); + } + // move to the next item + i = semi + 1; + } + if (inSelector) { + // The Intent had a selector; fix it up. + if (baseIntent.mPackage == null) { + baseIntent.setSelector(intent); + } + intent = baseIntent; + } + if (data != null) { + if (data.startsWith("intent:")) { + data = data.substring(7); + if (scheme != null) { + data = scheme + ':' + data; + } + } else if (data.startsWith("android-app:")) { + if (data.charAt(12) == '/' && data.charAt(13) == '/') { + // Correctly formed android-app, first part is package name. + int end = data.indexOf('/', 14); + if (end < 0) { + // All we have is a package name. + intent.mPackage = data.substring(14); + if (!explicitAction) { + intent.setAction(ACTION_MAIN); + } + data = ""; + } else { + // Target the Intent at the given package name always. + String authority = null; + intent.mPackage = data.substring(14, end); + int newEnd; + if ((end+1) < data.length()) { + if ((newEnd=data.indexOf('/', end+1)) >= 0) { + // Found a scheme, remember it. + scheme = data.substring(end+1, newEnd); + end = newEnd; + if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) { + // Found a authority, remember it. + authority = data.substring(end+1, newEnd); + end = newEnd; + } + } else { + // All we have is a scheme. + scheme = data.substring(end+1); + } + } + if (scheme == null) { + // If there was no scheme, then this just targets the package. + if (!explicitAction) { + intent.setAction(ACTION_MAIN); + } + data = ""; + } else if (authority == null) { + data = scheme + ":"; + } else { + data = scheme + "://" + authority + data.substring(end); + } + } + } else { + data = ""; + } + } + if (data.length() > 0) { + try { + intent.mData = Uri.parse(data); + } catch (IllegalArgumentException e) { + throw new URISyntaxException(uri, e.getMessage()); + } + } + } + return intent; + } catch (IndexOutOfBoundsException e) { + throw new URISyntaxException(uri, "illegal Intent URI format", i); + } + } + public static Intent getIntentOld(String uri) throws URISyntaxException { + return getIntentOld(uri, 0); + } + private static Intent getIntentOld(String uri, int flags) throws URISyntaxException { + Intent intent; + int i = uri.lastIndexOf('#'); + if (i >= 0) { + String action = null; + final int intentFragmentStart = i; + boolean isIntentFragment = false; + i++; + if (uri.regionMatches(i, "action(", 0, 7)) { + isIntentFragment = true; + i += 7; + int j = uri.indexOf(')', i); + action = uri.substring(i, j); + i = j + 1; + } + intent = new Intent(action); + if (uri.regionMatches(i, "categories(", 0, 11)) { + isIntentFragment = true; + i += 11; + int j = uri.indexOf(')', i); + while (i < j) { + int sep = uri.indexOf('!', i); + if (sep < 0 || sep > j) sep = j; + if (i < sep) { + intent.addCategory(uri.substring(i, sep)); + } + i = sep + 1; + } + i = j + 1; + } + if (uri.regionMatches(i, "type(", 0, 5)) { + isIntentFragment = true; + i += 5; + int j = uri.indexOf(')', i); + intent.mType = uri.substring(i, j); + i = j + 1; + } + if (uri.regionMatches(i, "launchFlags(", 0, 12)) { + isIntentFragment = true; + i += 12; + int j = uri.indexOf(')', i); + intent.mFlags = Integer.decode(uri.substring(i, j)).intValue(); + if ((flags& URI_ALLOW_UNSAFE) == 0) { + intent.mFlags &= ~IMMUTABLE_FLAGS; + } + i = j + 1; + } + if (uri.regionMatches(i, "component(", 0, 10)) { + isIntentFragment = true; + i += 10; + int j = uri.indexOf(')', i); + int sep = uri.indexOf('!', i); + if (sep >= 0 && sep < j) { + String pkg = uri.substring(i, sep); + String cls = uri.substring(sep + 1, j); + intent.mComponent = new ComponentName(pkg, cls); + } + i = j + 1; + } + if (uri.regionMatches(i, "extras(", 0, 7)) { + isIntentFragment = true; + i += 7; + final int closeParen = uri.indexOf(')', i); + if (closeParen == -1) throw new URISyntaxException(uri, + "EXTRA missing trailing ')'", i); + while (i < closeParen) { + // fetch the key value + int j = uri.indexOf('=', i); + if (j <= i + 1 || i >= closeParen) { + throw new URISyntaxException(uri, "EXTRA missing '='", i); + } + char type = uri.charAt(i); + i++; + String key = uri.substring(i, j); + i = j + 1; + // get type-value + j = uri.indexOf('!', i); + if (j == -1 || j >= closeParen) j = closeParen; + if (i >= j) throw new URISyntaxException(uri, "EXTRA missing '!'", i); + String value = uri.substring(i, j); + i = j; + // create Bundle if it doesn't already exist + if (intent.mExtras == null) intent.mExtras = new Bundle(); + // add item to bundle + try { + switch (type) { + case 'S': + intent.mExtras.putString(key, Uri.decode(value)); + break; + case 'B': + intent.mExtras.putBoolean(key, Boolean.parseBoolean(value)); + break; + case 'b': + intent.mExtras.putByte(key, Byte.parseByte(value)); + break; + case 'c': + intent.mExtras.putChar(key, Uri.decode(value).charAt(0)); + break; + case 'd': + intent.mExtras.putDouble(key, Double.parseDouble(value)); + break; + case 'f': + intent.mExtras.putFloat(key, Float.parseFloat(value)); + break; + case 'i': + intent.mExtras.putInt(key, Integer.parseInt(value)); + break; + case 'l': + intent.mExtras.putLong(key, Long.parseLong(value)); + break; + case 's': + intent.mExtras.putShort(key, Short.parseShort(value)); + break; + default: + throw new URISyntaxException(uri, "EXTRA has unknown type", i); + } + } catch (NumberFormatException e) { + throw new URISyntaxException(uri, "EXTRA value can't be parsed", i); + } + char ch = uri.charAt(i); + if (ch == ')') break; + if (ch != '!') throw new URISyntaxException(uri, "EXTRA missing '!'", i); + i++; + } + } + if (isIntentFragment) { + intent.mData = Uri.parse(uri.substring(0, intentFragmentStart)); + } else { + intent.mData = Uri.parse(uri); + } + if (intent.mAction == null) { + // By default, if no action is specified, then use VIEW. + intent.mAction = ACTION_VIEW; + } + } else { + intent = new Intent(ACTION_VIEW, Uri.parse(uri)); + } + return intent; + } + /** @hide */ + public static void printIntentArgsHelp(PrintWriter pw, String prefix) { + final String[] lines = new String[] { + " specifications include these flags and arguments:", + " [-a ] [-d ] [-t ]", + " [-c [-c ] ...]", + " [-e|--es ...]", + " [--esn ...]", + " [--ez ...]", + " [--ei ...]", + " [--el ...]", + " [--ef ...]", + " [--eu ...]", + " [--ecn ]", + " [--eia [, [,)", + " [--ela [, [,)", + " [--efa [, [,)", + " [--esa [, [,; to embed a comma into a string,", + " escape it using \"\\,\")", + " [--f ]", + " [--grant-read-uri-permission] [--grant-write-uri-permission]", + " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]", + " [--debug-log-resolution] [--exclude-stopped-packages]", + " [--include-stopped-packages]", + " [--activity-brought-to-front] [--activity-clear-top]", + " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]", + " [--activity-launched-from-history] [--activity-multiple-task]", + " [--activity-no-animation] [--activity-no-history]", + " [--activity-no-user-action] [--activity-previous-is-top]", + " [--activity-reorder-to-front] [--activity-reset-task-if-needed]", + " [--activity-single-top] [--activity-clear-task]", + " [--activity-task-on-home]", + " [--receiver-registered-only] [--receiver-replace-pending]", + " [--receiver-foreground]", + " [--selector]", + " [ | | ]" + }; + for (String line : lines) { + pw.print(prefix); + pw.println(line); + } + } + /** + * Retrieve the general action to be performed, such as + * {@link #ACTION_VIEW}. The action describes the general way the rest of + * the information in the intent should be interpreted -- most importantly, + * what to do with the data returned by {@link #getData}. + * + * @return The action of this intent or null if none is specified. + * + * @see #setAction + */ + public String getAction() { + return mAction; + } + /** + * Retrieve data this intent is operating on. This URI specifies the name + * of the data; often it uses the content: scheme, specifying data in a + * content provider. Other schemes may be handled by specific activities, + * such as http: by the web browser. + * + * @return The URI of the data this intent is targeting or null. + * + * @see #getScheme + * @see #setData + */ + public Uri getData() { + return mData; + } + /** + * The same as {@link #getData()}, but returns the URI as an encoded + * String. + */ + public String getDataString() { + return mData != null ? mData.toString() : null; + } + /** + * Return the scheme portion of the intent's data. If the data is null or + * does not include a scheme, null is returned. Otherwise, the scheme + * prefix without the final ':' is returned, i.e. "http". + * + *

This is the same as calling getData().getScheme() (and checking for + * null data). + * + * @return The scheme of this intent. + * + * @see #getData + */ + public String getScheme() { + return mData != null ? mData.getScheme() : null; + } + /** + * Retrieve any explicit MIME type included in the intent. This is usually + * null, as the type is determined by the intent data. + * + * @return If a type was manually set, it is returned; else null is + * returned. + * + * @see #resolveType(ContentResolver) + * @see #setType + */ + public String getType() { + return mType; + } + /** + * Return the MIME data type of this intent. If the type field is + * explicitly set, that is simply returned. Otherwise, if the data is set, + * the type of that data is returned. If neither fields are set, a null is + * returned. + * + * @return The MIME type of this intent. + * + * @see #getType + * @see #resolveType(ContentResolver) + */ + public String resolveType(Context context) { + return resolveType(context.getContentResolver()); + } + /** + * Return the MIME data type of this intent. If the type field is + * explicitly set, that is simply returned. Otherwise, if the data is set, + * the type of that data is returned. If neither fields are set, a null is + * returned. + * + * @param resolver A ContentResolver that can be used to determine the MIME + * type of the intent's data. + * + * @return The MIME type of this intent. + * + * @see #getType + * @see #resolveType(Context) + */ + public String resolveType(ContentResolver resolver) { + if (mType != null) { + return mType; + } + if (mData != null) { + if ("content".equals(mData.getScheme())) { + return resolver.getType(mData); + } + } + return null; + } + /** + * Return the MIME data type of this intent, only if it will be needed for + * intent resolution. This is not generally useful for application code; + * it is used by the frameworks for communicating with back-end system + * services. + * + * @param resolver A ContentResolver that can be used to determine the MIME + * type of the intent's data. + * + * @return The MIME type of this intent, or null if it is unknown or not + * needed. + */ + public String resolveTypeIfNeeded(ContentResolver resolver) { + if (mComponent != null) { + return mType; + } + return resolveType(resolver); + } + /** + * Check if a category exists in the intent. + * + * @param category The category to check. + * + * @return boolean True if the intent contains the category, else false. + * + * @see #getCategories + * @see #addCategory + */ + public boolean hasCategory(String category) { + return mCategories != null && mCategories.contains(category); + } + /** + * Return the set of all categories in the intent. If there are no categories, + * returns NULL. + * + * @return The set of categories you can examine. Do not modify! + * + * @see #hasCategory + * @see #addCategory + */ + public Set getCategories() { + return mCategories; + } + /** + * Return the specific selector associated with this Intent. If there is + * none, returns null. See {@link #setSelector} for more information. + * + * @see #setSelector + */ + public Intent getSelector() { + return mSelector; + } + /** + * Return the {@link ClipData} associated with this Intent. If there is + * none, returns null. See {@link #setClipData} for more information. + * + * @see #setClipData + */ + public ClipData getClipData() { + return mClipData; + } + /** + * Sets the ClassLoader that will be used when unmarshalling + * any Parcelable values from the extras of this Intent. + * + * @param loader a ClassLoader, or null to use the default loader + * at the time of unmarshalling. + */ + public void setExtrasClassLoader(ClassLoader loader) { + if (mExtras != null) { + mExtras.setClassLoader(loader); + } + } + /** + * Returns true if an extra value is associated with the given name. + * @param name the extra's name + * @return true if the given extra is present. + */ + public boolean hasExtra(String name) { + return mExtras != null && mExtras.containsKey(name); + } + /** + * Returns true if the Intent's extras contain a parcelled file descriptor. + * @return true if the Intent contains a parcelled file descriptor. + */ + public boolean hasFileDescriptors() { + return mExtras != null && mExtras.hasFileDescriptors(); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if none was found. + * + * @deprecated + * @hide + */ + @Deprecated + public Object getExtra(String name) { + return getExtra(name, null); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, boolean) + */ + public boolean getBooleanExtra(String name, boolean defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getBoolean(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, byte) + */ + public byte getByteExtra(String name, byte defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getByte(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, short) + */ + public short getShortExtra(String name, short defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getShort(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, char) + */ + public char getCharExtra(String name, char defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getChar(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, int) + */ + public int getIntExtra(String name, int defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getInt(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, long) + */ + public long getLongExtra(String name, long defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getLong(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra(), + * or the default value if no such item is present + * + * @see #putExtra(String, float) + */ + public float getFloatExtra(String name, float defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getFloat(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue the value to be returned if no value of the desired + * type is stored with the given name. + * + * @return the value of an item that previously added with putExtra() + * or the default value if none was found. + * + * @see #putExtra(String, double) + */ + public double getDoubleExtra(String name, double defaultValue) { + return mExtras == null ? defaultValue : + mExtras.getDouble(name, defaultValue); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no String value was found. + * + * @see #putExtra(String, String) + */ + public String getStringExtra(String name) { + return mExtras == null ? null : mExtras.getString(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no CharSequence value was found. + * + * @see #putExtra(String, CharSequence) + */ + public CharSequence getCharSequenceExtra(String name) { + return mExtras == null ? null : mExtras.getCharSequence(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no Parcelable value was found. + * + * @see #putExtra(String, Parcelable) + */ + public T getParcelableExtra(String name) { + return mExtras == null ? null : mExtras.getParcelable(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no Parcelable[] value was found. + * + * @see #putExtra(String, Parcelable[]) + */ + public Parcelable[] getParcelableArrayExtra(String name) { + return mExtras == null ? null : mExtras.getParcelableArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no ArrayList value was found. + * + * @see #putParcelableArrayListExtra(String, ArrayList) + */ + public ArrayList getParcelableArrayListExtra(String name) { + return mExtras == null ? null : mExtras.getParcelableArrayList(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no Serializable value was found. + * + * @see #putExtra(String, Serializable) + */ + public Serializable getSerializableExtra(String name) { + return mExtras == null ? null : mExtras.getSerializable(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no ArrayList value was found. + * + * @see #putIntegerArrayListExtra(String, ArrayList) + */ + public ArrayList getIntegerArrayListExtra(String name) { + return mExtras == null ? null : mExtras.getIntegerArrayList(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no ArrayList value was found. + * + * @see #putStringArrayListExtra(String, ArrayList) + */ + public ArrayList getStringArrayListExtra(String name) { + return mExtras == null ? null : mExtras.getStringArrayList(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no ArrayList value was found. + * + * @see #putCharSequenceArrayListExtra(String, ArrayList) + */ + public ArrayList getCharSequenceArrayListExtra(String name) { + return mExtras == null ? null : mExtras.getCharSequenceArrayList(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no boolean array value was found. + * + * @see #putExtra(String, boolean[]) + */ + public boolean[] getBooleanArrayExtra(String name) { + return mExtras == null ? null : mExtras.getBooleanArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no byte array value was found. + * + * @see #putExtra(String, byte[]) + */ + public byte[] getByteArrayExtra(String name) { + return mExtras == null ? null : mExtras.getByteArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no short array value was found. + * + * @see #putExtra(String, short[]) + */ + public short[] getShortArrayExtra(String name) { + return mExtras == null ? null : mExtras.getShortArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no char array value was found. + * + * @see #putExtra(String, char[]) + */ + public char[] getCharArrayExtra(String name) { + return mExtras == null ? null : mExtras.getCharArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no int array value was found. + * + * @see #putExtra(String, int[]) + */ + public int[] getIntArrayExtra(String name) { + return mExtras == null ? null : mExtras.getIntArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no long array value was found. + * + * @see #putExtra(String, long[]) + */ + public long[] getLongArrayExtra(String name) { + return mExtras == null ? null : mExtras.getLongArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no float array value was found. + * + * @see #putExtra(String, float[]) + */ + public float[] getFloatArrayExtra(String name) { + return mExtras == null ? null : mExtras.getFloatArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no double array value was found. + * + * @see #putExtra(String, double[]) + */ + public double[] getDoubleArrayExtra(String name) { + return mExtras == null ? null : mExtras.getDoubleArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no String array value was found. + * + * @see #putExtra(String, String[]) + */ + public String[] getStringArrayExtra(String name) { + return mExtras == null ? null : mExtras.getStringArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no CharSequence array value was found. + * + * @see #putExtra(String, CharSequence[]) + */ + public CharSequence[] getCharSequenceArrayExtra(String name) { + return mExtras == null ? null : mExtras.getCharSequenceArray(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * + * @return the value of an item that previously added with putExtra() + * or null if no Bundle value was found. + * + * @see #putExtra(String, Bundle) + */ + public Bundle getBundleExtra(String name) { + return mExtras == null ? null : mExtras.getBundle(name); + } + /** + * Retrieve extended data from the intent. + * + * @param name The name of the desired item. + * @param defaultValue The default value to return in case no item is + * associated with the key 'name' + * + * @return the value of an item that previously added with putExtra() + * or defaultValue if none was found. + * + * @see #putExtra + * + * @deprecated + * @hide + */ + @Deprecated + public Object getExtra(String name, Object defaultValue) { + Object result = defaultValue; + if (mExtras != null) { + Object result2 = mExtras.get(name); + if (result2 != null) { + result = result2; + } + } + return result; + } + /** + * Retrieves a map of extended data from the intent. + * + * @return the map of all extras previously added with putExtra(), + * or null if none have been added. + */ + public Bundle getExtras() { + return (mExtras != null) + ? new Bundle(mExtras) + : null; + } + /** + * Retrieve any special flags associated with this intent. You will + * normally just set them with {@link #setFlags} and let the system + * take the appropriate action with them. + * + * @return int The currently set flags. + * + * @see #setFlags + */ + public int getFlags() { + return mFlags; + } + /** @hide */ + public boolean isExcludingStopped() { + return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES)) + == FLAG_EXCLUDE_STOPPED_PACKAGES; + } + /** + * Retrieve the application package name this Intent is limited to. When + * resolving an Intent, if non-null this limits the resolution to only + * components in the given application package. + * + * @return The name of the application package for the Intent. + * + * @see #resolveActivity + * @see #setPackage + */ + public String getPackage() { + return mPackage; + } + /** + * Retrieve the concrete component associated with the intent. When receiving + * an intent, this is the component that was found to best handle it (that is, + * yourself) and will always be non-null; in all other cases it will be + * null unless explicitly set. + * + * @return The name of the application component to handle the intent. + * + * @see #resolveActivity + * @see #setComponent + */ + public ComponentName getComponent() { + return mComponent; + } + /** + * Get the bounds of the sender of this intent, in screen coordinates. This can be + * used as a hint to the receiver for animations and the like. Null means that there + * is no source bounds. + */ + public Rect getSourceBounds() { + return mSourceBounds; + } + /** + * Return the Activity component that should be used to handle this intent. + * The appropriate component is determined based on the information in the + * intent, evaluated as follows: + * + *

If {@link #getComponent} returns an explicit class, that is returned + * without any further consideration. + * + *

The activity must handle the {@link Intent#CATEGORY_DEFAULT} Intent + * category to be considered. + * + *

If {@link #getAction} is non-NULL, the activity must handle this + * action. + * + *

If {@link #resolveType} returns non-NULL, the activity must handle + * this type. + * + *

If {@link #addCategory} has added any categories, the activity must + * handle ALL of the categories specified. + * + *

If {@link #getPackage} is non-NULL, only activity components in + * that application package will be considered. + * + *

If there are no activities that satisfy all of these conditions, a + * null string is returned. + * + *

If multiple activities are found to satisfy the intent, the one with + * the highest priority will be used. If there are multiple activities + * with the same priority, the system will either pick the best activity + * based on user preference, or resolve to a system class that will allow + * the user to pick an activity and forward from there. + * + *

This method is implemented simply by calling + * {@link PackageManager#resolveActivity} with the "defaultOnly" parameter + * true.

+ *

This API is called for you as part of starting an activity from an + * intent. You do not normally need to call it yourself.

+ * + * @param pm The package manager with which to resolve the Intent. + * + * @return Name of the component implementing an activity that can + * display the intent. + * + * @see #setComponent + * @see #getComponent + * @see #resolveActivityInfo + */ + public ComponentName resolveActivity(PackageManager pm) { + if (mComponent != null) { + return mComponent; + } + ResolveInfo info = pm.resolveActivity( + this, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + } + return null; + } + /** + * Resolve the Intent into an {@link ActivityInfo} + * describing the activity that should execute the intent. Resolution + * follows the same rules as described for {@link #resolveActivity}, but + * you get back the completely information about the resolved activity + * instead of just its class name. + * + * @param pm The package manager with which to resolve the Intent. + * @param flags Addition information to retrieve as per + * {@link PackageManager#getActivityInfo(ComponentName, int) + * PackageManager.getActivityInfo()}. + * + * @return PackageManager.ActivityInfo + * + * @see #resolveActivity + */ + public ActivityInfo resolveActivityInfo(PackageManager pm, int flags) { + ActivityInfo ai = null; + if (mComponent != null) { + try { + ai = pm.getActivityInfo(mComponent, flags); + } catch (PackageManager.NameNotFoundException e) { + // ignore + } + } else { + ResolveInfo info = pm.resolveActivity( + this, PackageManager.MATCH_DEFAULT_ONLY | flags); + if (info != null) { + ai = info.activityInfo; + } + } + return ai; + } + /** + * Special function for use by the system to resolve service + * intents to system apps. Throws an exception if there are + * multiple potential matches to the Intent. Returns null if + * there are no matches. + * @hide + */ + public ComponentName resolveSystemService(PackageManager pm, int flags) { + if (mComponent != null) { + return mComponent; + } + List results = pm.queryIntentServices(this, flags); + if (results == null) { + return null; + } + ComponentName comp = null; + for (int i=0; iNote: scheme matching in the Android framework is + * case-sensitive, unlike the formal RFC. As a result, + * you should always write your Uri with a lower case scheme, + * or use {@link Uri#normalizeScheme} or + * {@link #setDataAndNormalize} + * to ensure that the scheme is converted to lower case. + * + * @param data The Uri of the data this intent is now targeting. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getData + * @see #setDataAndNormalize + * @see android.net.Uri#normalizeScheme() + */ + public Intent setData(Uri data) { + mData = data; + mType = null; + return this; + } + /** + * Normalize and set the data this intent is operating on. + * + *

This method automatically clears any type that was + * previously set (for example, by {@link #setType}). + * + *

The data Uri is normalized using + * {@link android.net.Uri#normalizeScheme} before it is set, + * so really this is just a convenience method for + *

+     * setData(data.normalize())
+     * 
+ * + * @param data The Uri of the data this intent is now targeting. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getData + * @see #setType + * @see android.net.Uri#normalizeScheme + */ + public Intent setDataAndNormalize(Uri data) { + return setData(data.normalizeScheme()); + } + /** + * Set an explicit MIME data type. + * + *

This is used to create intents that only specify a type and not data, + * for example to indicate the type of data to return. + * + *

This method automatically clears any data that was + * previously set (for example by {@link #setData}). + * + *

Note: MIME type matching in the Android framework is + * case-sensitive, unlike formal RFC MIME types. As a result, + * you should always write your MIME types with lower case letters, + * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize} + * to ensure that it is converted to lower case. + * + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getType + * @see #setTypeAndNormalize + * @see #setDataAndType + * @see #normalizeMimeType + */ + public Intent setType(String type) { + mData = null; + mType = type; + return this; + } + /** + * Normalize and set an explicit MIME data type. + * + *

This is used to create intents that only specify a type and not data, + * for example to indicate the type of data to return. + * + *

This method automatically clears any data that was + * previously set (for example by {@link #setData}). + * + *

The MIME type is normalized using + * {@link #normalizeMimeType} before it is set, + * so really this is just a convenience method for + *

+     * setType(Intent.normalizeMimeType(type))
+     * 
+ * + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getType + * @see #setData + * @see #normalizeMimeType + */ + public Intent setTypeAndNormalize(String type) { + return setType(normalizeMimeType(type)); + } + /** + * (Usually optional) Set the data for the intent along with an explicit + * MIME data type. This method should very rarely be used -- it allows you + * to override the MIME type that would ordinarily be inferred from the + * data with your own type given here. + * + *

Note: MIME type and Uri scheme matching in the + * Android framework is case-sensitive, unlike the formal RFC definitions. + * As a result, you should always write these elements with lower case letters, + * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalizeScheme} or + * {@link #setDataAndTypeAndNormalize} + * to ensure that they are converted to lower case. + * + * @param data The Uri of the data this intent is now targeting. + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setType + * @see #setData + * @see #normalizeMimeType + * @see android.net.Uri#normalizeScheme + * @see #setDataAndTypeAndNormalize + */ + public Intent setDataAndType(Uri data, String type) { + mData = data; + mType = type; + return this; + } + /** + * (Usually optional) Normalize and set both the data Uri and an explicit + * MIME data type. This method should very rarely be used -- it allows you + * to override the MIME type that would ordinarily be inferred from the + * data with your own type given here. + * + *

The data Uri and the MIME type are normalize using + * {@link android.net.Uri#normalizeScheme} and {@link #normalizeMimeType} + * before they are set, so really this is just a convenience method for + *

+     * setDataAndType(data.normalize(), Intent.normalizeMimeType(type))
+     * 
+ * + * @param data The Uri of the data this intent is now targeting. + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setType + * @see #setData + * @see #setDataAndType + * @see #normalizeMimeType + * @see android.net.Uri#normalizeScheme + */ + public Intent setDataAndTypeAndNormalize(Uri data, String type) { + return setDataAndType(data.normalizeScheme(), normalizeMimeType(type)); + } + /** + * Add a new category to the intent. Categories provide additional detail + * about the action the intent performs. When resolving an intent, only + * activities that provide all of the requested categories will be + * used. + * + * @param category The desired category. This can be either one of the + * predefined Intent categories, or a custom category in your own + * namespace. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #hasCategory + * @see #removeCategory + */ + public Intent addCategory(String category) { + if (mCategories == null) { + mCategories = new ArraySet(); + } + mCategories.add(category.intern()); + return this; + } + /** + * Remove a category from an intent. + * + * @param category The category to remove. + * + * @see #addCategory + */ + public void removeCategory(String category) { + if (mCategories != null) { + mCategories.remove(category); + if (mCategories.size() == 0) { + mCategories = null; + } + } + } + /** + * Set a selector for this Intent. This is a modification to the kinds of + * things the Intent will match. If the selector is set, it will be used + * when trying to find entities that can handle the Intent, instead of the + * main contents of the Intent. This allows you build an Intent containing + * a generic protocol while targeting it more specifically. + * + *

An example of where this may be used is with things like + * {@link #CATEGORY_APP_BROWSER}. This category allows you to build an + * Intent that will launch the Browser application. However, the correct + * main entry point of an application is actually {@link #ACTION_MAIN} + * {@link #CATEGORY_LAUNCHER} with {@link #setComponent(ComponentName)} + * used to specify the actual Activity to launch. If you launch the browser + * with something different, undesired behavior may happen if the user has + * previously or later launches it the normal way, since they do not match. + * Instead, you can build an Intent with the MAIN action (but no ComponentName + * yet specified) and set a selector with {@link #ACTION_MAIN} and + * {@link #CATEGORY_APP_BROWSER} to point it specifically to the browser activity. + * + *

Setting a selector does not impact the behavior of + * {@link #filterEquals(Intent)} and {@link #filterHashCode()}. This is part of the + * desired behavior of a selector -- it does not impact the base meaning + * of the Intent, just what kinds of things will be matched against it + * when determining who can handle it.

+ * + *

You can not use both a selector and {@link #setPackage(String)} on + * the same base Intent.

+ * + * @param selector The desired selector Intent; set to null to not use + * a special selector. + */ + public void setSelector(Intent selector) { + if (selector == this) { + throw new IllegalArgumentException( + "Intent being set as a selector of itself"); + } + if (selector != null && mPackage != null) { + throw new IllegalArgumentException( + "Can't set selector when package name is already set"); + } + mSelector = selector; + } + /** + * Set a {@link ClipData} associated with this Intent. This replaces any + * previously set ClipData. + * + *

The ClipData in an intent is not used for Intent matching or other + * such operations. Semantically it is like extras, used to transmit + * additional data with the Intent. The main feature of using this over + * the extras for data is that {@link #FLAG_GRANT_READ_URI_PERMISSION} + * and {@link #FLAG_GRANT_WRITE_URI_PERMISSION} will operate on any URI + * items included in the clip data. This is useful, in particular, if + * you want to transmit an Intent containing multiple content: + * URIs for which the recipient may not have global permission to access the + * content provider. + * + *

If the ClipData contains items that are themselves Intents, any + * grant flags in those Intents will be ignored. Only the top-level flags + * of the main Intent are respected, and will be applied to all Uri or + * Intent items in the clip (or sub-items of the clip). + * + *

The MIME type, label, and icon in the ClipData object are not + * directly used by Intent. Applications should generally rely on the + * MIME type of the Intent itself, not what it may find in the ClipData. + * A common practice is to construct a ClipData for use with an Intent + * with a MIME type of "*/*". + * + * @param clip The new clip to set. May be null to clear the current clip. + */ + public void setClipData(ClipData clip) { + mClipData = clip; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The boolean data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getBooleanExtra(String, boolean) + */ + public Intent putExtra(String name, boolean value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putBoolean(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The byte data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getByteExtra(String, byte) + */ + public Intent putExtra(String name, byte value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putByte(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The char data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getCharExtra(String, char) + */ + public Intent putExtra(String name, char value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putChar(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The short data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getShortExtra(String, short) + */ + public Intent putExtra(String name, short value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putShort(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The integer data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getIntExtra(String, int) + */ + public Intent putExtra(String name, int value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putInt(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The long data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getLongExtra(String, long) + */ + public Intent putExtra(String name, long value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putLong(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The float data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getFloatExtra(String, float) + */ + public Intent putExtra(String name, float value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putFloat(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The double data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getDoubleExtra(String, double) + */ + public Intent putExtra(String name, double value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putDouble(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The String data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getStringExtra(String) + */ + public Intent putExtra(String name, String value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putString(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The CharSequence data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getCharSequenceExtra(String) + */ + public Intent putExtra(String name, CharSequence value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putCharSequence(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The Parcelable data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getParcelableExtra(String) + */ + public Intent putExtra(String name, Parcelable value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putParcelable(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The Parcelable[] data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getParcelableArrayExtra(String) + */ + public Intent putExtra(String name, Parcelable[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putParcelableArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The ArrayList data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getParcelableArrayListExtra(String) + */ + public Intent putParcelableArrayListExtra(String name, ArrayList value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putParcelableArrayList(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The ArrayList data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getIntegerArrayListExtra(String) + */ + public Intent putIntegerArrayListExtra(String name, ArrayList value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putIntegerArrayList(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The ArrayList data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getStringArrayListExtra(String) + */ + public Intent putStringArrayListExtra(String name, ArrayList value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putStringArrayList(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The ArrayList data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getCharSequenceArrayListExtra(String) + */ + public Intent putCharSequenceArrayListExtra(String name, ArrayList value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putCharSequenceArrayList(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The Serializable data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getSerializableExtra(String) + */ + public Intent putExtra(String name, Serializable value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putSerializable(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The boolean array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getBooleanArrayExtra(String) + */ + public Intent putExtra(String name, boolean[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putBooleanArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The byte array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getByteArrayExtra(String) + */ + public Intent putExtra(String name, byte[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putByteArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The short array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getShortArrayExtra(String) + */ + public Intent putExtra(String name, short[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putShortArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The char array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getCharArrayExtra(String) + */ + public Intent putExtra(String name, char[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putCharArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The int array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getIntArrayExtra(String) + */ + public Intent putExtra(String name, int[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putIntArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The byte array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getLongArrayExtra(String) + */ + public Intent putExtra(String name, long[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putLongArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The float array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getFloatArrayExtra(String) + */ + public Intent putExtra(String name, float[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putFloatArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The double array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getDoubleArrayExtra(String) + */ + public Intent putExtra(String name, double[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putDoubleArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The String array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getStringArrayExtra(String) + */ + public Intent putExtra(String name, String[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putStringArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The CharSequence array data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getCharSequenceArrayExtra(String) + */ + public Intent putExtra(String name, CharSequence[] value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putCharSequenceArray(name, value); + return this; + } + /** + * Add extended data to the intent. The name must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param name The name of the extra data, with package prefix. + * @param value The Bundle data value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #putExtras + * @see #removeExtra + * @see #getBundleExtra(String) + */ + public Intent putExtra(String name, Bundle value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putBundle(name, value); + return this; + } + /** + * Copy all extras in 'src' in to this intent. + * + * @param src Contains the extras to copy. + * + * @see #putExtra + */ + public Intent putExtras(Intent src) { + if (src.mExtras != null) { + if (mExtras == null) { + mExtras = new Bundle(src.mExtras); + } else { + mExtras.putAll(src.mExtras); + } + } + return this; + } + /** + * Add a set of extended data to the intent. The keys must include a package + * prefix, for example the app com.android.contacts would use names + * like "com.android.contacts.ShowAll". + * + * @param extras The Bundle of extras to add to this intent. + * + * @see #putExtra + * @see #removeExtra + */ + public Intent putExtras(Bundle extras) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putAll(extras); + return this; + } + /** + * Completely replace the extras in the Intent with the extras in the + * given Intent. + * + * @param src The exact extras contained in this Intent are copied + * into the target intent, replacing any that were previously there. + */ + public Intent replaceExtras(Intent src) { + mExtras = src.mExtras != null ? new Bundle(src.mExtras) : null; + return this; + } + /** + * Completely replace the extras in the Intent with the given Bundle of + * extras. + * + * @param extras The new set of extras in the Intent, or null to erase + * all extras. + */ + public Intent replaceExtras(Bundle extras) { + mExtras = extras != null ? new Bundle(extras) : null; + return this; + } + /** + * Remove extended data from the intent. + * + * @see #putExtra + */ + public void removeExtra(String name) { + if (mExtras != null) { + mExtras.remove(name); + if (mExtras.size() == 0) { + mExtras = null; + } + } + } + /** + * Set special flags controlling how this intent is handled. Most values + * here depend on the type of component being executed by the Intent, + * specifically the FLAG_ACTIVITY_* flags are all for use with + * {@link Context#startActivity Context.startActivity()} and the + * FLAG_RECEIVER_* flags are all for use with + * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}. + * + *

See the + * Tasks and Back + * Stack documentation for important information on how some of these options impact + * the behavior of your application. + * + * @param flags The desired flags. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getFlags + * @see #addFlags + * + * @see #FLAG_GRANT_READ_URI_PERMISSION + * @see #FLAG_GRANT_WRITE_URI_PERMISSION + * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION + * @see #FLAG_GRANT_PREFIX_URI_PERMISSION + * @see #FLAG_DEBUG_LOG_RESOLUTION + * @see #FLAG_FROM_BACKGROUND + * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT + * @see #FLAG_ACTIVITY_CLEAR_TASK + * @see #FLAG_ACTIVITY_CLEAR_TOP + * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET + * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + * @see #FLAG_ACTIVITY_FORWARD_RESULT + * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + * @see #FLAG_ACTIVITY_MULTIPLE_TASK + * @see #FLAG_ACTIVITY_NEW_DOCUMENT + * @see #FLAG_ACTIVITY_NEW_TASK + * @see #FLAG_ACTIVITY_NO_ANIMATION + * @see #FLAG_ACTIVITY_NO_HISTORY + * @see #FLAG_ACTIVITY_NO_USER_ACTION + * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP + * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + * @see #FLAG_ACTIVITY_REORDER_TO_FRONT + * @see #FLAG_ACTIVITY_SINGLE_TOP + * @see #FLAG_ACTIVITY_TASK_ON_HOME + * @see #FLAG_RECEIVER_REGISTERED_ONLY + */ + public Intent setFlags(int flags) { + mFlags = flags; + return this; + } + /** + * Add additional flags to the intent (or with existing flags + * value). + * + * @param flags The new flags to set. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setFlags + */ + public Intent addFlags(int flags) { + mFlags |= flags; + return this; + } + /** + * (Usually optional) Set an explicit application package name that limits + * the components this Intent will resolve to. If left to the default + * value of null, all components in all applications will considered. + * If non-null, the Intent can only match the components in the given + * application package. + * + * @param packageName The name of the application package to handle the + * intent, or null to allow any application package. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getPackage + * @see #resolveActivity + */ + public Intent setPackage(String packageName) { + if (packageName != null && mSelector != null) { + throw new IllegalArgumentException( + "Can't set package name when selector is already set"); + } + mPackage = packageName; + return this; + } + /** + * (Usually optional) Explicitly set the component to handle the intent. + * If left with the default value of null, the system will determine the + * appropriate class to use based on the other fields (action, data, + * type, categories) in the Intent. If this class is defined, the + * specified class will always be used regardless of the other fields. You + * should only set this value when you know you absolutely want a specific + * class to be used; otherwise it is better to let the system find the + * appropriate class so that you will respect the installed applications + * and user preferences. + * + * @param component The name of the application component to handle the + * intent, or null to let the system find one for you. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setClass + * @see #setClassName(Context, String) + * @see #setClassName(String, String) + * @see #getComponent + * @see #resolveActivity + */ + public Intent setComponent(ComponentName component) { + mComponent = component; + return this; + } + /** + * Convenience for calling {@link #setComponent} with an + * explicit class name. + * + * @param packageContext A Context of the application package implementing + * this class. + * @param className The name of a class inside of the application package + * that will be used as the component for this Intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setComponent + * @see #setClass + */ + public Intent setClassName(Context packageContext, String className) { + mComponent = new ComponentName(packageContext, className); + return this; + } + /** + * Convenience for calling {@link #setComponent} with an + * explicit application package name and class name. + * + * @param packageName The name of the package implementing the desired + * component. + * @param className The name of a class inside of the application package + * that will be used as the component for this Intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setComponent + * @see #setClass + */ + public Intent setClassName(String packageName, String className) { + mComponent = new ComponentName(packageName, className); + return this; + } + /** + * Convenience for calling {@link #setComponent(ComponentName)} with the + * name returned by a {@link Class} object. + * + * @param packageContext A Context of the application package implementing + * this class. + * @param cls The class name to set, equivalent to + * setClassName(context, cls.getName()). + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setComponent + */ + public Intent setClass(Context packageContext, Class cls) { + mComponent = new ComponentName(packageContext, cls); + return this; + } + /** + * Set the bounds of the sender of this intent, in screen coordinates. This can be + * used as a hint to the receiver for animations and the like. Null means that there + * is no source bounds. + */ + public void setSourceBounds(Rect r) { + if (r != null) { + mSourceBounds = new Rect(r); + } else { + mSourceBounds = null; + } + } + /** @hide */ + @IntDef(flag = true, + value = { + FILL_IN_ACTION, + FILL_IN_DATA, + FILL_IN_CATEGORIES, + FILL_IN_COMPONENT, + FILL_IN_PACKAGE, + FILL_IN_SOURCE_BOUNDS, + FILL_IN_SELECTOR, + FILL_IN_CLIP_DATA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FillInFlags {} + /** + * Use with {@link #fillIn} to allow the current action value to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_ACTION = 1<<0; + /** + * Use with {@link #fillIn} to allow the current data or type value + * overwritten, even if it is already set. + */ + public static final int FILL_IN_DATA = 1<<1; + /** + * Use with {@link #fillIn} to allow the current categories to be + * overwritten, even if they are already set. + */ + public static final int FILL_IN_CATEGORIES = 1<<2; + /** + * Use with {@link #fillIn} to allow the current component value to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_COMPONENT = 1<<3; + /** + * Use with {@link #fillIn} to allow the current package value to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_PACKAGE = 1<<4; + /** + * Use with {@link #fillIn} to allow the current bounds rectangle to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_SOURCE_BOUNDS = 1<<5; + /** + * Use with {@link #fillIn} to allow the current selector to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_SELECTOR = 1<<6; + /** + * Use with {@link #fillIn} to allow the current ClipData to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_CLIP_DATA = 1<<7; + /** + * Wrapper class holding an Intent and implementing comparisons on it for + * the purpose of filtering. The class implements its + * {@link #equals equals()} and {@link #hashCode hashCode()} methods as + * simple calls to {@link Intent#filterEquals(Intent)} filterEquals()} and + * {@link android.content.Intent#filterHashCode()} filterHashCode()} + * on the wrapped Intent. + */ + public static final class FilterComparison { + private final Intent mIntent; + private final int mHashCode; + public FilterComparison(Intent intent) { + mIntent = intent; + mHashCode = intent.filterHashCode(); + } + /** + * Return the Intent that this FilterComparison represents. + * @return Returns the Intent held by the FilterComparison. Do + * not modify! + */ + public Intent getIntent() { + return mIntent; + } + @Override + public boolean equals(Object obj) { + if (obj instanceof FilterComparison) { + Intent other = ((FilterComparison)obj).mIntent; + return mIntent.filterEquals(other); + } + return false; + } + @Override + public int hashCode() { + return mHashCode; + } + } + /** + * Determine if two intents are the same for the purposes of intent + * resolution (filtering). That is, if their action, data, type, + * class, and categories are the same. This does not compare + * any extra data included in the intents. + * + * @param other The other Intent to compare against. + * + * @return Returns true if action, data, type, class, and categories + * are the same. + */ + public boolean filterEquals(Intent other) { + if (other == null) { + return false; + } + if (!Objects.equals(this.mAction, other.mAction)) return false; + if (!Objects.equals(this.mData, other.mData)) return false; + if (!Objects.equals(this.mType, other.mType)) return false; + if (!Objects.equals(this.mPackage, other.mPackage)) return false; + if (!Objects.equals(this.mComponent, other.mComponent)) return false; + if (!Objects.equals(this.mCategories, other.mCategories)) return false; + return true; + } + /** + * Generate hash code that matches semantics of filterEquals(). + * + * @return Returns the hash value of the action, data, type, class, and + * categories. + * + * @see #filterEquals + */ + public int filterHashCode() { + int code = 0; + if (mAction != null) { + code += mAction.hashCode(); + } + if (mData != null) { + code += mData.hashCode(); + } + if (mType != null) { + code += mType.hashCode(); + } + if (mPackage != null) { + code += mPackage.hashCode(); + } + if (mComponent != null) { + code += mComponent.hashCode(); + } + if (mCategories != null) { + code += mCategories.hashCode(); + } + return code; + } + @Override + public String toString() { + StringBuilder b = new StringBuilder(128); + b.append("Intent { "); + toShortString(b, true, true, true, false); + b.append(" }"); + return b.toString(); + } + /** @hide */ + public String toInsecureString() { + StringBuilder b = new StringBuilder(128); + b.append("Intent { "); + toShortString(b, false, true, true, false); + b.append(" }"); + return b.toString(); + } + /** @hide */ + public String toInsecureStringWithClip() { + StringBuilder b = new StringBuilder(128); + b.append("Intent { "); + toShortString(b, false, true, true, true); + b.append(" }"); + return b.toString(); + } + /** @hide */ + public String toShortString(boolean secure, boolean comp, boolean extras, boolean clip) { + StringBuilder b = new StringBuilder(128); + toShortString(b, secure, comp, extras, clip); + return b.toString(); + } + /** @hide */ + public void toShortString(StringBuilder b, boolean secure, boolean comp, boolean extras, + boolean clip) { + boolean first = true; + if (mAction != null) { + b.append("act=").append(mAction); + first = false; + } + if (mCategories != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("cat=["); + for (int i=0; i 0) b.append(','); + b.append(mCategories.valueAt(i)); + } + b.append("]"); + } + if (mData != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("dat="); + if (secure) { + b.append(mData.toSafeString()); + } else { + b.append(mData); + } + } + if (mType != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("typ=").append(mType); + } + if (mFlags != 0) { + if (!first) { + b.append(' '); + } + first = false; + b.append("flg=0x").append(Integer.toHexString(mFlags)); + } + if (mPackage != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("pkg=").append(mPackage); + } + if (comp && mComponent != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("cmp=").append(mComponent.flattenToShortString()); + } + if (mSourceBounds != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("bnds=").append(mSourceBounds.toShortString()); + } + if (extras && mExtras != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("(has extras)"); + } + if (mSelector != null) { + b.append(" sel="); + mSelector.toShortString(b, secure, comp, extras, clip); + b.append("}"); + } + } + /** + * Call {@link #toUri} with 0 flags. + * @deprecated Use {@link #toUri} instead. + */ + @Deprecated + public String toURI() { + return toUri(0); + } + /** + * Convert this Intent into a String holding a URI representation of it. + * The returned URI string has been properly URI encoded, so it can be + * used with {@link Uri#parse Uri.parse(String)}. The URI contains the + * Intent's data as the base URI, with an additional fragment describing + * the action, categories, type, flags, package, component, and extras. + * + *

You can convert the returned string back to an Intent with + * {@link #getIntent}. + * + * @param flags Additional operating flags. Either 0, + * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}. + * + * @return Returns a URI encoding URI string describing the entire contents + * of the Intent. + */ + public String toUri(int flags) { + StringBuilder uri = new StringBuilder(128); + if ((flags&URI_ANDROID_APP_SCHEME) != 0) { + if (mPackage == null) { + throw new IllegalArgumentException( + "Intent must include an explicit package name to build an android-app: " + + this); + } + uri.append("android-app://"); + uri.append(mPackage); + String scheme = null; + if (mData != null) { + scheme = mData.getScheme(); + if (scheme != null) { + uri.append('/'); + uri.append(scheme); + String authority = mData.getEncodedAuthority(); + if (authority != null) { + uri.append('/'); + uri.append(authority); + String path = mData.getEncodedPath(); + if (path != null) { + uri.append(path); + } + String queryParams = mData.getEncodedQuery(); + if (queryParams != null) { + uri.append('?'); + uri.append(queryParams); + } + String fragment = mData.getEncodedFragment(); + if (fragment != null) { + uri.append('#'); + uri.append(fragment); + } + } + } + } + toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW, + mPackage, flags); + return uri.toString(); + } + String scheme = null; + if (mData != null) { + String data = mData.toString(); + if ((flags&URI_INTENT_SCHEME) != 0) { + final int N = data.length(); + for (int i=0; i= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || c == '.' || c == '-') { + continue; + } + if (c == ':' && i > 0) { + // Valid scheme. + scheme = data.substring(0, i); + uri.append("intent:"); + data = data.substring(i+1); + break; + } + // No scheme. + break; + } + } + uri.append(data); + } else if ((flags&URI_INTENT_SCHEME) != 0) { + uri.append("intent:"); + } + toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags); + return uri.toString(); + } + private void toUriFragment(StringBuilder uri, String scheme, String defAction, + String defPackage, int flags) { + StringBuilder frag = new StringBuilder(128); + toUriInner(frag, scheme, defAction, defPackage, flags); + if (mSelector != null) { + frag.append("SEL;"); + // Note that for now we are not going to try to handle the + // data part; not clear how to represent this as a URI, and + // not much utility in it. + mSelector.toUriInner(frag, mSelector.mData != null ? mSelector.mData.getScheme() : null, + null, null, flags); + } + if (frag.length() > 0) { + uri.append("#Intent;"); + uri.append(frag); + uri.append("end"); + } + } + private void toUriInner(StringBuilder uri, String scheme, String defAction, + String defPackage, int flags) { + if (scheme != null) { + uri.append("scheme=").append(scheme).append(';'); + } + if (mAction != null && !mAction.equals(defAction)) { + uri.append("action=").append(Uri.encode(mAction)).append(';'); + } + if (mCategories != null) { + for (int i=0; iAll MIME types received from outside Android (such as user input, + * or external sources like Bluetooth, NFC, or the Internet) should + * be normalized before they are used to create an Intent. + * + * @param type MIME data type to normalize + * @return normalized MIME data type, or null if the input was null + * @see #setType + * @see #setTypeAndNormalize + */ + public static String normalizeMimeType(String type) { + if (type == null) { + return null; + } + type = type.trim().toLowerCase(Locale.ROOT); + final int semicolonIndex = type.indexOf(';'); + if (semicolonIndex != -1) { + type = type.substring(0, semicolonIndex); + } + return type; + } + private static ClipData.Item makeClipItem(ArrayList streams, ArrayList texts, + ArrayList htmlTexts, int which) { + Uri uri = streams != null ? streams.get(which) : null; + CharSequence text = texts != null ? texts.get(which) : null; + String htmlText = htmlTexts != null ? htmlTexts.get(which) : null; + return new ClipData.Item(text, htmlText, null, uri); + } + /** @hide */ + public boolean isDocument() { + return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/SharedPreferences.java b/AndroidCompat/src/main/java/android/content/SharedPreferences.java new file mode 100644 index 00000000..ec663e84 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/SharedPreferences.java @@ -0,0 +1,370 @@ +/* + * 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 android.content; +import android.annotation.Nullable; +import java.util.Map; +import java.util.Set; +/** + * Interface for accessing and modifying preference data returned by {@link + * Context#getSharedPreferences}. For any particular set of preferences, + * there is a single instance of this class that all clients share. + * Modifications to the preferences must go through an {@link Editor} object + * to ensure the preference values remain in a consistent state and control + * when they are committed to storage. Objects that are returned from the + * various get methods must be treated as immutable by the application. + * + *

Note: This class does not support use across multiple processes. + * + *

+ *

Developer Guides

+ *

For more information about using SharedPreferences, read the + * Data Storage + * developer guide.

+ * + * @see Context#getSharedPreferences + */ +public interface SharedPreferences { + /** + * Interface definition for a callback to be invoked when a shared + * preference is changed. + */ + public interface OnSharedPreferenceChangeListener { + /** + * Called when a shared preference is changed, added, or removed. This + * may be called even if a preference is set to its existing value. + * + *

This callback will be run on your main thread. + * + * @param sharedPreferences The {@link SharedPreferences} that received + * the change. + * @param key The key of the preference that was changed, added, or + * removed. + */ + void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); + } + + /** + * Interface used for modifying values in a {@link SharedPreferences} + * object. All changes you make in an editor are batched, and not copied + * back to the original {@link SharedPreferences} until you call {@link #commit} + * or {@link #apply} + */ + public interface Editor { + /** + * Set a String value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. Passing {@code null} + * for this argument is equivalent to calling {@link #remove(String)} with + * this key. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putString(String key, @Nullable String value); + + /** + * Set a set of String values in the preferences editor, to be written + * back once {@link #commit} or {@link #apply} is called. + * + * @param key The name of the preference to modify. + * @param values The set of new values for the preference. Passing {@code null} + * for this argument is equivalent to calling {@link #remove(String)} with + * this key. + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putStringSet(String key, @Nullable Set values); + + /** + * Set an int value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putInt(String key, int value); + + /** + * Set a long value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putLong(String key, long value); + + /** + * Set a float value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putFloat(String key, float value); + + /** + * Set a boolean value in the preferences editor, to be written back + * once {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putBoolean(String key, boolean value); + /** + * Mark in the editor that a preference value should be removed, which + * will be done in the actual preferences once {@link #commit} is + * called. + * + *

Note that when committing back to the preferences, all removals + * are done first, regardless of whether you called remove before + * or after put methods on this editor. + * + * @param key The name of the preference to remove. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor remove(String key); + /** + * Mark in the editor to remove all values from the + * preferences. Once commit is called, the only remaining preferences + * will be any that you have defined in this editor. + * + *

Note that when committing back to the preferences, the clear + * is done first, regardless of whether you called clear before + * or after put methods on this editor. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor clear(); + /** + * Commit your preferences changes back from this Editor to the + * {@link SharedPreferences} object it is editing. This atomically + * performs the requested modifications, replacing whatever is currently + * in the SharedPreferences. + * + *

Note that when two editors are modifying preferences at the same + * time, the last one to call commit wins. + * + *

If you don't care about the return value and you're + * using this from your application's main thread, consider + * using {@link #apply} instead. + * + * @return Returns true if the new values were successfully written + * to persistent storage. + */ + boolean commit(); + /** + * Commit your preferences changes back from this Editor to the + * {@link SharedPreferences} object it is editing. This atomically + * performs the requested modifications, replacing whatever is currently + * in the SharedPreferences. + * + *

Note that when two editors are modifying preferences at the same + * time, the last one to call apply wins. + * + *

Unlike {@link #commit}, which writes its preferences out + * to persistent storage synchronously, {@link #apply} + * commits its changes to the in-memory + * {@link SharedPreferences} immediately but starts an + * asynchronous commit to disk and you won't be notified of + * any failures. If another editor on this + * {@link SharedPreferences} does a regular {@link #commit} + * while a {@link #apply} is still outstanding, the + * {@link #commit} will block until all async commits are + * completed as well as the commit itself. + * + *

As {@link SharedPreferences} instances are singletons within + * a process, it's safe to replace any instance of {@link #commit} with + * {@link #apply} if you were already ignoring the return value. + * + *

You don't need to worry about Android component + * lifecycles and their interaction with apply() + * writing to disk. The framework makes sure in-flight disk + * writes from apply() complete before switching + * states. + * + *

The SharedPreferences.Editor interface + * isn't expected to be implemented directly. However, if you + * previously did implement it and are now getting errors + * about missing apply(), you can simply call + * {@link #commit} from apply(). + */ + void apply(); + } + /** + * Retrieve all values from the preferences. + * + *

Note that you must not modify the collection returned + * by this method, or alter any of its contents. The consistency of your + * stored data is not guaranteed if you do. + * + * @return Returns a map containing a list of pairs key/value representing + * the preferences. + * + * @throws NullPointerException + */ + Map getAll(); + /** + * Retrieve a String value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a String. + * + * @throws ClassCastException + */ + @Nullable + String getString(String key, @Nullable String defValue); + + /** + * Retrieve a set of String values from the preferences. + * + *

Note that you must not modify the set instance returned + * by this call. The consistency of the stored data is not guaranteed + * if you do, nor is your ability to modify the instance at all. + * + * @param key The name of the preference to retrieve. + * @param defValues Values to return if this preference does not exist. + * + * @return Returns the preference values if they exist, or defValues. + * Throws ClassCastException if there is a preference with this name + * that is not a Set. + * + * @throws ClassCastException + */ + @Nullable + Set getStringSet(String key, @Nullable Set defValues); + + /** + * Retrieve an int value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * an int. + * + * @throws ClassCastException + */ + int getInt(String key, int defValue); + + /** + * Retrieve a long value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a long. + * + * @throws ClassCastException + */ + long getLong(String key, long defValue); + + /** + * Retrieve a float value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a float. + * + * @throws ClassCastException + */ + float getFloat(String key, float defValue); + + /** + * Retrieve a boolean value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a boolean. + * + * @throws ClassCastException + */ + boolean getBoolean(String key, boolean defValue); + /** + * Checks whether the preferences contains a preference. + * + * @param key The name of the preference to check. + * @return Returns true if the preference exists in the preferences, + * otherwise false. + */ + boolean contains(String key); + + /** + * Create a new Editor for these preferences, through which you can make + * modifications to the data in the preferences and atomically commit those + * changes back to the SharedPreferences object. + * + *

Note that you must call {@link Editor#commit} to have any + * changes you perform in the Editor actually show up in the + * SharedPreferences. + * + * @return Returns a new instance of the {@link Editor} interface, allowing + * you to modify the values in this SharedPreferences object. + */ + Editor edit(); + + /** + * Registers a callback to be invoked when a change happens to a preference. + * + *

Caution: The preference manager does + * not currently store a strong reference to the listener. You must store a + * strong reference to the listener, or it will be susceptible to garbage + * collection. We recommend you keep a reference to the listener in the + * instance data of an object that will exist as long as you need the + * listener.

+ * + * @param listener The callback that will run. + * @see #unregisterOnSharedPreferenceChangeListener + */ + void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); + + /** + * Unregisters a previous callback. + * + * @param listener The callback that should be unregistered. + * @see #registerOnSharedPreferenceChangeListener + */ + void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/ApplicationInfo.java b/AndroidCompat/src/main/java/android/content/pm/ApplicationInfo.java new file mode 100644 index 00000000..d9b56163 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/ApplicationInfo.java @@ -0,0 +1,999 @@ +/* + * 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 android.content.pm; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.text.Collator; +import java.util.Comparator; +/** + * Information you can retrieve about a particular application. This + * corresponds to information collected from the AndroidManifest.xml's + * <application> tag. + */ +public class ApplicationInfo extends PackageItemInfo implements Parcelable { + + /** + * Default task affinity of all activities in this application. See + * {@link ActivityInfo#taskAffinity} for more information. This comes + * from the "taskAffinity" attribute. + */ + public String taskAffinity; + + /** + * Optional name of a permission required to be able to access this + * application's components. From the "permission" attribute. + */ + public String permission; + + /** + * The name of the process this application should run in. From the + * "process" attribute or, if not set, the same as + * packageName. + */ + public String processName; + + /** + * Class implementing the Application object. From the "class" + * attribute. + */ + public String className; + + /** + * A style resource identifier (in the package's resources) of the + * description of an application. From the "description" attribute + * or, if not set, 0. + */ + public int descriptionRes; + + /** + * A style resource identifier (in the package's resources) of the + * default visual theme of the application. From the "theme" attribute + * or, if not set, 0. + */ + public int theme; + + /** + * Class implementing the Application's manage space + * functionality. From the "manageSpaceActivity" + * attribute. This is an optional attribute and will be null if + * applications don't specify it in their manifest + */ + public String manageSpaceActivityName; + + /** + * Class implementing the Application's backup functionality. From + * the "backupAgent" attribute. This is an optional attribute and + * will be null if the application does not specify it in its manifest. + * + *

If android:allowBackup is set to false, this attribute is ignored. + */ + public String backupAgentName; + /** + * An optional attribute that indicates the app supports automatic backup of app data. + *

0 is the default and means the app's entire data folder + managed external storage will + * be backed up; + * Any negative value indicates the app does not support full-data backup, though it may still + * want to participate via the traditional key/value backup API; + * A positive number specifies an xml resource in which the application has defined its backup + * include/exclude criteria. + *

If android:allowBackup is set to false, this attribute is ignored. + * + * @see android.content.Context#getNoBackupFilesDir() + * @see #FLAG_ALLOW_BACKUP + * + * @hide + */ + public int fullBackupContent = 0; + /** + * The default extra UI options for activities in this application. + * Set from the {@link android.R.attr#uiOptions} attribute in the + * activity's manifest. + */ + public int uiOptions = 0; + /** + * Value for {@link #flags}: if set, this application is installed in the + * device's system image. + */ + public static final int FLAG_SYSTEM = 1<<0; + + /** + * Value for {@link #flags}: set to true if this application would like to + * allow debugging of its + * code, even when installed on a non-development system. Comes + * from {@link android.R.styleable#AndroidManifestApplication_debuggable + * android:debuggable} of the <application> tag. + */ + public static final int FLAG_DEBUGGABLE = 1<<1; + + /** + * Value for {@link #flags}: set to true if this application has code + * associated with it. Comes + * from {@link android.R.styleable#AndroidManifestApplication_hasCode + * android:hasCode} of the <application> tag. + */ + public static final int FLAG_HAS_CODE = 1<<2; + + /** + * Value for {@link #flags}: set to true if this application is persistent. + * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent + * android:persistent} of the <application> tag. + */ + public static final int FLAG_PERSISTENT = 1<<3; + /** + * Value for {@link #flags}: set to true if this application holds the + * {@link android.Manifest.permission#FACTORY_TEST} permission and the + * device is running in factory test mode. + */ + public static final int FLAG_FACTORY_TEST = 1<<4; + /** + * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. + * Comes from {@link android.R.styleable#AndroidManifestApplication_allowTaskReparenting + * android:allowTaskReparenting} of the <application> tag. + */ + public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5; + + /** + * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. + * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData + * android:allowClearUserData} of the <application> tag. + */ + public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6; + + /** + * Value for {@link #flags}: this is set if this application has been + * installed as an update to a built-in system application. + */ + public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; + + /** + * Value for {@link #flags}: this is set if the application has specified + * {@link android.R.styleable#AndroidManifestApplication_testOnly + * android:testOnly} to be true. + */ + public static final int FLAG_TEST_ONLY = 1<<8; + /** + * Value for {@link #flags}: true when the application's window can be + * reduced in size for smaller screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens + * android:smallScreens}. + */ + public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9; + + /** + * Value for {@link #flags}: true when the application's window can be + * displayed on normal screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens + * android:normalScreens}. + */ + public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10; + + /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for larger screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens + * android:largeScreens}. + */ + public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; + + /** + * Value for {@link #flags}: true when the application knows how to adjust + * its UI for different screen sizes. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_resizeable + * android:resizeable}. + */ + public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12; + + /** + * Value for {@link #flags}: true when the application knows how to + * accomodate different screen densities. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_anyDensity + * android:anyDensity}. + */ + public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13; + + /** + * Value for {@link #flags}: set to true if this application would like to + * request the VM to operate under the safe mode. Comes from + * {@link android.R.styleable#AndroidManifestApplication_vmSafeMode + * android:vmSafeMode} of the <application> tag. + */ + public static final int FLAG_VM_SAFE_MODE = 1<<14; + /** + * Value for {@link #flags}: set to false if the application does not wish + * to permit any OS-driven backups of its data; true otherwise. + * + *

Comes from the + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * attribute of the <application> tag. + */ + public static final int FLAG_ALLOW_BACKUP = 1<<15; + /** + * Value for {@link #flags}: set to false if the application must be kept + * in memory following a full-system restore operation; true otherwise. + * Ordinarily, during a full system restore operation each application is shut down + * following execution of its agent's onRestore() method. Setting this attribute to + * false prevents this. Most applications will not need to set this attribute. + * + *

If + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * is set to false or no + * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent} + * is specified, this flag will be ignored. + * + *

Comes from the + * {@link android.R.styleable#AndroidManifestApplication_killAfterRestore android:killAfterRestore} + * attribute of the <application> tag. + */ + public static final int FLAG_KILL_AFTER_RESTORE = 1<<16; + /** + * Value for {@link #flags}: Set to true if the application's backup + * agent claims to be able to handle restore data even "from the future," + * i.e. from versions of the application with a versionCode greater than + * the one currently installed on the device. Use with caution! By default + * this attribute is false and the Backup Manager will ensure that data + * from "future" versions of the application are never supplied during a restore operation. + * + *

If + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * is set to false or no + * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent} + * is specified, this flag will be ignored. + * + *

Comes from the + * {@link android.R.styleable#AndroidManifestApplication_restoreAnyVersion android:restoreAnyVersion} + * attribute of the <application> tag. + */ + public static final int FLAG_RESTORE_ANY_VERSION = 1<<17; + /** + * Value for {@link #flags}: Set to true if the application is + * currently installed on external/removable/unprotected storage. Such + * applications may not be available if their storage is not currently + * mounted. When the storage it is on is not available, it will look like + * the application has been uninstalled (its .apk is no longer available) + * but its persistent data is not removed. + */ + public static final int FLAG_EXTERNAL_STORAGE = 1<<18; + /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for extra large screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens + * android:xlargeScreens}. + */ + public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19; + + /** + * Value for {@link #flags}: true when the application has requested a + * large heap for its processes. Corresponds to + * {@link android.R.styleable#AndroidManifestApplication_largeHeap + * android:largeHeap}. + */ + public static final int FLAG_LARGE_HEAP = 1<<20; + /** + * Value for {@link #flags}: true if this application's package is in + * the stopped state. + */ + public static final int FLAG_STOPPED = 1<<21; + /** + * Value for {@link #flags}: true when the application is willing to support + * RTL (right to left). All activities will inherit this value. + * + * Set from the {@link android.R.attr#supportsRtl} attribute in the + * activity's manifest. + * + * Default value is false (no support for RTL). + */ + public static final int FLAG_SUPPORTS_RTL = 1<<22; + /** + * Value for {@link #flags}: true if the application is currently + * installed for the calling user. + */ + public static final int FLAG_INSTALLED = 1<<23; + /** + * Value for {@link #flags}: true if the application only has its + * data installed; the application package itself does not currently + * exist on the device. + */ + public static final int FLAG_IS_DATA_ONLY = 1<<24; + /** + * Value for {@link #flags}: true if the application was declared to be a game, or + * false if it is a non-game application. + */ + public static final int FLAG_IS_GAME = 1<<25; + /** + * Value for {@link #flags}: {@code true} if the application asks that only + * full-data streaming backups of its data be performed even though it defines + * a {@link android.app.backup.BackupAgent BackupAgent}, which normally + * indicates that the app will manage its backed-up data via incremental + * key/value updates. + */ + public static final int FLAG_FULL_BACKUP_ONLY = 1<<26; + /** + * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic + * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP + * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use + * cleartext network traffic, in which case platform components (e.g., HTTP stacks, + * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext + * traffic. Third-party libraries are encouraged to honor this flag as well. + * + *

NOTE: {@code WebView} does not honor this flag. + * + *

This flag is ignored on Android N and above if an Android Network Security Config is + * present. + * + *

This flag comes from + * {@link android.R.styleable#AndroidManifestApplication_usesCleartextTraffic + * android:usesCleartextTraffic} of the <application> tag. + */ + public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; + /** + * When set installer extracts native libs from .apk files. + */ + public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28; + /** + * Value for {@link #flags}: {@code true} when the application's rendering + * should be hardware accelerated. + */ + public static final int FLAG_HARDWARE_ACCELERATED = 1<<29; + /** + * Value for {@link #flags}: true if this application's package is in + * the suspended state. + */ + public static final int FLAG_SUSPENDED = 1<<30; + /** + * Value for {@link #flags}: true if code from this application will need to be + * loaded into other applications' processes. On devices that support multiple + * instruction sets, this implies the code might be loaded into a process that's + * using any of the devices supported instruction sets. + * + *

The system might treat such applications specially, for eg., by + * extracting the application's native libraries for all supported instruction + * sets or by compiling the application's dex code for all supported instruction + * sets. + */ + public static final int FLAG_MULTIARCH = 1 << 31; + /** + * Flags associated with the application. Any combination of + * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, + * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and + * {@link #FLAG_ALLOW_TASK_REPARENTING} + * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, + * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, + * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS}, + * {@link #FLAG_RESIZEABLE_FOR_SCREENS}, + * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}, + * {@link #FLAG_ALLOW_BACKUP}, {@link #FLAG_KILL_AFTER_RESTORE}, + * {@link #FLAG_RESTORE_ANY_VERSION}, {@link #FLAG_EXTERNAL_STORAGE}, + * {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED}, + * {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED}, + * {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME}, + * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC}, + * {@link #FLAG_MULTIARCH}. + */ + public int flags = 0; + /** + * Value for {@link #privateFlags}: true if the application is hidden via restrictions and for + * most purposes is considered as not installed. + * {@hide} + */ + public static final int PRIVATE_FLAG_HIDDEN = 1<<0; + /** + * Value for {@link #privateFlags}: set to true if the application + * has reported that it is heavy-weight, and thus can not participate in + * the normal application lifecycle. + * + *

Comes from the + * android.R.styleable#AndroidManifestApplication_cantSaveState + * attribute of the <application> tag. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_CANT_SAVE_STATE = 1<<1; + /** + * Value for {@link #privateFlags}: Set to true if the application has been + * installed using the forward lock option. + * + * NOTE: DO NOT CHANGE THIS VALUE! It is saved in packages.xml. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_FORWARD_LOCK = 1<<2; + /** + * Value for {@link #privateFlags}: set to {@code true} if the application + * is permitted to hold privileged permissions. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3; + /** + * Value for {@link #privateFlags}: {@code true} if the application has any IntentFiler + * with some data URI using HTTP or HTTPS with an associated VIEW action. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_HAS_DOMAIN_URLS = 1<<4; + /** + * When set, the default data storage directory for this app is pointed at + * the device-protected location. + * + * @hide + */ + public static final int PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 5; + /** + * When set, assume that all components under the given app are direct boot + * aware, unless otherwise specified. + * + * @hide + */ + public static final int PRIVATE_FLAG_DIRECT_BOOT_AWARE = 1 << 6; + /** + * Value for {@link #privateFlags}: set to {@code true} if the application + * is AutoPlay. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7; + /** + * When set, at least one component inside this application is direct boot + * aware. + * + * @hide + */ + public static final int PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE = 1 << 8; + /** + * Value for {@link #flags}: {@code true} if the application is blocked via restrictions + * and for most purposes is considered as not installed. + * {@hide} + */ + public static final int PRIVATE_FLAG_EPHEMERAL = 1 << 9; + /** + * When set, signals that the application is required for the system user and should not be + * uninstalled. + * + * @hide + */ + public static final int PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER = 1 << 10; + /** + * When set, the activities associated with this application are resizeable by default. + * @see android.R.styleable#AndroidManifestActivity_resizeableActivity + * + * @hide + */ + public static final int PRIVATE_FLAG_RESIZEABLE_ACTIVITIES = 1 << 11; + /** + * Value for {@link #privateFlags}: {@code true} means the OS should go ahead and + * run full-data backup operations for the app even when it is in a + * foreground-equivalent run state. Defaults to {@code false} if unspecified. + * @hide + */ + public static final int PRIVATE_FLAG_BACKUP_IN_FOREGROUND = 1 << 12; + /** + * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants. + * {@hide} + */ + public int privateFlags; + /** + * The required smallest screen width the application can run on. If 0, + * nothing has been specified. Comes from + * {@link android.R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp + * android:requiresSmallestWidthDp} attribute of the <supports-screens> tag. + */ + public int requiresSmallestWidthDp = 0; + /** + * The maximum smallest screen width the application is designed for. If 0, + * nothing has been specified. Comes from + * {@link android.R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp + * android:compatibleWidthLimitDp} attribute of the <supports-screens> tag. + */ + public int compatibleWidthLimitDp = 0; + /** + * The maximum smallest screen width the application will work on. If 0, + * nothing has been specified. Comes from + * {@link android.R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp + * android:largestWidthLimitDp} attribute of the <supports-screens> tag. + */ + public int largestWidthLimitDp = 0; + /** {@hide} */ + public String volumeUuid; + /** {@hide} */ + public String scanSourceDir; + /** {@hide} */ + public String scanPublicSourceDir; + /** + * Full path to the base APK for this application. + */ + public String sourceDir; + /** + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. + */ + public String publicSourceDir; + /** + * Full paths to zero or more split APKs that, when combined with the base + * APK defined in {@link #sourceDir}, form a complete application. + */ + public String[] splitSourceDirs; + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + */ + public String[] splitPublicSourceDirs; + /** + * Full paths to the locations of extra resource packages this application + * uses. This field is only used if there are extra resource packages, + * otherwise it is null. + * + * {@hide} + */ + public String[] resourceDirs; + /** + * String retrieved from the seinfo tag found in selinux policy. This value + * can be overridden with a value set through the mac_permissions.xml policy + * construct. This value is useful in setting an SELinux security context on + * the process as well as its data directory. The String default is being used + * here to represent a catchall label when no policy matches. + * + * {@hide} + */ + public String seinfo = "default"; + /** + * Paths to all shared libraries this application is linked against. This + * field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES + * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving + * the structure. + */ + public String[] sharedLibraryFiles; + + /** + * Full path to the default directory assigned to the package for its + * persistent data. + */ + public String dataDir; + /** + * Full path to the device-protected directory assigned to the package for + * its persistent data. + * + * @see Context#createDeviceProtectedStorageContext() + */ + public String deviceProtectedDataDir; + /** @removed */ + @Deprecated + public String deviceEncryptedDataDir; + /** + * Full path to the credential-protected directory assigned to the package + * for its persistent data. + * + * @hide + */ + @SystemApi + public String credentialProtectedDataDir; + /** @removed */ + @Deprecated + public String credentialEncryptedDataDir; + /** + * Full path to the directory where native JNI libraries are stored. + */ + public String nativeLibraryDir; + /** + * Full path where unpacked native libraries for {@link #secondaryCpuAbi} + * are stored, if present. + * + * The main reason this exists is for bundled multi-arch apps, where + * it's not trivial to calculate the location of libs for the secondary abi + * given the location of the primary. + * + * TODO: Change the layout of bundled installs so that we can use + * nativeLibraryRootDir & nativeLibraryRootRequiresIsa there as well. + * (e.g {@code [ "/system/app-lib/Foo/arm", "/system/app-lib/Foo/arm64" ]} + * instead of {@code [ "/system/lib/Foo", "/system/lib64/Foo" ]}. + * + * @hide + */ + public String secondaryNativeLibraryDir; + /** + * The root path where unpacked native libraries are stored. + *

+ * When {@link #nativeLibraryRootRequiresIsa} is set, the libraries are + * placed in ISA-specific subdirectories under this path, otherwise the + * libraries are placed directly at this path. + * + * @hide + */ + public String nativeLibraryRootDir; + /** + * Flag indicating that ISA must be appended to + * {@link #nativeLibraryRootDir} to be useful. + * + * @hide + */ + public boolean nativeLibraryRootRequiresIsa; + /** + * The primary ABI that this application requires, This is inferred from the ABIs + * of the native JNI libraries the application bundles. Will be {@code null} + * if this application does not require any particular ABI. + * + * If non-null, the application will always be launched with this ABI. + * + * {@hide} + */ + public String primaryCpuAbi; + /** + * The secondary ABI for this application. Might be non-null for multi-arch + * installs. The application itself never uses this ABI, but other applications that + * use its code might. + * + * {@hide} + */ + public String secondaryCpuAbi; + /** + * The kernel user-ID that has been assigned to this application; + * currently this is not a unique ID (multiple applications can have + * the same uid). + */ + public int uid; + + /** + * The minimum SDK version this application can run on. It will not run + * on earlier versions. + */ + public int minSdkVersion; + /** + * The minimum SDK version this application targets. It may run on earlier + * versions, but it knows how to work with any new behavior added at this + * version. Will be {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT} + * if this is a development build and the app is targeting that. You should + * compare that this number is >= the SDK version number at which your + * behavior was introduced. + */ + public int targetSdkVersion; + /** + * The app's declared version code. + * @hide + */ + public int versionCode; + /** + * When false, indicates that all components within this application are + * considered disabled, regardless of their individually set enabled status. + */ + public boolean enabled = true; + /** + * For convenient access to the current enabled setting of this app. + * @hide + */ + public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + /** + * For convenient access to package's install location. + * @hide + */ + public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; + /** + * Resource file providing the application's Network Security Config. + * @hide + */ + public int networkSecurityConfigRes; + /** + * @return true if "supportsRtl" has been set to true in the AndroidManifest + * @hide + */ + public boolean hasRtlSupport() { + return (flags & FLAG_SUPPORTS_RTL) == FLAG_SUPPORTS_RTL; + } + /** {@hide} */ + public boolean hasCode() { + return (flags & FLAG_HAS_CODE) != 0; + } + public static class DisplayNameComparator + implements Comparator { + public DisplayNameComparator(PackageManager pm) { + mPM = pm; + } + public final int compare(ApplicationInfo aa, ApplicationInfo ab) { + CharSequence sa = mPM.getApplicationLabel(aa); + if (sa == null) { + sa = aa.packageName; + } + CharSequence sb = mPM.getApplicationLabel(ab); + if (sb == null) { + sb = ab.packageName; + } + + return sCollator.compare(sa.toString(), sb.toString()); + } + private final Collator sCollator = Collator.getInstance(); + private PackageManager mPM; + } + public ApplicationInfo() { + } + + public ApplicationInfo(ApplicationInfo orig) { + super(orig); + taskAffinity = orig.taskAffinity; + permission = orig.permission; + processName = orig.processName; + className = orig.className; + theme = orig.theme; + flags = orig.flags; + privateFlags = orig.privateFlags; + requiresSmallestWidthDp = orig.requiresSmallestWidthDp; + compatibleWidthLimitDp = orig.compatibleWidthLimitDp; + largestWidthLimitDp = orig.largestWidthLimitDp; + volumeUuid = orig.volumeUuid; + scanSourceDir = orig.scanSourceDir; + scanPublicSourceDir = orig.scanPublicSourceDir; + sourceDir = orig.sourceDir; + publicSourceDir = orig.publicSourceDir; + splitSourceDirs = orig.splitSourceDirs; + splitPublicSourceDirs = orig.splitPublicSourceDirs; + nativeLibraryDir = orig.nativeLibraryDir; + secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir; + nativeLibraryRootDir = orig.nativeLibraryRootDir; + nativeLibraryRootRequiresIsa = orig.nativeLibraryRootRequiresIsa; + primaryCpuAbi = orig.primaryCpuAbi; + secondaryCpuAbi = orig.secondaryCpuAbi; + resourceDirs = orig.resourceDirs; + seinfo = orig.seinfo; + sharedLibraryFiles = orig.sharedLibraryFiles; + dataDir = orig.dataDir; + deviceEncryptedDataDir = deviceProtectedDataDir = orig.deviceProtectedDataDir; + credentialEncryptedDataDir = credentialProtectedDataDir = orig.credentialProtectedDataDir; + uid = orig.uid; + minSdkVersion = orig.minSdkVersion; + targetSdkVersion = orig.targetSdkVersion; + versionCode = orig.versionCode; + enabled = orig.enabled; + enabledSetting = orig.enabledSetting; + installLocation = orig.installLocation; + manageSpaceActivityName = orig.manageSpaceActivityName; + descriptionRes = orig.descriptionRes; + uiOptions = orig.uiOptions; + backupAgentName = orig.backupAgentName; + fullBackupContent = orig.fullBackupContent; + networkSecurityConfigRes = orig.networkSecurityConfigRes; + } + public String toString() { + return "ApplicationInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + "}"; + } + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int parcelableFlags) { + super.writeToParcel(dest, parcelableFlags); + dest.writeString(taskAffinity); + dest.writeString(permission); + dest.writeString(processName); + dest.writeString(className); + dest.writeInt(theme); + dest.writeInt(flags); + dest.writeInt(privateFlags); + dest.writeInt(requiresSmallestWidthDp); + dest.writeInt(compatibleWidthLimitDp); + dest.writeInt(largestWidthLimitDp); + dest.writeString(volumeUuid); + dest.writeString(scanSourceDir); + dest.writeString(scanPublicSourceDir); + dest.writeString(sourceDir); + dest.writeString(publicSourceDir); + dest.writeStringArray(splitSourceDirs); + dest.writeStringArray(splitPublicSourceDirs); + dest.writeString(nativeLibraryDir); + dest.writeString(secondaryNativeLibraryDir); + dest.writeString(nativeLibraryRootDir); + dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0); + dest.writeString(primaryCpuAbi); + dest.writeString(secondaryCpuAbi); + dest.writeStringArray(resourceDirs); + dest.writeString(seinfo); + dest.writeStringArray(sharedLibraryFiles); + dest.writeString(dataDir); + dest.writeString(deviceProtectedDataDir); + dest.writeString(credentialProtectedDataDir); + dest.writeInt(uid); + dest.writeInt(minSdkVersion); + dest.writeInt(targetSdkVersion); + dest.writeInt(versionCode); + dest.writeInt(enabled ? 1 : 0); + dest.writeInt(enabledSetting); + dest.writeInt(installLocation); + dest.writeString(manageSpaceActivityName); + dest.writeString(backupAgentName); + dest.writeInt(descriptionRes); + dest.writeInt(uiOptions); + dest.writeInt(fullBackupContent); + dest.writeInt(networkSecurityConfigRes); + } + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public ApplicationInfo createFromParcel(Parcel source) { + return new ApplicationInfo(source); + } + public ApplicationInfo[] newArray(int size) { + return new ApplicationInfo[size]; + } + }; + private ApplicationInfo(Parcel source) { + super(source); + taskAffinity = source.readString(); + permission = source.readString(); + processName = source.readString(); + className = source.readString(); + theme = source.readInt(); + flags = source.readInt(); + privateFlags = source.readInt(); + requiresSmallestWidthDp = source.readInt(); + compatibleWidthLimitDp = source.readInt(); + largestWidthLimitDp = source.readInt(); + volumeUuid = source.readString(); + scanSourceDir = source.readString(); + scanPublicSourceDir = source.readString(); + sourceDir = source.readString(); + publicSourceDir = source.readString(); + nativeLibraryDir = source.readString(); + secondaryNativeLibraryDir = source.readString(); + nativeLibraryRootDir = source.readString(); + nativeLibraryRootRequiresIsa = source.readInt() != 0; + primaryCpuAbi = source.readString(); + secondaryCpuAbi = source.readString(); + seinfo = source.readString(); + dataDir = source.readString(); + deviceEncryptedDataDir = deviceProtectedDataDir = source.readString(); + credentialEncryptedDataDir = credentialProtectedDataDir = source.readString(); + uid = source.readInt(); + minSdkVersion = source.readInt(); + targetSdkVersion = source.readInt(); + versionCode = source.readInt(); + enabled = source.readInt() != 0; + enabledSetting = source.readInt(); + installLocation = source.readInt(); + manageSpaceActivityName = source.readString(); + backupAgentName = source.readString(); + descriptionRes = source.readInt(); + uiOptions = source.readInt(); + fullBackupContent = source.readInt(); + networkSecurityConfigRes = source.readInt(); + } + /** + * Retrieve the textual description of the application. This + * will call back on the given PackageManager to load the description from + * the application. + * + * @param pm A PackageManager from which the label can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a CharSequence containing the application's description. + * If there is no description, null is returned. + */ + public CharSequence loadDescription(PackageManager pm) { + if (descriptionRes != 0) { + CharSequence label = pm.getText(packageName, descriptionRes, this); + if (label != null) { + return label; + } + } + return null; + } + /** + * Disable compatibility mode + * + * @hide + */ + public void disableCompatibilityMode() { + flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS | + FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS | + FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS); + } + + private boolean isPackageUnavailable(PackageManager pm) { + try { + return pm.getPackageInfo(packageName, 0) == null; + } catch (NameNotFoundException ex) { + return true; + } + } + /** + * @hide + */ + public boolean isForwardLocked() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; + } + /** + * @hide + */ + @TestApi + public boolean isSystemApp() { + return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + /** + * @hide + */ + @TestApi + public boolean isPrivilegedApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + } + /** + * @hide + */ + public boolean isUpdatedSystemApp() { + return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + /** @hide */ + public boolean isInternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; + } + /** @hide */ + public boolean isExternalAsec() { + return TextUtils.isEmpty(volumeUuid) + && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + /** @hide */ + public boolean isDefaultToDeviceProtectedStorage() { + return (privateFlags + & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0; + } + /** @hide */ + public boolean isDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; + } + /** @hide */ + public boolean isPartiallyDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; + } + /** + * @hide + */ + public boolean isAutoPlayApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0; + } + /** + * @hide + */ + public boolean isEphemeralApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0; + } + /** + * @hide + */ + public boolean isRequiredForSystemUser() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0; + } + /** + * @hide + */ + protected ApplicationInfo getApplicationInfo() { + return this; + } + /** {@hide} */ public void setCodePath(String codePath) { scanSourceDir = codePath; } + /** {@hide} */ public void setBaseCodePath(String baseCodePath) { sourceDir = baseCodePath; } + /** {@hide} */ public void setSplitCodePaths(String[] splitCodePaths) { splitSourceDirs = splitCodePaths; } + /** {@hide} */ public void setResourcePath(String resourcePath) { scanPublicSourceDir = resourcePath; } + /** {@hide} */ public void setBaseResourcePath(String baseResourcePath) { publicSourceDir = baseResourcePath; } + /** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; } + /** {@hide} */ public String getCodePath() { return scanSourceDir; } + /** {@hide} */ public String getBaseCodePath() { return sourceDir; } + /** {@hide} */ public String[] getSplitCodePaths() { return splitSourceDirs; } + /** {@hide} */ public String getResourcePath() { return scanPublicSourceDir; } + /** {@hide} */ public String getBaseResourcePath() { return publicSourceDir; } + /** {@hide} */ public String[] getSplitResourcePaths() { return splitSourceDirs; } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/FeatureInfo.java b/AndroidCompat/src/main/java/android/content/pm/FeatureInfo.java new file mode 100644 index 00000000..4ed2f831 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/FeatureInfo.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 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 android.content.pm; +import android.os.Parcel; +import android.os.Parcelable; +/** + * Definition of a single optional hardware or software feature of an Android + * device. + *

+ * This object is used to represent both features supported by a device and + * features requested by an app. Apps can request that certain features be + * available as a prerequisite to being installed through the + * {@code uses-feature} tag in their manifests. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#N}, features can have a + * version, which must always be backwards compatible. That is, a device + * claiming to support version 3 of a specific feature must support apps + * requesting version 1 of that feature. + */ +public class FeatureInfo implements Parcelable { + /** + * The name of this feature, for example "android.hardware.camera". If + * this is null, then this is an OpenGL ES version feature as described + * in {@link #reqGlEsVersion}. + */ + public String name; + /** + * If this object represents a feature supported by a device, this is the + * maximum version of this feature supported by the device. The device + * implicitly supports all older versions of this feature. + *

+ * If this object represents a feature requested by an app, this is the + * minimum version of the feature required by the app. + *

+ * When a feature version is undefined by a device, it's assumed to be + * version 0. + */ + public int version; + /** + * Default value for {@link #reqGlEsVersion}; + */ + public static final int GL_ES_VERSION_UNDEFINED = 0; + + /** + * The GLES version used by an application. The upper order 16 bits represent the + * major version and the lower order 16 bits the minor version. Only valid + * if {@link #name} is null. + */ + public int reqGlEsVersion; + /** + * Set on {@link #flags} if this feature has been required by the application. + */ + public static final int FLAG_REQUIRED = 0x0001; + + /** + * Additional flags. May be zero or more of {@link #FLAG_REQUIRED}. + */ + public int flags; + + public FeatureInfo() { + } + public FeatureInfo(FeatureInfo orig) { + name = orig.name; + version = orig.version; + reqGlEsVersion = orig.reqGlEsVersion; + flags = orig.flags; + } + @Override + public String toString() { + if (name != null) { + return "FeatureInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + name + " v=" + version + " fl=0x" + Integer.toHexString(flags) + "}"; + } else { + return "FeatureInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " glEsVers=" + getGlEsVersion() + + " fl=0x" + Integer.toHexString(flags) + "}"; + } + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(name); + dest.writeInt(version); + dest.writeInt(reqGlEsVersion); + dest.writeInt(flags); + } + public static final Creator CREATOR = new Creator() { + @Override + public FeatureInfo createFromParcel(Parcel source) { + return new FeatureInfo(source); + } + @Override + public FeatureInfo[] newArray(int size) { + return new FeatureInfo[size]; + } + }; + private FeatureInfo(Parcel source) { + name = source.readString(); + version = source.readInt(); + reqGlEsVersion = source.readInt(); + flags = source.readInt(); + } + /** + * This method extracts the major and minor version of reqGLEsVersion attribute + * and returns it as a string. Say reqGlEsVersion value of 0x00010002 is returned + * as 1.2 + * @return String representation of the reqGlEsVersion attribute + */ + public String getGlEsVersion() { + int major = ((reqGlEsVersion & 0xffff0000) >> 16); + int minor = reqGlEsVersion & 0x0000ffff; + return String.valueOf(major)+"."+String.valueOf(minor); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/InstantAppInfo.java b/AndroidCompat/src/main/java/android/content/pm/InstantAppInfo.java new file mode 100644 index 00000000..007142f5 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/InstantAppInfo.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015 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 android.content.pm; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import kotlin.NotImplementedError; + +/** + * This class represents the state of an instant app. Instant apps can + * be installed or uninstalled. If the app is installed you can call + * {@link #getApplicationInfo()} to get the app info, otherwise this + * class provides APIs to get basic app info for showing it in the UI, + * such as permissions, label, package name. + * + * @hide + */ +@SystemApi +public final class InstantAppInfo implements Parcelable { + private final ApplicationInfo mApplicationInfo; + private final String mPackageName; + private final CharSequence mLabelText; + private final String[] mRequestedPermissions; + private final String[] mGrantedPermissions; + public InstantAppInfo(ApplicationInfo appInfo, + String[] requestedPermissions, String[] grantedPermissions) { + mApplicationInfo = appInfo; + mPackageName = null; + mLabelText = null; + mRequestedPermissions = requestedPermissions; + mGrantedPermissions = grantedPermissions; + } + public InstantAppInfo(String packageName, CharSequence label, + String[] requestedPermissions, String[] grantedPermissions) { + mApplicationInfo = null; + mPackageName = packageName; + mLabelText = label; + mRequestedPermissions = requestedPermissions; + mGrantedPermissions = grantedPermissions; + } + private InstantAppInfo(Parcel parcel) { + throw new NotImplementedError(); + } + /** + * @return The application info if the app is installed, + * null otherwise, + */ + public @Nullable ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + /** + * @return The package name. + */ + public @NonNull String getPackageName() { + if (mApplicationInfo != null) { + return mApplicationInfo.packageName; + } + return mPackageName; + } + /** + * @param packageManager Package manager for loading resources. + * @return Loads the label if the app is installed or returns the cached one otherwise. + */ + public @NonNull CharSequence loadLabel(@NonNull PackageManager packageManager) { + if (mApplicationInfo != null) { + return mApplicationInfo.loadLabel(packageManager); + } + return mLabelText; + } + /** + * @param packageManager Package manager for loading resources. + * @return Loads the icon if the app is installed or returns the cached one otherwise. + */ + public @NonNull Drawable loadIcon(@NonNull PackageManager packageManager) { + if (mApplicationInfo != null) { + return mApplicationInfo.loadIcon(packageManager); + } + return packageManager.getInstantAppIcon(mPackageName); + } + /** + * @return The requested permissions. + */ + public @Nullable String[] getRequestedPermissions() { + return mRequestedPermissions; + } + /** + * @return The granted permissions. + */ + public @Nullable String[] getGrantedPermissions() { + return mGrantedPermissions; + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel parcel, int flags) { + throw new NotImplementedError(); + } + public static final Creator CREATOR = + new Creator() { + @Override + public InstantAppInfo createFromParcel(Parcel parcel) { + return new InstantAppInfo(parcel); + } + @Override + public InstantAppInfo[] newArray(int size) { + return new InstantAppInfo[0]; + } + }; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/IntentFilterVerificationInfo.java b/AndroidCompat/src/main/java/android/content/pm/IntentFilterVerificationInfo.java new file mode 100644 index 00000000..7cd3b377 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/IntentFilterVerificationInfo.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2015 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 android.content.pm; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; +/** + * The {@link com.android.server.pm.PackageManagerService} maintains some + * {@link IntentFilterVerificationInfo}s for each domain / package name. + * + * @hide + */ +@SystemApi +public final class IntentFilterVerificationInfo implements Parcelable { + private static final String TAG = IntentFilterVerificationInfo.class.getName(); + private static final String TAG_DOMAIN = "domain"; + private static final String ATTR_DOMAIN_NAME = "name"; + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_STATUS = "status"; + private ArraySet mDomains = new ArraySet<>(); + private String mPackageName; + private int mMainStatus; + /** @hide */ + public IntentFilterVerificationInfo() { + mPackageName = null; + mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + /** @hide */ + public IntentFilterVerificationInfo(String packageName, ArraySet domains) { + mPackageName = packageName; + mDomains = domains; + mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + /** @hide */ + public IntentFilterVerificationInfo(XmlPullParser parser) + throws IOException, XmlPullParserException { + readFromXml(parser); + } + /** @hide */ + public IntentFilterVerificationInfo(Parcel source) { + readFromParcel(source); + } + public String getPackageName() { + return mPackageName; + } + public int getStatus() { + return mMainStatus; + } + /** @hide */ + public void setStatus(int s) { + if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED && + s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + mMainStatus = s; + } else { + Log.w(TAG, "Trying to set a non supported status: " + s); + } + } + public Set getDomains() { + return mDomains; + } + /** @hide */ + public void setDomains(ArraySet list) { + mDomains = list; + } + /** @hide */ + public String getDomainsString() { + StringBuilder sb = new StringBuilder(); + for (String str : mDomains) { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append(str); + } + return sb.toString(); + } + String getStringFromXml(XmlPullParser parser, String attribute, String defaultValue) { + String value = parser.getAttributeValue(null, attribute); + if (value == null) { + String msg = "Missing element under " + TAG +": " + attribute + " at " + + parser.getPositionDescription(); + Log.w(TAG, msg); + return defaultValue; + } else { + return value; + } + } + int getIntFromXml(XmlPullParser parser, String attribute, int defaultValue) { + String value = parser.getAttributeValue(null, attribute); + if (TextUtils.isEmpty(value)) { + String msg = "Missing element under " + TAG +": " + attribute + " at " + + parser.getPositionDescription(); + Log.w(TAG, msg); + return defaultValue; + } else { + return Integer.parseInt(value); + } + } + /** @hide */ + public void readFromXml(XmlPullParser parser) throws XmlPullParserException, + IOException { + mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null); + if (mPackageName == null) { + Log.e(TAG, "Package name cannot be null!"); + } + int status = getIntFromXml(parser, ATTR_STATUS, -1); + if (status == -1) { + Log.e(TAG, "Unknown status value: " + status); + } + mMainStatus = status; + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals(TAG_DOMAIN)) { + String name = getStringFromXml(parser, ATTR_DOMAIN_NAME, null); + if (!TextUtils.isEmpty(name)) { + mDomains.add(name); + } + } else { + Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName); + } + XmlUtils.skipCurrentTag(parser); + } + } + /** @hide */ + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); + serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus)); + for (String str : mDomains) { + serializer.startTag(null, TAG_DOMAIN); + serializer.attribute(null, ATTR_DOMAIN_NAME, str); + serializer.endTag(null, TAG_DOMAIN); + } + } + /** @hide */ + public String getStatusString() { + return getStatusStringFromValue(((long)mMainStatus) << 32); + } + /** @hide */ + public static String getStatusStringFromValue(long val) { + StringBuilder sb = new StringBuilder(); + switch ((int)(val >> 32)) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: + sb.append("always : "); + sb.append(Long.toHexString(val & 0x00000000FFFFFFFF)); + break; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: + sb.append("ask"); + break; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: + sb.append("never"); + break; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK: + sb.append("always-ask"); + break; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: + default: + sb.append("undefined"); + break; + } + return sb.toString(); + } + @Override + public int describeContents() { + return 0; + } + private void readFromParcel(Parcel source) { + mPackageName = source.readString(); + mMainStatus = source.readInt(); + ArrayList list = new ArrayList<>(); + source.readStringList(list); + mDomains.addAll(list); + } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mMainStatus); + dest.writeStringList(new ArrayList<>(mDomains)); + } + public static final Creator CREATOR = + new Creator() { + public IntentFilterVerificationInfo createFromParcel(Parcel source) { + return new IntentFilterVerificationInfo(source); + } + public IntentFilterVerificationInfo[] newArray(int size) { + return new IntentFilterVerificationInfo[size]; + } + }; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/KeySet.java b/AndroidCompat/src/main/java/android/content/pm/KeySet.java new file mode 100644 index 00000000..37ca4f76 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/KeySet.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 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 android.content.pm; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +/** + * Represents a {@code KeySet} that has been declared in the AndroidManifest.xml + * file for the application. A {@code KeySet} can be used explicitly to + * represent a trust relationship with other applications on the device. + * @hide + */ +public class KeySet implements Parcelable { + private IBinder token; + /** @hide */ + public KeySet(IBinder token) { + if (token == null) { + throw new NullPointerException("null value for KeySet IBinder token"); + } + this.token = token; + } + /** @hide */ + public IBinder getToken() { + return token; + } + /** @hide */ + @Override + public boolean equals(Object o) { + if (o instanceof KeySet) { + KeySet ks = (KeySet) o; + return token == ks.token; + } + return false; + } + /** @hide */ + @Override + public int hashCode() { + return token.hashCode(); + } + /** + * Implement Parcelable + * @hide + */ + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + /** + * Create a KeySet from a Parcel + * + * @param source The parcel containing the KeySet + */ + public KeySet createFromParcel(Parcel source) { + return readFromParcel(source); + } + /** + * Create an array of null KeySets + */ + public KeySet[] newArray(int size) { + return new KeySet[size]; + } + }; + /** + * @hide + */ + private static KeySet readFromParcel(Parcel in) { + IBinder token = in.readStrongBinder(); + return new KeySet(token); + } + /** + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(token); + } + /** + * @hide + */ + @Override + public int describeContents() { + return 0; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/PackageInfo.java b/AndroidCompat/src/main/java/android/content/pm/PackageInfo.java new file mode 100644 index 00000000..6254d150 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/PackageInfo.java @@ -0,0 +1,353 @@ +/* + * 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 android.content.pm; +import android.os.Parcel; +import android.os.Parcelable; +/** + * Overall information about the contents of a package. This corresponds + * to all of the information collected from AndroidManifest.xml. + */ +public class PackageInfo implements Parcelable { + /** + * The name of this package. From the <manifest> tag's "name" + * attribute. + */ + public String packageName; + /** + * The names of any installed split APKs for this package. + */ + public String[] splitNames; + /** + * The version number of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode} + * attribute. + */ + public int versionCode; + /** + * The version name of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_versionName versionName} + * attribute. + */ + public String versionName; + /** + * The revision number of the base APK for this package, as specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. + */ + public int baseRevisionCode; + /** + * The revision number of any split APKs for this package, as specified by + * the <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. Indexes are a 1:1 mapping against {@link #splitNames}. + */ + public int[] splitRevisionCodes; + /** + * The shared user ID name of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId} + * attribute. + */ + public String sharedUserId; + + /** + * The shared user ID label of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel} + * attribute. + */ + public int sharedUserLabel; + + /** + * Information collected from the <application> tag, or null if + * there was none. + */ + public ApplicationInfo applicationInfo; + + /** + * The time at which the app was first installed. Units are as + * per {@link System#currentTimeMillis()}. + */ + public long firstInstallTime; + /** + * The time at which the app was last updated. Units are as + * per {@link System#currentTimeMillis()}. + */ + public long lastUpdateTime; + /** + * All kernel group-IDs that have been assigned to this package. + * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set. + */ + public int[] gids; + /** + * Array of all {@link android.R.styleable#AndroidManifestActivity + * <activity>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_ACTIVITIES} was set. + */ + public ActivityInfo[] activities; + /** + * Array of all {@link android.R.styleable#AndroidManifestReceiver + * <receiver>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_RECEIVERS} was set. + */ + public ActivityInfo[] receivers; + /** + * Array of all {@link android.R.styleable#AndroidManifestService + * <service>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_SERVICES} was set. + */ + public ServiceInfo[] services; + /** + * Array of all {@link android.R.styleable#AndroidManifestProvider + * <provider>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PROVIDERS} was set. + */ + public ProviderInfo[] providers; + /** + * Array of all {@link android.R.styleable#AndroidManifestInstrumentation + * <instrumentation>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_INSTRUMENTATION} was set. + */ + public InstrumentationInfo[] instrumentation; + + /** + * Array of all {@link android.R.styleable#AndroidManifestPermission + * <permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. + */ + public PermissionInfo[] permissions; + + /** + * Array of all {@link android.R.styleable#AndroidManifestUsesPermission + * <uses-permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. This list includes + * all permissions requested, even those that were not granted or known + * by the system at install time. + */ + public String[] requestedPermissions; + + /** + * Array of flags of all {@link android.R.styleable#AndroidManifestUsesPermission + * <uses-permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches + * the corresponding entry in {@link #requestedPermissions}, and will have + * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. + */ + public int[] requestedPermissionsFlags; + /** + * Flag for {@link #requestedPermissionsFlags}: the requested permission + * is required for the application to run; the user can not optionally + * disable it. Currently all permissions are required. + * + * @removed We do not support required permissions. + */ + public static final int REQUESTED_PERMISSION_REQUIRED = 1<<0; + /** + * Flag for {@link #requestedPermissionsFlags}: the requested permission + * is currently granted to the application. + */ + public static final int REQUESTED_PERMISSION_GRANTED = 1<<1; + /** + * Array of all signatures read from the package file. This is only filled + * in if the flag {@link PackageManager#GET_SIGNATURES} was set. + */ + public Signature[] signatures; + + /** + * Application specified preferred configuration + * {@link android.R.styleable#AndroidManifestUsesConfiguration + * <uses-configuration>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_CONFIGURATIONS} was set. + */ + public ConfigurationInfo[] configPreferences; + /** + * Features that this application has requested. + * + * @see FeatureInfo#FLAG_REQUIRED + */ + public FeatureInfo[] reqFeatures; + /** + * Groups of features that this application has requested. + * Each group contains a set of features that are required. + * A device must match the features listed in {@link #reqFeatures} and one + * or more FeatureGroups in order to have satisfied the feature requirement. + * + * @see FeatureInfo#FLAG_REQUIRED + */ + public FeatureGroupInfo[] featureGroups; + /** + * Constant corresponding to auto in + * the {@link android.R.attr#installLocation} attribute. + * @hide + */ + public static final int INSTALL_LOCATION_UNSPECIFIED = -1; + /** + * Constant corresponding to auto in the + * {@link android.R.attr#installLocation} attribute. + */ + public static final int INSTALL_LOCATION_AUTO = 0; + /** + * Constant corresponding to internalOnly in the + * {@link android.R.attr#installLocation} attribute. + */ + public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; + /** + * Constant corresponding to preferExternal in the + * {@link android.R.attr#installLocation} attribute. + */ + public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; + /** + * The install location requested by the package. From the + * {@link android.R.attr#installLocation} attribute, one of + * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY}, + * {@link #INSTALL_LOCATION_PREFER_EXTERNAL} + */ + public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; + /** @hide */ + public boolean coreApp; + /** @hide */ + public boolean requiredForAllUsers; + /** @hide */ + public String restrictedAccountType; + /** @hide */ + public String requiredAccountType; + /** + * What package, if any, this package will overlay. + * + * Package name of target package, or null. + * @hide + */ + public String overlayTarget; + public PackageInfo() { + } + @Override + public String toString() { + return "PackageInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + "}"; + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(packageName); + dest.writeStringArray(splitNames); + dest.writeInt(versionCode); + dest.writeString(versionName); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); + dest.writeString(sharedUserId); + dest.writeInt(sharedUserLabel); + if (applicationInfo != null) { + dest.writeInt(1); + applicationInfo.writeToParcel(dest, parcelableFlags); + } else { + dest.writeInt(0); + } + dest.writeLong(firstInstallTime); + dest.writeLong(lastUpdateTime); + dest.writeIntArray(gids); + dest.writeTypedArray(activities, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(receivers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(services, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(providers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(instrumentation, parcelableFlags); + dest.writeTypedArray(permissions, parcelableFlags); + dest.writeStringArray(requestedPermissions); + dest.writeIntArray(requestedPermissionsFlags); + dest.writeTypedArray(signatures, parcelableFlags); + dest.writeTypedArray(configPreferences, parcelableFlags); + dest.writeTypedArray(reqFeatures, parcelableFlags); + dest.writeTypedArray(featureGroups, parcelableFlags); + dest.writeInt(installLocation); + dest.writeInt(coreApp ? 1 : 0); + dest.writeInt(requiredForAllUsers ? 1 : 0); + dest.writeString(restrictedAccountType); + dest.writeString(requiredAccountType); + dest.writeString(overlayTarget); + } + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public PackageInfo createFromParcel(Parcel source) { + return new PackageInfo(source); + } + @Override + public PackageInfo[] newArray(int size) { + return new PackageInfo[size]; + } + }; + private PackageInfo(Parcel source) { + packageName = source.readString(); + splitNames = source.createStringArray(); + versionCode = source.readInt(); + versionName = source.readString(); + baseRevisionCode = source.readInt(); + splitRevisionCodes = source.createIntArray(); + sharedUserId = source.readString(); + sharedUserLabel = source.readInt(); + int hasApp = source.readInt(); + if (hasApp != 0) { + applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source); + } + firstInstallTime = source.readLong(); + lastUpdateTime = source.readLong(); + gids = source.createIntArray(); + activities = source.createTypedArray(ActivityInfo.CREATOR); + receivers = source.createTypedArray(ActivityInfo.CREATOR); + services = source.createTypedArray(ServiceInfo.CREATOR); + providers = source.createTypedArray(ProviderInfo.CREATOR); + instrumentation = source.createTypedArray(InstrumentationInfo.CREATOR); + permissions = source.createTypedArray(PermissionInfo.CREATOR); + requestedPermissions = source.createStringArray(); + requestedPermissionsFlags = source.createIntArray(); + signatures = source.createTypedArray(Signature.CREATOR); + configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR); + reqFeatures = source.createTypedArray(FeatureInfo.CREATOR); + featureGroups = source.createTypedArray(FeatureGroupInfo.CREATOR); + installLocation = source.readInt(); + coreApp = source.readInt() != 0; + requiredForAllUsers = source.readInt() != 0; + restrictedAccountType = source.readString(); + requiredAccountType = source.readString(); + overlayTarget = source.readString(); + // The component lists were flattened with the redundant ApplicationInfo + // instances omitted. Distribute the canonical one here as appropriate. + if (applicationInfo != null) { + propagateApplicationInfo(applicationInfo, activities); + propagateApplicationInfo(applicationInfo, receivers); + propagateApplicationInfo(applicationInfo, services); + propagateApplicationInfo(applicationInfo, providers); + } + } + private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) { + if (components != null) { + for (ComponentInfo ci : components) { + ci.applicationInfo = appInfo; + } + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/PackageItemInfo.java b/AndroidCompat/src/main/java/android/content/pm/PackageItemInfo.java new file mode 100644 index 00000000..e96d0035 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/PackageItemInfo.java @@ -0,0 +1,346 @@ +/* + * 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 android.content.pm; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Parcel; +import android.util.Printer; +import kotlin.NotImplementedError; + +import java.text.Collator; +import java.util.Comparator; +/** + * Base class containing information common to all package items held by + * the package manager. This provides a very common basic set of attributes: + * a label, icon, and meta-data. This class is not intended + * to be used by itself; it is simply here to share common definitions + * between all items returned by the package manager. As such, it does not + * itself implement Parcelable, but does provide convenience methods to assist + * in the implementation of Parcelable in subclasses. + */ +public class PackageItemInfo { + private static final float MAX_LABEL_SIZE_PX = 500f; + /** + * Public name of this item. From the "android:name" attribute. + */ + public String name; + + /** + * Name of the package that this item is in. + */ + public String packageName; + + /** + * A string resource identifier (in the package's resources) of this + * component's label. From the "label" attribute or, if not set, 0. + */ + public int labelRes; + + /** + * The string provided in the AndroidManifest file, if any. You + * probably don't want to use this. You probably want + * {@link PackageManager#getApplicationLabel} + */ + public CharSequence nonLocalizedLabel; + + /** + * A drawable resource identifier (in the package's resources) of this + * component's icon. From the "icon" attribute or, if not set, 0. + */ + public int icon; + + /** + * A drawable resource identifier (in the package's resources) of this + * component's banner. From the "banner" attribute or, if not set, 0. + */ + public int banner; + /** + * A drawable resource identifier (in the package's resources) of this + * component's logo. Logos may be larger/wider than icons and are + * displayed by certain UI elements in place of a name or name/icon + * combination. From the "logo" attribute or, if not set, 0. + */ + public int logo; + + /** + * Additional meta-data associated with this component. This field + * will only be filled in if you set the + * {@link PackageManager#GET_META_DATA} flag when requesting the info. + */ + public Bundle metaData; + /** + * If different of UserHandle.USER_NULL, The icon of this item will be the one of that user. + * @hide + */ + public int showUserIcon; + public PackageItemInfo() { +// showUserIcon = UserHandle.USER_NULL; + } + public PackageItemInfo(PackageItemInfo orig) { + name = orig.name; + if (name != null) name = name.trim(); + packageName = orig.packageName; + labelRes = orig.labelRes; + nonLocalizedLabel = orig.nonLocalizedLabel; + if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim(); + icon = orig.icon; + banner = orig.banner; + logo = orig.logo; + metaData = orig.metaData; + showUserIcon = orig.showUserIcon; + } + /** + * Retrieve the current textual label associated with this item. This + * will call back on the given PackageManager to load the label from + * the application. + * + * @param pm A PackageManager from which the label can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a CharSequence containing the item's label. If the + * item does not have a label, its name is returned. + */ + public CharSequence loadLabel(PackageManager pm) { + if (nonLocalizedLabel != null) { + return nonLocalizedLabel; + } + if (labelRes != 0) { + CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo()); + if (label != null) { + return label.toString().trim(); + } + } + if (name != null) { + return name; + } + return packageName; + } + + /** + * Retrieve the current graphical icon associated with this item. This + * will call back on the given PackageManager to load the icon from + * the application. + * + * @param pm A PackageManager from which the icon can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's icon. If the + * item does not have an icon, the item's default icon is returned + * such as the default activity icon. + */ + public Drawable loadIcon(PackageManager pm) { + throw new NotImplementedError(); + } + /** + * Retrieve the current graphical icon associated with this item without + * the addition of a work badge if applicable. + * This will call back on the given PackageManager to load the icon from + * the application. + * + * @param pm A PackageManager from which the icon can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's icon. If the + * item does not have an icon, the item's default icon is returned + * such as the default activity icon. + */ + public Drawable loadUnbadgedIcon(PackageManager pm) { + throw new NotImplementedError(); + } + /** + * Retrieve the current graphical banner associated with this item. This + * will call back on the given PackageManager to load the banner from + * the application. + * + * @param pm A PackageManager from which the banner can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's banner. If the item + * does not have a banner, this method will return null. + */ + public Drawable loadBanner(PackageManager pm) { + if (banner != 0) { + Drawable dr = pm.getDrawable(packageName, banner, getApplicationInfo()); + if (dr != null) { + return dr; + } + } + return loadDefaultBanner(pm); + } + /** + * Retrieve the default graphical icon associated with this item. + * + * @param pm A PackageManager from which the icon can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's default icon + * such as the default activity icon. + * + * @hide + */ + public Drawable loadDefaultIcon(PackageManager pm) { + return pm.getDefaultActivityIcon(); + } + /** + * Retrieve the default graphical banner associated with this item. + * + * @param pm A PackageManager from which the banner can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's default banner + * or null if no default logo is available. + * + * @hide + */ + protected Drawable loadDefaultBanner(PackageManager pm) { + return null; + } + /** + * Retrieve the current graphical logo associated with this item. This + * will call back on the given PackageManager to load the logo from + * the application. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's logo. If the item + * does not have a logo, this method will return null. + */ + public Drawable loadLogo(PackageManager pm) { + if (logo != 0) { + Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo()); + if (d != null) { + return d; + } + } + return loadDefaultLogo(pm); + } + + /** + * Retrieve the default graphical logo associated with this item. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's default logo + * or null if no default logo is available. + * + * @hide + */ + protected Drawable loadDefaultLogo(PackageManager pm) { + return null; + } + + /** + * Load an XML resource attached to the meta-data of this item. This will + * retrieved the name meta-data entry, and if defined call back on the + * given PackageManager to load its XML file from the application. + * + * @param pm A PackageManager from which the XML can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * @param name Name of the meta-date you would like to load. + * + * @return Returns an XmlPullParser you can use to parse the XML file + * assigned as the given meta-data. If the meta-data name is not defined + * or the XML resource could not be found, null is returned. + */ + public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) { + if (metaData != null) { + int resid = metaData.getInt(name); + if (resid != 0) { + return pm.getXml(packageName, resid, getApplicationInfo()); + } + } + return null; + } + /** + * @hide Flag for dumping: include all details. + */ + public static final int DUMP_FLAG_DETAILS = 1<<0; + /** + * @hide Flag for dumping: include nested ApplicationInfo. + */ + public static final int DUMP_FLAG_APPLICATION = 1<<1; + /** + * @hide Flag for dumping: all flags to dump everything. + */ + public static final int DUMP_FLAG_ALL = DUMP_FLAG_DETAILS | DUMP_FLAG_APPLICATION; + protected void dumpFront(Printer pw, String prefix) { + if (name != null) { + pw.println(prefix + "name=" + name); + } + pw.println(prefix + "packageName=" + packageName); + if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) { + pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes) + + " nonLocalizedLabel=" + nonLocalizedLabel + + " icon=0x" + Integer.toHexString(icon) + + " banner=0x" + Integer.toHexString(banner)); + } + } + + protected void dumpBack(Printer pw, String prefix) { + // no back here + } + + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(name); + dest.writeString(packageName); + dest.writeInt(labelRes); + dest.writeInt(icon); + dest.writeInt(logo); + dest.writeBundle(metaData); + dest.writeInt(banner); + dest.writeInt(showUserIcon); + } + + protected PackageItemInfo(Parcel source) { + name = source.readString(); + packageName = source.readString(); + labelRes = source.readInt(); + icon = source.readInt(); + logo = source.readInt(); + metaData = source.readBundle(); + banner = source.readInt(); + showUserIcon = source.readInt(); + } + /** + * Get the ApplicationInfo for the application to which this item belongs, + * if available, otherwise returns null. + * + * @return Returns the ApplicationInfo of this item, or null if not known. + * + * @hide + */ + protected ApplicationInfo getApplicationInfo() { + return null; + } + public static class DisplayNameComparator + implements Comparator { + public DisplayNameComparator(PackageManager pm) { + mPM = pm; + } + public final int compare(PackageItemInfo aa, PackageItemInfo ab) { + CharSequence sa = aa.loadLabel(mPM); + if (sa == null) sa = aa.name; + CharSequence sb = ab.loadLabel(mPM); + if (sb == null) sb = ab.name; + return sCollator.compare(sa.toString(), sb.toString()); + } + private final Collator sCollator = Collator.getInstance(); + private PackageManager mPM; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/PackageManager.java b/AndroidCompat/src/main/java/android/content/pm/PackageManager.java new file mode 100644 index 00000000..6a8a61e0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/PackageManager.java @@ -0,0 +1,5318 @@ +/* + * 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 android.content.pm; +import android.Manifest; +import android.annotation.CheckResult; +import android.annotation.DrawableRes; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.StringRes; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.annotation.UserIdInt; +import android.annotation.XmlRes; +import android.app.ActivityManager; +import android.app.PackageDeleteObserver; +import android.app.PackageInstallObserver; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.util.AndroidException; +import android.util.Log; +import com.android.internal.util.ArrayUtils; +import kotlin.NotImplementedError; + +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +/** + * Class for retrieving various kinds of information related to the application + * packages that are currently installed on the device. + * + * You can find this class through {@link Context#getPackageManager}. + */ +public abstract class PackageManager { + private static final String TAG = "PackageManager"; + /** {@hide} */ + public static final boolean APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE = true; + /** + * This exception is thrown when a given package, application, or component + * name cannot be found. + */ + public static class NameNotFoundException extends AndroidException { + public NameNotFoundException() { + } + public NameNotFoundException(String name) { + super(name); + } + } + /** + * Listener for changes in permissions granted to a UID. + * + * @hide + */ + @SystemApi + public interface OnPermissionsChangedListener { + /** + * Called when the permissions for a UID change. + * @param uid The UID with a change. + */ + public void onPermissionsChanged(int uid); + } + /** + * As a guiding principle: + *

+ * {@code GET_} flags are used to request additional data that may have been + * elided to save wire space. + *

+ * {@code MATCH_} flags are used to include components or packages that + * would have otherwise been omitted from a result set by current system + * state. + */ + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_ACTIVITIES, + GET_CONFIGURATIONS, + GET_GIDS, + GET_INSTRUMENTATION, + GET_INTENT_FILTERS, + GET_META_DATA, + GET_PERMISSIONS, + GET_PROVIDERS, + GET_RECEIVERS, + GET_SERVICES, + GET_SHARED_LIBRARY_FILES, + GET_SIGNATURES, + GET_URI_PERMISSION_PATTERNS, + MATCH_UNINSTALLED_PACKAGES, + MATCH_DISABLED_COMPONENTS, + MATCH_DISABLED_UNTIL_USED_COMPONENTS, + MATCH_SYSTEM_ONLY, + MATCH_FACTORY_ONLY, + MATCH_DEBUG_TRIAGED_MISSING, + MATCH_INSTANT, + GET_DISABLED_COMPONENTS, + GET_DISABLED_UNTIL_USED_COMPONENTS, + GET_UNINSTALLED_PACKAGES, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PackageInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + GET_SHARED_LIBRARY_FILES, + MATCH_UNINSTALLED_PACKAGES, + MATCH_SYSTEM_ONLY, + MATCH_DEBUG_TRIAGED_MISSING, + MATCH_DISABLED_COMPONENTS, + MATCH_DISABLED_UNTIL_USED_COMPONENTS, + MATCH_INSTANT, + MATCH_STATIC_SHARED_LIBRARIES, + GET_DISABLED_UNTIL_USED_COMPONENTS, + GET_UNINSTALLED_PACKAGES, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ApplicationInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + GET_SHARED_LIBRARY_FILES, + MATCH_ALL, + MATCH_DEBUG_TRIAGED_MISSING, + MATCH_DEFAULT_ONLY, + MATCH_DISABLED_COMPONENTS, + MATCH_DISABLED_UNTIL_USED_COMPONENTS, + MATCH_DIRECT_BOOT_AWARE, + MATCH_DIRECT_BOOT_UNAWARE, + MATCH_SYSTEM_ONLY, + MATCH_UNINSTALLED_PACKAGES, + MATCH_INSTANT, + MATCH_STATIC_SHARED_LIBRARIES, + GET_DISABLED_COMPONENTS, + GET_DISABLED_UNTIL_USED_COMPONENTS, + GET_UNINSTALLED_PACKAGES, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + GET_RESOLVED_FILTER, + GET_SHARED_LIBRARY_FILES, + MATCH_ALL, + MATCH_DEBUG_TRIAGED_MISSING, + MATCH_DISABLED_COMPONENTS, + MATCH_DISABLED_UNTIL_USED_COMPONENTS, + MATCH_DEFAULT_ONLY, + MATCH_DIRECT_BOOT_AWARE, + MATCH_DIRECT_BOOT_UNAWARE, + MATCH_SYSTEM_ONLY, + MATCH_UNINSTALLED_PACKAGES, + MATCH_INSTANT, + GET_DISABLED_COMPONENTS, + GET_DISABLED_UNTIL_USED_COMPONENTS, + GET_UNINSTALLED_PACKAGES, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResolveInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionGroupInfoFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + GET_META_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InstrumentationInfoFlags {} + /** + * {@link PackageInfo} flag: return information about + * activities in the package in {@link PackageInfo#activities}. + */ + public static final int GET_ACTIVITIES = 0x00000001; + /** + * {@link PackageInfo} flag: return information about + * intent receivers in the package in + * {@link PackageInfo#receivers}. + */ + public static final int GET_RECEIVERS = 0x00000002; + /** + * {@link PackageInfo} flag: return information about + * services in the package in {@link PackageInfo#services}. + */ + public static final int GET_SERVICES = 0x00000004; + /** + * {@link PackageInfo} flag: return information about + * content providers in the package in + * {@link PackageInfo#providers}. + */ + public static final int GET_PROVIDERS = 0x00000008; + /** + * {@link PackageInfo} flag: return information about + * instrumentation in the package in + * {@link PackageInfo#instrumentation}. + */ + public static final int GET_INSTRUMENTATION = 0x00000010; + /** + * {@link PackageInfo} flag: return information about the + * intent filters supported by the activity. + */ + public static final int GET_INTENT_FILTERS = 0x00000020; + /** + * {@link PackageInfo} flag: return information about the + * signatures included in the package. + */ + public static final int GET_SIGNATURES = 0x00000040; + /** + * {@link ResolveInfo} flag: return the IntentFilter that + * was matched for a particular ResolveInfo in + * {@link ResolveInfo#filter}. + */ + public static final int GET_RESOLVED_FILTER = 0x00000040; + /** + * {@link ComponentInfo} flag: return the {@link ComponentInfo#metaData} + * data {@link android.os.Bundle}s that are associated with a component. + * This applies for any API returning a ComponentInfo subclass. + */ + public static final int GET_META_DATA = 0x00000080; + /** + * {@link PackageInfo} flag: return the + * {@link PackageInfo#gids group ids} that are associated with an + * application. + * This applies for any API returning a PackageInfo class, either + * directly or nested inside of another. + */ + public static final int GET_GIDS = 0x00000100; + /** + * @deprecated replaced with {@link #MATCH_DISABLED_COMPONENTS} + */ + @Deprecated + public static final int GET_DISABLED_COMPONENTS = 0x00000200; + /** + * {@link PackageInfo} flag: include disabled components in the returned info. + */ + public static final int MATCH_DISABLED_COMPONENTS = 0x00000200; + /** + * {@link ApplicationInfo} flag: return the + * {@link ApplicationInfo#sharedLibraryFiles paths to the shared libraries} + * that are associated with an application. + * This applies for any API returning an ApplicationInfo class, either + * directly or nested inside of another. + */ + public static final int GET_SHARED_LIBRARY_FILES = 0x00000400; + /** + * {@link ProviderInfo} flag: return the + * {@link ProviderInfo#uriPermissionPatterns URI permission patterns} + * that are associated with a content provider. + * This applies for any API returning a ProviderInfo class, either + * directly or nested inside of another. + */ + public static final int GET_URI_PERMISSION_PATTERNS = 0x00000800; + /** + * {@link PackageInfo} flag: return information about + * permissions in the package in + * {@link PackageInfo#permissions}. + */ + public static final int GET_PERMISSIONS = 0x00001000; + /** + * @deprecated replaced with {@link #MATCH_UNINSTALLED_PACKAGES} + */ + @Deprecated + public static final int GET_UNINSTALLED_PACKAGES = 0x00002000; + /** + * Flag parameter to retrieve some information about all applications (even + * uninstalled ones) which have data directories. This state could have + * resulted if applications have been deleted with flag + * {@code DONT_DELETE_DATA} with a possibility of being replaced or + * reinstalled in future. + *

+ * Note: this flag may cause less information about currently installed + * applications to be returned. + */ + public static final int MATCH_UNINSTALLED_PACKAGES = 0x00002000; + /** + * {@link PackageInfo} flag: return information about + * hardware preferences in + * {@link PackageInfo#configPreferences PackageInfo.configPreferences}, + * and requested features in {@link PackageInfo#reqFeatures} and + * {@link PackageInfo#featureGroups}. + */ + public static final int GET_CONFIGURATIONS = 0x00004000; + /** + * @deprecated replaced with {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}. + */ + @Deprecated + public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000; + /** + * {@link PackageInfo} flag: include disabled components which are in + * that state only because of {@link #COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} + * in the returned info. Note that if you set this flag, applications + * that are in this disabled state will be reported as enabled. + */ + public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000; + /** + * Resolution and querying flag: if set, only filters that support the + * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for + * matching. This is a synonym for including the CATEGORY_DEFAULT in your + * supplied Intent. + */ + public static final int MATCH_DEFAULT_ONLY = 0x00010000; + /** + * Querying flag: if set and if the platform is doing any filtering of the + * results, then the filtering will not happen. This is a synonym for saying + * that all results should be returned. + *

+ * This flag should be used with extreme care. + */ + public static final int MATCH_ALL = 0x00020000; + /** + * Querying flag: match components which are direct boot unaware in + * the returned info, regardless of the current user state. + *

+ * When neither {@link #MATCH_DIRECT_BOOT_AWARE} nor + * {@link #MATCH_DIRECT_BOOT_UNAWARE} are specified, the default behavior is + * to match only runnable components based on the user state. For example, + * when a user is started but credentials have not been presented yet, the + * user is running "locked" and only {@link #MATCH_DIRECT_BOOT_AWARE} + * components are returned. Once the user credentials have been presented, + * the user is running "unlocked" and both {@link #MATCH_DIRECT_BOOT_AWARE} + * and {@link #MATCH_DIRECT_BOOT_UNAWARE} components are returned. + * + * @see UserManager#isUserUnlocked() + */ + public static final int MATCH_DIRECT_BOOT_UNAWARE = 0x00040000; + /** + * Querying flag: match components which are direct boot aware in + * the returned info, regardless of the current user state. + *

+ * When neither {@link #MATCH_DIRECT_BOOT_AWARE} nor + * {@link #MATCH_DIRECT_BOOT_UNAWARE} are specified, the default behavior is + * to match only runnable components based on the user state. For example, + * when a user is started but credentials have not been presented yet, the + * user is running "locked" and only {@link #MATCH_DIRECT_BOOT_AWARE} + * components are returned. Once the user credentials have been presented, + * the user is running "unlocked" and both {@link #MATCH_DIRECT_BOOT_AWARE} + * and {@link #MATCH_DIRECT_BOOT_UNAWARE} components are returned. + * + * @see UserManager#isUserUnlocked() + */ + public static final int MATCH_DIRECT_BOOT_AWARE = 0x00080000; + /** + * Querying flag: include only components from applications that are marked + * with {@link ApplicationInfo#FLAG_SYSTEM}. + */ + public static final int MATCH_SYSTEM_ONLY = 0x00100000; + /** + * Internal {@link PackageInfo} flag: include only components on the system image. + * This will not return information on any unbundled update to system components. + * @hide + */ + @SystemApi + public static final int MATCH_FACTORY_ONLY = 0x00200000; + /** + * Allows querying of packages installed for any user, not just the specific one. This flag + * is only meant for use by apps that have INTERACT_ACROSS_USERS permission. + * @hide + */ + @SystemApi + public static final int MATCH_ANY_USER = 0x00400000; + /** + * Combination of MATCH_ANY_USER and MATCH_UNINSTALLED_PACKAGES to mean any known + * package. + * @hide + */ + public static final int MATCH_KNOWN_PACKAGES = MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER; + /** + * Internal {@link PackageInfo} flag: include components that are part of an + * instant app. By default, instant app components are not matched. + * @hide + */ + @SystemApi + public static final int MATCH_INSTANT = 0x00800000; + /** + * Internal {@link PackageInfo} flag: include only components that are exposed to + * instant apps. Matched components may have been either explicitly or implicitly + * exposed. + * @hide + */ + public static final int MATCH_VISIBLE_TO_INSTANT_APP_ONLY = 0x01000000; + /** + * Internal {@link PackageInfo} flag: include only components that have been + * explicitly exposed to instant apps. + * @hide + */ + public static final int MATCH_EXPLICITLY_VISIBLE_ONLY = 0x02000000; + /** + * Internal {@link PackageInfo} flag: include static shared libraries. + * Apps that depend on static shared libs can always access the version + * of the lib they depend on. System/shell/root can access all shared + * libs regardless of dependency but need to explicitly ask for them + * via this flag. + * @hide + */ + public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000; + /** + * Internal flag used to indicate that a system component has done their + * homework and verified that they correctly handle packages and components + * that come and go over time. In particular: + *

    + *
  • Apps installed on external storage, which will appear to be + * uninstalled while the the device is ejected. + *
  • Apps with encryption unaware components, which will appear to not + * exist while the device is locked. + *
+ * + * @see #MATCH_UNINSTALLED_PACKAGES + * @see #MATCH_DIRECT_BOOT_AWARE + * @see #MATCH_DIRECT_BOOT_UNAWARE + * @hide + */ + public static final int MATCH_DEBUG_TRIAGED_MISSING = 0x10000000; + /** + * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when + * resolving an intent that matches the {@code CrossProfileIntentFilter}, + * the current profile will be skipped. Only activities in the target user + * can respond to the intent. + * + * @hide + */ + public static final int SKIP_CURRENT_PROFILE = 0x00000002; + /** + * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: + * activities in the other profiles can respond to the intent only if no activity with + * non-negative priority in current profile can respond to the intent. + * @hide + */ + public static final int ONLY_IF_NO_MATCH_FOUND = 0x00000004; + /** @hide */ + @IntDef(prefix = { "PERMISSION_" }, value = { + PERMISSION_GRANTED, + PERMISSION_DENIED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionResult {} + /** + * Permission check result: this is returned by {@link #checkPermission} + * if the permission has been granted to the given package. + */ + public static final int PERMISSION_GRANTED = 0; + /** + * Permission check result: this is returned by {@link #checkPermission} + * if the permission has not been granted to the given package. + */ + public static final int PERMISSION_DENIED = -1; + /** @hide */ + @IntDef(prefix = { "SIGNATURE_" }, value = { + SIGNATURE_MATCH, + SIGNATURE_NEITHER_SIGNED, + SIGNATURE_FIRST_NOT_SIGNED, + SIGNATURE_SECOND_NOT_SIGNED, + SIGNATURE_NO_MATCH, + SIGNATURE_UNKNOWN_PACKAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SignatureResult {} + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if all signatures on the two packages match. + */ + public static final int SIGNATURE_MATCH = 0; + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if neither of the two packages is signed. + */ + public static final int SIGNATURE_NEITHER_SIGNED = 1; + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if the first package is not signed but the second is. + */ + public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if the second package is not signed but the first is. + */ + public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if not all signatures on both packages match. + */ + public static final int SIGNATURE_NO_MATCH = -3; + /** + * Signature check result: this is returned by {@link #checkSignatures} + * if either of the packages are not valid. + */ + public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; + /** @hide */ + @IntDef(prefix = { "COMPONENT_ENABLED_STATE_" }, value = { + COMPONENT_ENABLED_STATE_DEFAULT, + COMPONENT_ENABLED_STATE_ENABLED, + COMPONENT_ENABLED_STATE_DISABLED, + COMPONENT_ENABLED_STATE_DISABLED_USER, + COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnabledState {} + /** + * Flag for {@link #setApplicationEnabledSetting(String, int, int)} and + * {@link #setComponentEnabledSetting(ComponentName, int, int)}: This + * component or application is in its default enabled state (as specified in + * its manifest). + *

+ * Explicitly setting the component state to this value restores it's + * enabled state to whatever is set in the manifest. + */ + public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; + /** + * Flag for {@link #setApplicationEnabledSetting(String, int, int)} + * and {@link #setComponentEnabledSetting(ComponentName, int, int)}: This + * component or application has been explictily enabled, regardless of + * what it has specified in its manifest. + */ + public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; + /** + * Flag for {@link #setApplicationEnabledSetting(String, int, int)} + * and {@link #setComponentEnabledSetting(ComponentName, int, int)}: This + * component or application has been explicitly disabled, regardless of + * what it has specified in its manifest. + */ + public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; + /** + * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: The + * user has explicitly disabled the application, regardless of what it has + * specified in its manifest. Because this is due to the user's request, + * they may re-enable it if desired through the appropriate system UI. This + * option currently cannot be used with + * {@link #setComponentEnabledSetting(ComponentName, int, int)}. + */ + public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; + /** + * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: This + * application should be considered, until the point where the user actually + * wants to use it. This means that it will not normally show up to the user + * (such as in the launcher), but various parts of the user interface can + * use {@link #GET_DISABLED_UNTIL_USED_COMPONENTS} to still see it and allow + * the user to select it (as for example an IME, device admin, etc). Such code, + * once the user has selected the app, should at that point also make it enabled. + * This option currently can not be used with + * {@link #setComponentEnabledSetting(ComponentName, int, int)}. + */ + public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; + /** @hide */ + @IntDef(flag = true, prefix = { "INSTALL_" }, value = { + INSTALL_FORWARD_LOCK, + INSTALL_REPLACE_EXISTING, + INSTALL_ALLOW_TEST, + INSTALL_EXTERNAL, + INSTALL_INTERNAL, + INSTALL_FROM_ADB, + INSTALL_ALL_USERS, + INSTALL_ALLOW_DOWNGRADE, + INSTALL_GRANT_RUNTIME_PERMISSIONS, + INSTALL_FORCE_VOLUME_UUID, + INSTALL_FORCE_PERMISSION_PROMPT, + INSTALL_INSTANT_APP, + INSTALL_DONT_KILL_APP, + INSTALL_FORCE_SDK, + INSTALL_FULL_APP, + INSTALL_ALLOCATE_AGGRESSIVE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InstallFlags {} + /** + * Flag parameter for {@link #installPackage} to indicate that this package + * should be installed as forward locked, i.e. only the app itself should + * have access to its code and non-resource assets. + * + * @deprecated new installs into ASEC containers are no longer supported. + * @hide + */ + @Deprecated + public static final int INSTALL_FORWARD_LOCK = 0x00000001; + /** + * Flag parameter for {@link #installPackage} to indicate that you want to + * replace an already installed package, if one exists. + * + * @hide + */ + public static final int INSTALL_REPLACE_EXISTING = 0x00000002; + /** + * Flag parameter for {@link #installPackage} to indicate that you want to + * allow test packages (those that have set android:testOnly in their + * manifest) to be installed. + * @hide + */ + public static final int INSTALL_ALLOW_TEST = 0x00000004; + /** + * Flag parameter for {@link #installPackage} to indicate that this package + * must be installed to an ASEC on a {@link VolumeInfo#TYPE_PUBLIC}. + * + * @deprecated new installs into ASEC containers are no longer supported; + * use adoptable storage instead. + * @hide + */ + @Deprecated + public static final int INSTALL_EXTERNAL = 0x00000008; + /** + * Flag parameter for {@link #installPackage} to indicate that this package + * must be installed to internal storage. + * + * @hide + */ + public static final int INSTALL_INTERNAL = 0x00000010; + /** + * Flag parameter for {@link #installPackage} to indicate that this install + * was initiated via ADB. + * + * @hide + */ + public static final int INSTALL_FROM_ADB = 0x00000020; + /** + * Flag parameter for {@link #installPackage} to indicate that this install + * should immediately be visible to all users. + * + * @hide + */ + public static final int INSTALL_ALL_USERS = 0x00000040; + /** + * Flag parameter for {@link #installPackage} to indicate that it is okay + * to install an update to an app where the newly installed app has a lower + * version code than the currently installed app. This is permitted only if + * the currently installed app is marked debuggable. + * + * @hide + */ + public static final int INSTALL_ALLOW_DOWNGRADE = 0x00000080; + /** + * Flag parameter for {@link #installPackage} to indicate that all runtime + * permissions should be granted to the package. If {@link #INSTALL_ALL_USERS} + * is set the runtime permissions will be granted to all users, otherwise + * only to the owner. + * + * @hide + */ + public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100; + /** {@hide} */ + public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200; + /** + * Flag parameter for {@link #installPackage} to indicate that we always want to force + * the prompt for permission approval. This overrides any special behaviour for internal + * components. + * + * @hide + */ + public static final int INSTALL_FORCE_PERMISSION_PROMPT = 0x00000400; + /** + * Flag parameter for {@link #installPackage} to indicate that this package is + * to be installed as a lightweight "ephemeral" app. + * + * @hide + */ + public static final int INSTALL_INSTANT_APP = 0x00000800; + /** + * Flag parameter for {@link #installPackage} to indicate that this package contains + * a feature split to an existing application and the existing application should not + * be killed during the installation process. + * + * @hide + */ + public static final int INSTALL_DONT_KILL_APP = 0x00001000; + /** + * Flag parameter for {@link #installPackage} to indicate that this package is an + * upgrade to a package that refers to the SDK via release letter. + * + * @hide + */ + public static final int INSTALL_FORCE_SDK = 0x00002000; + /** + * Flag parameter for {@link #installPackage} to indicate that this package is + * to be installed as a heavy weight app. This is fundamentally the opposite of + * {@link #INSTALL_INSTANT_APP}. + * + * @hide + */ + public static final int INSTALL_FULL_APP = 0x00004000; + /** + * Flag parameter for {@link #installPackage} to indicate that this package + * is critical to system health or security, meaning the system should use + * {@link StorageManager#FLAG_ALLOCATE_AGGRESSIVE} internally. + * + * @hide + */ + public static final int INSTALL_ALLOCATE_AGGRESSIVE = 0x00008000; + /** + * Flag parameter for {@link #installPackage} to indicate that this package + * is a virtual preload. + * + * @hide + */ + public static final int INSTALL_VIRTUAL_PRELOAD = 0x00010000; + /** @hide */ + @IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = { + DONT_KILL_APP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnabledFlags {} + /** + * Flag parameter for + * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate + * that you don't want to kill the app containing the component. Be careful when you set this + * since changing component states can make the containing application's behavior unpredictable. + */ + public static final int DONT_KILL_APP = 0x00000001; + /** @hide */ + @IntDef(prefix = { "INSTALL_REASON_" }, value = { + INSTALL_REASON_UNKNOWN, + INSTALL_REASON_POLICY, + INSTALL_REASON_DEVICE_RESTORE, + INSTALL_REASON_DEVICE_SETUP, + INSTALL_REASON_USER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InstallReason {} + /** + * Code indicating that the reason for installing this package is unknown. + */ + public static final int INSTALL_REASON_UNKNOWN = 0; + /** + * Code indicating that this package was installed due to enterprise policy. + */ + public static final int INSTALL_REASON_POLICY = 1; + /** + * Code indicating that this package was installed as part of restoring from another device. + */ + public static final int INSTALL_REASON_DEVICE_RESTORE = 2; + /** + * Code indicating that this package was installed as part of device setup. + */ + public static final int INSTALL_REASON_DEVICE_SETUP = 3; + /** + * Code indicating that the package installation was initiated by the user. + */ + public static final int INSTALL_REASON_USER = 4; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} on success. + * + * @hide + */ + @SystemApi + public static final int INSTALL_SUCCEEDED = 1; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the package is already installed. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the package archive file is invalid. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_INVALID_APK = -2; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the URI passed in is invalid. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_INVALID_URI = -3; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the package manager service found that + * the device didn't have enough storage space to install the app. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if a package is already installed with + * the same name. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the requested shared user does not + * exist. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_NO_SHARED_USER = -6; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if a previously installed package of the + * same name has a different signature than the new package (and the old + * package's data was not removed). + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package is requested a shared + * user which is already installed on the device and does not have matching + * signature. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package uses a shared library + * that is not available. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package uses a shared library + * that is not available. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package failed while + * optimizing and validating its dex files, either because there was not + * enough storage or the validation failed. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_DEXOPT = -11; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package failed because the + * current SDK version is older than that required by the package. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_OLDER_SDK = -12; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package failed because it + * contains a content provider with the same authority as a provider already + * installed in the system. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package failed because the + * current SDK version is newer than that required by the package. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_NEWER_SDK = -14; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package failed because it has + * specified that it is a test-only package and the caller has not supplied + * the {@link #INSTALL_ALLOW_TEST} flag. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_TEST_ONLY = -15; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the package being installed contains + * native code, but none that is compatible with the device's CPU_ABI. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package uses a feature that is + * not available. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_MISSING_FEATURE = -17; + // ------ Errors related to sdcard + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if a secure container mount point + * couldn't be accessed on external media. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package couldn't be installed + * in the specified install location. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package couldn't be installed + * in the specified install location because the media is not available. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package couldn't be installed + * because the verification timed out. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package couldn't be installed + * because the verification did not succeed. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the package changed from what the + * calling program expected. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package is assigned a + * different UID than it previously held. + * + * @hide + */ + public static final int INSTALL_FAILED_UID_CHANGED = -24; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package has an older version + * code than the currently installed package. + * + * @hide + */ + public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the old package has target SDK high + * enough to support runtime permission and the new package has target SDK + * low enough to not support runtime permissions. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26; + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} if the new package attempts to downgrade the + * target sandbox version of the app. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser was given a path that is + * not a file, or does not end with the expected '.apk' extension. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser was unable to retrieve the + * AndroidManifest.xml file. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser encountered an unexpected + * exception. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser did not find any + * certificates in the .apk. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser found inconsistent + * certificates on the files in the .apk. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser encountered a + * CertificateEncodingException in one of the files in the .apk. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser encountered a bad or + * missing package name in the manifest. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser encountered a bad shared + * user id name in the manifest. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser encountered some structural + * problem in the manifest. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} if the parser did not find any actionable + * tags (instrumentation or application) in the manifest. + * + * @hide + */ + @SystemApi + public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; + /** + * Installation failed return code: this is passed to the + * {@link IPackageInstallObserver} if the system failed to install the + * package because of system issues. + * + * @hide + */ + @SystemApi + public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; + /** + * Installation failed return code: this is passed to the + * {@link IPackageInstallObserver} if the system failed to install the + * package because the user is restricted from installing apps. + * + * @hide + */ + public static final int INSTALL_FAILED_USER_RESTRICTED = -111; + /** + * Installation failed return code: this is passed to the + * {@link IPackageInstallObserver} if the system failed to install the + * package because it is attempting to define a permission that is already + * defined by some existing package. + *

+ * The package name of the app which has already defined the permission is + * passed to a {@link PackageInstallObserver}, if any, as the + * {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string extra; and the name of the + * permission being redefined is passed in the + * {@link #EXTRA_FAILURE_EXISTING_PERMISSION} string extra. + * + * @hide + */ + public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112; + /** + * Installation failed return code: this is passed to the + * {@link IPackageInstallObserver} if the system failed to install the + * package because its packaged native code did not match any of the ABIs + * supported by the system. + * + * @hide + */ + public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -113; + /** + * Internal return code for NativeLibraryHelper methods to indicate that the package + * being processed did not contain any native code. This is placed here only so that + * it can belong to the same value space as the other install failure codes. + * + * @hide + */ + public static final int NO_NATIVE_LIBRARIES = -114; + /** {@hide} */ + public static final int INSTALL_FAILED_ABORTED = -115; + /** + * Installation failed return code: instant app installs are incompatible with some + * other installation flags supplied for the operation; or other circumstances such + * as trying to upgrade a system app via an instant app install. + * @hide + */ + public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116; + /** @hide */ + @IntDef(flag = true, prefix = { "DELETE_" }, value = { + DELETE_KEEP_DATA, + DELETE_ALL_USERS, + DELETE_SYSTEM_APP, + DELETE_DONT_KILL_APP, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeleteFlags {} + /** + * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the + * package's data directory. + * + * @hide + */ + public static final int DELETE_KEEP_DATA = 0x00000001; + /** + * Flag parameter for {@link #deletePackage} to indicate that you want the + * package deleted for all users. + * + * @hide + */ + public static final int DELETE_ALL_USERS = 0x00000002; + /** + * Flag parameter for {@link #deletePackage} to indicate that, if you are calling + * uninstall on a system that has been updated, then don't do the normal process + * of uninstalling the update and rolling back to the older system version (which + * needs to happen for all users); instead, just mark the app as uninstalled for + * the current user. + * + * @hide + */ + public static final int DELETE_SYSTEM_APP = 0x00000004; + /** + * Flag parameter for {@link #deletePackage} to indicate that, if you are calling + * uninstall on a package that is replaced to provide new feature splits, the + * existing application should not be killed during the removal process. + * + * @hide + */ + public static final int DELETE_DONT_KILL_APP = 0x00000008; + /** + * Return code for when package deletion succeeds. This is passed to the + * {@link IPackageDeleteObserver} if the system succeeded in deleting the + * package. + * + * @hide + */ + public static final int DELETE_SUCCEEDED = 1; + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} if the system failed to delete the package + * for an unspecified reason. + * + * @hide + */ + public static final int DELETE_FAILED_INTERNAL_ERROR = -1; + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} if the system failed to delete the package + * because it is the active DevicePolicy manager. + * + * @hide + */ + public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} if the system failed to delete the package + * since the user is restricted. + * + * @hide + */ + public static final int DELETE_FAILED_USER_RESTRICTED = -3; + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} if the system failed to delete the package + * because a profile or device owner has marked the package as + * uninstallable. + * + * @hide + */ + public static final int DELETE_FAILED_OWNER_BLOCKED = -4; + /** {@hide} */ + public static final int DELETE_FAILED_ABORTED = -5; + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} if the system failed to delete the package + * because the packge is a shared library used by other installed packages. + * {@hide} */ + public static final int DELETE_FAILED_USED_SHARED_LIBRARY = -6; + /** + * Return code that is passed to the {@link IPackageMoveObserver} when the + * package has been successfully moved by the system. + * + * @hide + */ + public static final int MOVE_SUCCEEDED = -100; + /** + * Error code that is passed to the {@link IPackageMoveObserver} when the + * package hasn't been successfully moved by the system because of + * insufficient memory on specified media. + * + * @hide + */ + public static final int MOVE_FAILED_INSUFFICIENT_STORAGE = -1; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package doesn't exist. + * + * @hide + */ + public static final int MOVE_FAILED_DOESNT_EXIST = -2; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package cannot be moved since its a system package. + * + * @hide + */ + public static final int MOVE_FAILED_SYSTEM_PACKAGE = -3; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package cannot be moved since its forward locked. + * + * @hide + */ + public static final int MOVE_FAILED_FORWARD_LOCKED = -4; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package cannot be moved to the specified location. + * + * @hide + */ + public static final int MOVE_FAILED_INVALID_LOCATION = -5; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package cannot be moved to the specified location. + * + * @hide + */ + public static final int MOVE_FAILED_INTERNAL_ERROR = -6; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package already has an operation pending in the queue. + * + * @hide + */ + public static final int MOVE_FAILED_OPERATION_PENDING = -7; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if the + * specified package cannot be moved since it contains a device admin. + * + * @hide + */ + public static final int MOVE_FAILED_DEVICE_ADMIN = -8; + /** + * Error code that is passed to the {@link IPackageMoveObserver} if system does not allow + * non-system apps to be moved to internal storage. + * + * @hide + */ + public static final int MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL = -9; + /** @hide */ + public static final int MOVE_FAILED_LOCKED_USER = -10; + /** + * Flag parameter for {@link #movePackage} to indicate that + * the package should be moved to internal storage if its + * been installed on external media. + * @hide + */ + @Deprecated + public static final int MOVE_INTERNAL = 0x00000001; + /** + * Flag parameter for {@link #movePackage} to indicate that + * the package should be moved to external media. + * @hide + */ + @Deprecated + public static final int MOVE_EXTERNAL_MEDIA = 0x00000002; + /** {@hide} */ + public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID"; + /** + * Usable by the required verifier as the {@code verificationCode} argument + * for {@link PackageManager#verifyPendingInstall} to indicate that it will + * allow the installation to proceed without any of the optional verifiers + * needing to vote. + * + * @hide + */ + public static final int VERIFICATION_ALLOW_WITHOUT_SUFFICIENT = 2; + /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyPendingInstall} to indicate that the calling + * package verifier allows the installation to proceed. + */ + public static final int VERIFICATION_ALLOW = 1; + /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyPendingInstall} to indicate the calling + * package verifier does not vote to allow the installation to proceed. + */ + public static final int VERIFICATION_REJECT = -1; + /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyIntentFilter} to indicate that the calling + * IntentFilter Verifier confirms that the IntentFilter is verified. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; + /** + * Used as the {@code verificationCode} argument for + * {@link PackageManager#verifyIntentFilter} to indicate that the calling + * IntentFilter Verifier confirms that the IntentFilter is NOT verified. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; + /** + * Internal status code to indicate that an IntentFilter verification result is not specified. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; + /** + * Used as the {@code status} argument for + * {@link #updateIntentVerificationStatusAsUser} to indicate that the User + * will always be prompted the Intent Disambiguation Dialog if there are two + * or more Intent resolved for the IntentFilter's domain(s). + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; + /** + * Used as the {@code status} argument for + * {@link #updateIntentVerificationStatusAsUser} to indicate that the User + * will never be prompted the Intent Disambiguation Dialog if there are two + * or more resolution of the Intent. The default App for the domain(s) + * specified in the IntentFilter will also ALWAYS be used. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; + /** + * Used as the {@code status} argument for + * {@link #updateIntentVerificationStatusAsUser} to indicate that the User + * may be prompted the Intent Disambiguation Dialog if there are two or more + * Intent resolved. The default App for the domain(s) specified in the + * IntentFilter will also NEVER be presented to the User. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; + /** + * Used as the {@code status} argument for + * {@link #updateIntentVerificationStatusAsUser} to indicate that this app + * should always be considered as an ambiguous candidate for handling the + * matching Intent even if there are other candidate apps in the "always" + * state. Put another way: if there are any 'always ask' apps in a set of + * more than one candidate app, then a disambiguation is *always* presented + * even if there is another candidate app with the 'always' state. + * + * @hide + */ + @SystemApi + public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; + /** + * Can be used as the {@code millisecondsToDelay} argument for + * {@link PackageManager#extendVerificationTimeout}. This is the + * maximum time {@code PackageManager} waits for the verification + * agent to return (in milliseconds). + */ + public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's + * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or + * lag in sound input or output. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes at least one form of audio + * output, such as speakers, audio jack or streaming over bluetooth + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has professional audio level of functionality and performance. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device is capable of communicating with + * other devices via Bluetooth. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device is capable of communicating with + * other devices via Bluetooth Low Energy radio. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a camera facing away + * from the screen. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA = "android.hardware.camera"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's camera supports auto-focus. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has at least one camera pointing in + * some direction, or can support an external camera being connected to it. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device can support having an external camera connected to it. + * The external camera may not always be connected or available to applications to use. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_EXTERNAL = "android.hardware.camera.external"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's camera supports flash. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a front facing camera. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL full hardware} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR manual sensor} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR = + "android.hardware.camera.capability.manual_sensor"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING manual post-processing} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING = + "android.hardware.camera.capability.manual_post_processing"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_RAW RAW} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_CAPABILITY_RAW = + "android.hardware.camera.capability.raw"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device is capable of communicating with + * consumer IR devices. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; + /** {@hide} */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CTS = "android.software.cts"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports one or more methods of + * reporting current location. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LOCATION = "android.hardware.location"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a Global Positioning System + * receiver and can report precise location. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device can report location with coarse + * accuracy using a network-based geolocation system. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LOCATION_NETWORK = "android.hardware.location.network"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's + * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns + * true. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_RAM_LOW = "android.hardware.ram.low"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's + * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns + * false. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_RAM_NORMAL = "android.hardware.ram.normal"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device can record audio via a + * microphone. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MICROPHONE = "android.hardware.microphone"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device can communicate using Near-Field + * Communications (NFC). + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC = "android.hardware.nfc"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports host- + * based NFC card emulation. + * + * TODO remove when depending apps have moved to new constant. + * @hide + * @deprecated + */ + @Deprecated + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC_HCE = "android.hardware.nfc.hce"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports host- + * based NFC card emulation. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports host- + * based NFC-F card emulation. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports any + * one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION}, + * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features. + * + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports the OpenGL ES + * + * Android Extension Pack. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan native API + * will enumerate at least one {@code VkPhysicalDevice}, and the feature version will indicate + * what level of optional hardware features limits it supports. + *

+ * Level 0 includes the base Vulkan requirements as well as: + *

  • {@code VkPhysicalDeviceFeatures::textureCompressionETC2}
+ *

+ * Level 1 additionally includes: + *

    + *
  • {@code VkPhysicalDeviceFeatures::fullDrawIndexUint32}
  • + *
  • {@code VkPhysicalDeviceFeatures::imageCubeArray}
  • + *
  • {@code VkPhysicalDeviceFeatures::independentBlend}
  • + *
  • {@code VkPhysicalDeviceFeatures::geometryShader}
  • + *
  • {@code VkPhysicalDeviceFeatures::tessellationShader}
  • + *
  • {@code VkPhysicalDeviceFeatures::sampleRateShading}
  • + *
  • {@code VkPhysicalDeviceFeatures::textureCompressionASTC_LDR}
  • + *
  • {@code VkPhysicalDeviceFeatures::fragmentStoresAndAtomics}
  • + *
  • {@code VkPhysicalDeviceFeatures::shaderImageGatherExtended}
  • + *
  • {@code VkPhysicalDeviceFeatures::shaderUniformBufferArrayDynamicIndexing}
  • + *
  • {@code VkPhysicalDeviceFeatures::shaderSampledImageArrayDynamicIndexing}
  • + *
+ */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan native API + * will enumerate at least one {@code VkPhysicalDevice}, and the feature version will indicate + * what level of optional compute features that device supports beyond the Vulkan 1.0 + * requirements. + *

+ * Compute level 0 indicates: + *

    + *
  • The {@code VK_KHR_variable_pointers} extension and + * {@code VkPhysicalDeviceVariablePointerFeaturesKHR::variablePointers} feature are + supported.
  • + *
  • {@code VkPhysicalDeviceLimits::maxPerStageDescriptorStorageBuffers} is at least 16.
  • + *
+ */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: The version of this feature indicates the highest + * {@code VkPhysicalDeviceProperties::apiVersion} supported by the physical devices that support + * the hardware level indicated by {@link #FEATURE_VULKAN_HARDWARE_LEVEL}. The feature version + * uses the same encoding as Vulkan version numbers: + *
    + *
  • Major version number in bits 31-22
  • + *
  • Minor version number in bits 21-12
  • + *
  • Patch version number in bits 11-0
  • + *
+ */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes broadcast radio tuner. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes an accelerometer. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a barometer (air + * pressure sensor.) + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a magnetometer (compass). + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a gyroscope. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a light sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a proximity sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a hardware step counter. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a hardware step detector. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a heart rate monitor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The heart rate sensor on this device is an Electrocardiogram. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_HEART_RATE_ECG = + "android.hardware.sensor.heartrate.ecg"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a relative humidity sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY = + "android.hardware.sensor.relative_humidity"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes an ambient temperature sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = + "android.hardware.sensor.ambient_temperature"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports high fidelity sensor processing + * capabilities. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_HIFI_SENSORS = + "android.hardware.sensor.hifi_sensors"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a telephony radio with data + * communication support. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a CDMA telephony stack. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a GSM telephony stack. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports telephony carrier restriction mechanism. + * + *

Devices declaring this feature must have an implementation of the + * {@link android.telephony.TelephonyManager#getAllowedCarriers} and + * {@link android.telephony.TelephonyManager#setAllowedCarriers}. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_CARRIERLOCK = + "android.hardware.telephony.carrierlock"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports embedded subscriptions on eUICCs. + * TODO(b/35851809): Make this public. + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports cell-broadcast reception using the MBMS APIs. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports connecting to USB devices + * as the USB host. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_USB_HOST = "android.hardware.usb.host"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports connecting to USB accessories. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The SIP API is enabled on the device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SIP = "android.software.sip"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports SIP-based VOIP. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SIP_VOIP = "android.software.sip.voip"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The Connection Service API is enabled on the device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's display has a touch screen. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's touch screen supports + * multitouch sufficient for basic two-finger gesture detection. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's touch screen is capable of + * tracking two or more fingers fully independently. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's touch screen is capable of + * tracking a full hand of fingers fully independently -- that is, 5 or + * more simultaneous independent pointers. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device does not have a touch screen, but + * does support touch emulation for basic events. For instance, the + * device might use a mouse or remote control to drive a cursor, and + * emulate basic touch pointer events like down, up, drag, etc. All + * devices that support android.hardware.touchscreen or a sub-feature are + * presumed to also support faketouch. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FAKETOUCH = "android.hardware.faketouch"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device does not have a touch screen, but + * does support touch emulation for basic events that supports distinct + * tracking of two or more fingers. This is an extension of + * {@link #FEATURE_FAKETOUCH} for input devices with this capability. Note + * that unlike a distinct multitouch screen as defined by + * {@link #FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT}, these kinds of input + * devices will not actually provide full two-finger gestures since the + * input is being transformed to cursor movement on the screen. That is, + * single finger gestures will move a cursor; two-finger swipes will + * result in single-finger touch events; other two-finger gestures will + * result in the corresponding two-finger touch event. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device does not have a touch screen, but + * does support touch emulation for basic events that supports tracking + * a hand of fingers (5 or more fingers) fully independently. + * This is an extension of + * {@link #FEATURE_FAKETOUCH} for input devices with this capability. Note + * that unlike a multitouch screen as defined by + * {@link #FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND}, not all two finger + * gestures can be detected due to the limitations described for + * {@link #FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports portrait orientation + * screens. For backwards compatibility, you can assume that if neither + * this nor {@link #FEATURE_SCREEN_LANDSCAPE} is set then the device supports + * both portrait and landscape. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports landscape orientation + * screens. For backwards compatibility, you can assume that if neither + * this nor {@link #FEATURE_SCREEN_PORTRAIT} is set then the device supports + * both portrait and landscape. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports live wallpapers. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports app widgets. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets"; + /** + * @hide + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports + * {@link android.service.voice.VoiceInteractionService} and + * {@link android.app.VoiceInteractor}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports a home screen that is replaceable + * by third party applications. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_HOME_SCREEN = "android.software.home_screen"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports adding new input methods implemented + * with the {@link android.inputmethodservice.InputMethodService} API. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_INPUT_METHODS = "android.software.input_methods"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports device policy enforcement via device admins. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports leanback UI. This is + * typically used in a living room television experience, but is a software + * feature unlike {@link #FEATURE_TELEVISION}. Devices running with this + * feature will use resources associated with the "television" UI mode. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LEANBACK = "android.software.leanback"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports only leanback UI. Only + * applications designed for this experience should be run, though this is + * not enforced by the system. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports live TV and can display + * contents from TV inputs implemented with the + * {@link android.media.tv.TvInputService} API. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LIVE_TV = "android.software.live_tv"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports WiFi (802.11) networking. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WIFI = "android.hardware.wifi"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports Wi-Fi Direct networking. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports Wi-Fi Aware. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports Wi-Fi Passpoint and all + * Passpoint related APIs in {@link WifiManager} are supported. Refer to + * {@link WifiManager#addOrUpdatePasspointConfiguration} for more info. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports LoWPAN networking. + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LOWPAN = "android.hardware.lowpan"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to showing UI + * on a vehicle headunit. A headunit here is defined to be inside a + * vehicle that may or may not be moving. A headunit uses either a + * primary display in the center console and/or additional displays in + * the instrument cluster or elsewhere in the vehicle. Headunit display(s) + * have limited size and resolution. The user will likely be focused on + * driving so limiting driver distraction is a primary concern. User input + * can be a variety of hard buttons, touch, rotary controllers and even mouse- + * like interfaces. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to showing UI + * on a television. Television here is defined to be a typical living + * room television experience: displayed on a big screen, where the user + * is sitting far away from it, and the dominant form of input will be + * something like a DPAD, not through touch or mouse. + * @deprecated use {@link #FEATURE_LEANBACK} instead. + */ + @Deprecated + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEVISION = "android.hardware.type.television"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to showing UI + * on a watch. A watch here is defined to be a device worn on the body, perhaps on + * the wrist. The user is very close when interacting with the device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WATCH = "android.hardware.type.watch"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device for IoT and may not have an UI. An embedded + * device is defined as a full stack Android device with or without a display and no + * user-installable apps. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to be primarily used + * with keyboard, mouse or touchpad. This includes traditional desktop + * computers, laptops and variants such as convertibles or detachables. + * Due to the larger screen, the device will most likely use the + * {@link #FEATURE_FREEFORM_WINDOW_MANAGEMENT} feature as well. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_PC = "android.hardware.type.pc"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports printing. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_PRINTING = "android.software.print"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports {@link android.companion.CompanionDeviceManager#associate associating} + * with devices via {@link android.companion.CompanionDeviceManager}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_COMPANION_DEVICE_SETUP + = "android.software.companion_device_setup"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device can perform backup and restore operations on installed applications. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_BACKUP = "android.software.backup"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports freeform window management. + * Windows have title bars and can be moved and resized. + */ + // If this feature is present, you also need to set + // com.android.internal.R.config_freeformWindowManagement to true in your configuration overlay. + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT + = "android.software.freeform_window_management"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports picture-in-picture multi-window mode. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports running activities on secondary displays. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS + = "android.software.activities_on_secondary_displays"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports creating secondary users and managed profiles via + * {@link DevicePolicyManager}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MANAGED_USERS = "android.software.managed_users"; + /** + * @hide + * TODO: Remove after dependencies updated b/17392243 + */ + public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports verified boot. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports secure removal of users. When a user is deleted the data associated + * with that user is securely deleted and no longer available. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SECURELY_REMOVES_USERS + = "android.software.securely_removes_users"; + /** {@hide} */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FILE_BASED_ENCRYPTION + = "android.software.file_based_encryption"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a full implementation of the android.webkit.* APIs. Devices + * lacking this feature will not have a functioning WebView implementation. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WEBVIEW = "android.software.webview"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This device supports ethernet. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_ETHERNET = "android.hardware.ethernet"; + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This device supports HDMI-CEC. + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has all of the inputs necessary to be considered a compatible game controller, or + * includes a compatible game controller in the box. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_GAMEPAD = "android.hardware.gamepad"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a full implementation of the android.media.midi.* APIs. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MIDI = "android.software.midi"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device implements an optimized mode for virtual reality (VR) applications that handles + * stereoscopic rendering of notifications, and disables most monocular system UI components + * while a VR application has user focus. + * Devices declaring this feature must include an application implementing a + * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via + * {@link android.app.Activity#setVrModeEnabled}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VR_MODE = "android.software.vr.mode"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device implements {@link #FEATURE_VR_MODE} but additionally meets extra CDD requirements + * to provide a high-quality VR experience. In general, devices declaring this feature will + * additionally: + *

    + *
  • Deliver consistent performance at a high framerate over an extended period of time + * for typical VR application CPU/GPU workloads with a minimal number of frame drops for VR + * applications that have called + * {@link android.view.Window#setSustainedPerformanceMode}.
  • + *
  • Implement {@link #FEATURE_HIFI_SENSORS} and have a low sensor latency.
  • + *
  • Include optimizations to lower display persistence while running VR applications.
  • + *
  • Implement an optimized render path to minimize latency to draw to the device's main + * display.
  • + *
  • Include the following EGL extensions: EGL_ANDROID_create_native_client_buffer, + * EGL_ANDROID_front_buffer_auto_refresh, EGL_EXT_protected_content, + * EGL_KHR_mutable_render_buffer, EGL_KHR_reusable_sync, and EGL_KHR_wait_sync.
  • + *
  • Provide at least one CPU core that is reserved for use solely by the top, foreground + * VR application process for critical render threads while such an application is + * running.
  • + *
+ */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE + = "android.hardware.vr.high_performance"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports autofill of user credentials, addresses, credit cards, etc + * via integration with {@link android.service.autofill.AutofillService autofill + * providers}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUTOFILL = "android.software.autofill"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device implements headtracking suitable for a VR device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; + /** + * Action to external storage service to clean out removed apps. + * @hide + */ + public static final String ACTION_CLEAN_EXTERNAL_STORAGE + = "android.content.pm.CLEAN_EXTERNAL_STORAGE"; + /** + * Extra field name for the URI to a verification file. Passed to a package + * verifier. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_URI = "android.content.pm.extra.VERIFICATION_URI"; + /** + * Extra field name for the ID of a package pending verification. Passed to + * a package verifier and is used to call back to + * {@link PackageManager#verifyPendingInstall(int, int)} + */ + public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID"; + /** + * Extra field name for the package identifier which is trying to install + * the package. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_INSTALLER_PACKAGE + = "android.content.pm.extra.VERIFICATION_INSTALLER_PACKAGE"; + /** + * Extra field name for the requested install flags for a package pending + * verification. Passed to a package verifier. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_INSTALL_FLAGS + = "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS"; + /** + * Extra field name for the uid of who is requesting to install + * the package. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_INSTALLER_UID + = "android.content.pm.extra.VERIFICATION_INSTALLER_UID"; + /** + * Extra field name for the package name of a package pending verification. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_PACKAGE_NAME + = "android.content.pm.extra.VERIFICATION_PACKAGE_NAME"; + /** + * Extra field name for the result of a verification, either + * {@link #VERIFICATION_ALLOW}, or {@link #VERIFICATION_REJECT}. + * Passed to package verifiers after a package is verified. + */ + public static final String EXTRA_VERIFICATION_RESULT + = "android.content.pm.extra.VERIFICATION_RESULT"; + /** + * Extra field name for the version code of a package pending verification. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_VERSION_CODE + = "android.content.pm.extra.VERIFICATION_VERSION_CODE"; + /** + * Extra field name for the ID of a intent filter pending verification. + * Passed to an intent filter verifier and is used to call back to + * {@link #verifyIntentFilter} + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID"; + /** + * Extra field name for the scheme used for an intent filter pending verification. Passed to + * an intent filter verifier and is used to construct the URI to verify against. + * + * Usually this is "https" + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME"; + /** + * Extra field name for the host names to be used for an intent filter pending verification. + * Passed to an intent filter verifier and is used to construct the URI to verify the + * intent filter. + * + * This is a space delimited list of hosts. + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS"; + /** + * Extra field name for the package name to be used for an intent filter pending verification. + * Passed to an intent filter verifier and is used to check the verification responses coming + * from the hosts. Each host response will need to include the package name of APK containing + * the intent filter. + * + * @hide + */ + public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME + = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME"; + /** + * The action used to request that the user approve a permission request + * from the application. + * + * @hide + */ + @SystemApi + public static final String ACTION_REQUEST_PERMISSIONS = + "android.content.pm.action.REQUEST_PERMISSIONS"; + /** + * The names of the requested permissions. + *

+ * Type: String[] + *

+ * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = + "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; + /** + * The results from the permissions request. + *

+ * Type: int[] of #PermissionResult + *

+ * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS + = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; + /** + * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of + * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides + * the existing definition for the permission. + * @hide + */ + public static final String EXTRA_FAILURE_EXISTING_PACKAGE + = "android.content.pm.extra.FAILURE_EXISTING_PACKAGE"; + /** + * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of + * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the permission that is + * being redundantly defined by the package being installed. + * @hide + */ + public static final String EXTRA_FAILURE_EXISTING_PERMISSION + = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION"; + /** + * Permission flag: The permission is set in its current state + * by the user and apps can still request it at runtime. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_USER_SET = 1 << 0; + /** + * Permission flag: The permission is set in its current state + * by the user and it is fixed, i.e. apps can no longer request + * this permission. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_USER_FIXED = 1 << 1; + /** + * Permission flag: The permission is set in its current state + * by device policy and neither apps nor the user can change + * its state. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2; + /** + * Permission flag: The permission is set in a granted state but + * access to resources it guards is restricted by other means to + * enable revoking a permission on legacy apps that do not support + * runtime permissions. If this permission is upgraded to runtime + * because the app was updated to support runtime permissions, the + * the permission will be revoked in the upgrade process. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 1 << 3; + /** + * Permission flag: The permission is set in its current state + * because the app is a component that is a part of the system. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4; + /** + * Permission flag: The permission is granted by default because it + * enables app functionality that is expected to work out-of-the-box + * for providing a smooth user experience. For example, the phone app + * is expected to have the phone permission. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5; + /** + * Permission flag: The permission has to be reviewed before any of + * the app components can run. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 1 << 6; + /** + * Mask for all permission flags. + * + * @hide + */ + @SystemApi + public static final int MASK_PERMISSION_FLAGS = 0xFF; + /** + * This is a library that contains components apps can invoke. For + * example, a services for apps to bind to, or standard chooser UI, + * etc. This library is versioned and backwards compatible. Clients + * should check its version via {@link android.ext.services.Version + * #getVersionCode()} and avoid calling APIs added in later versions. + * + * @hide + */ + public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; + /** + * This is a library that contains components apps can dynamically + * load. For example, new widgets, helper classes, etc. This library + * is versioned and backwards compatible. Clients should check its + * version via {@link android.ext.shared.Version#getVersionCode()} + * and avoid calling APIs added in later versions. + * + * @hide + */ + public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared"; + /** + * Used when starting a process for an Activity. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_ACTIVITY = 0; + /** + * Used when starting a process for a Service. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_SERVICE = 1; + /** + * Used when moving a Service to the foreground. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE = 2; + /** + * Used when starting a process for a BroadcastReceiver. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER = 3; + /** + * Used when starting a process for a ContentProvider. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_CONTENT_PROVIDER = 4; + /** + * Used when starting a process for a BroadcastReceiver. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_BACKUP = 5; + /** + * Used with Context.getClassLoader() across Android packages. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_CROSS_PACKAGE = 6; + /** + * Used when starting a package within a process for Instrumentation. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_INSTRUMENTATION = 7; + /** + * Total number of usage reasons. + * + * @hide + */ + public static final int NOTIFY_PACKAGE_USE_REASONS_COUNT = 8; + /** + * Constant for specifying the highest installed package version code. + */ + public static final int VERSION_CODE_HIGHEST = -1; + /** + * Retrieve overall information about an application package that is + * installed on the system. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param flags Additional option flags to modify the data returned. + * @return A PackageInfo object containing information about the package. If + * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package + * is not found in the list of installed applications, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) + throws NameNotFoundException; + /** + * Retrieve overall information about an application package that is + * installed on the system. This method can be used for retrieving + * information about packages for which multiple versions can be installed + * at the time. Currently only packages hosting static shared libraries can + * have multiple installed versions. The method can also be used to get info + * for a package that has a single version installed by passing + * {@link #VERSION_CODE_HIGHEST} in the {@link VersionedPackage} + * constructor. + * + * @param versionedPackage The versioned package for which to query. + * @param flags Additional option flags to modify the data returned. + * @return A PackageInfo object containing information about the package. If + * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package + * is not found in the list of installed applications, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract PackageInfo getPackageInfo(VersionedPackage versionedPackage, + @PackageInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve overall information about an application package that is + * installed on the system. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param flags Additional option flags to modify the data returned. + * @param userId The user id. + * @return A PackageInfo object containing information about the package. If + * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package + * is not found in the list of installed applications, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + * @hide + */ + public abstract PackageInfo getPackageInfoAsUser(String packageName, + @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; + /** + * Map from the current package names in use on the device to whatever + * the current canonical name of that package is. + * @param names Array of current names to be mapped. + * @return Returns an array of the same size as the original, containing + * the canonical name for each package. + */ + public abstract String[] currentToCanonicalPackageNames(String[] names); + /** + * Map from a packages canonical name to the current name in use on the device. + * @param names Array of new names to be mapped. + * @return Returns an array of the same size as the original, containing + * the current name for each package. + */ + public abstract String[] canonicalToCurrentPackageNames(String[] names); + /** + * Returns a "good" intent to launch a front-door activity in a package. + * This is used, for example, to implement an "open" button when browsing + * through packages. The current implementation looks first for a main + * activity in the category {@link Intent#CATEGORY_INFO}, and next for a + * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns + * null if neither are found. + * + * @param packageName The name of the package to inspect. + * + * @return A fully-qualified {@link Intent} that can be used to launch the + * main activity in the package. Returns null if the package + * does not contain such an activity, or if packageName is not + * recognized. + */ + public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName); + /** + * Return a "good" intent to launch a front-door Leanback activity in a + * package, for use for example to implement an "open" button when browsing + * through packages. The current implementation will look for a main + * activity in the category {@link Intent#CATEGORY_LEANBACK_LAUNCHER}, or + * return null if no main leanback activities are found. + * + * @param packageName The name of the package to inspect. + * @return Returns either a fully-qualified Intent that can be used to launch + * the main Leanback activity in the package, or null if the package + * does not contain such an activity. + */ + public abstract @Nullable Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName); + /** + * Return an array of all of the POSIX secondary group IDs that have been + * assigned to the given package. + *

+ * Note that the same package may have different GIDs under different + * {@link UserHandle} on the same device. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @return Returns an int array of the assigned GIDs, or null if there are + * none. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract int[] getPackageGids(@NonNull String packageName) + throws NameNotFoundException; + /** + * Return an array of all of the POSIX secondary group IDs that have been + * assigned to the given package. + *

+ * Note that the same package may have different GIDs under different + * {@link UserHandle} on the same device. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @return Returns an int array of the assigned gids, or null if there are + * none. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract int[] getPackageGids(String packageName, @PackageInfoFlags int flags) + throws NameNotFoundException; + /** + * Return the UID associated with the given package name. + *

+ * Note that the same package will have different UIDs under different + * {@link UserHandle} on the same device. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + */ + public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags) + throws NameNotFoundException; + /** + * Return the UID associated with the given package name. + *

+ * Note that the same package will have different UIDs under different + * {@link UserHandle} on the same device. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param userId The user handle identifier to look up the package under. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + * @hide + */ + public abstract int getPackageUidAsUser(String packageName, @UserIdInt int userId) + throws NameNotFoundException; + /** + * Return the UID associated with the given package name. + *

+ * Note that the same package will have different UIDs under different + * {@link UserHandle} on the same device. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param userId The user handle identifier to look up the package under. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + * @hide + */ + public abstract int getPackageUidAsUser(String packageName, @PackageInfoFlags int flags, + @UserIdInt int userId) throws NameNotFoundException; + /** + * Retrieve all of the information we know about a particular permission. + * + * @param name The fully qualified name (i.e. com.google.permission.LOGIN) + * of the permission you are interested in. + * @param flags Additional option flags to modify the data returned. + * @return Returns a {@link PermissionInfo} containing information about the + * permission. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract PermissionInfo getPermissionInfo(String name, @PermissionInfoFlags int flags) + throws NameNotFoundException; + /** + * Query for all of the permissions associated with a particular group. + * + * @param group The fully qualified name (i.e. com.google.permission.LOGIN) + * of the permission group you are interested in. Use null to + * find all of the permissions not associated with a group. + * @param flags Additional option flags to modify the data returned. + * @return Returns a list of {@link PermissionInfo} containing information + * about all of the permissions in the given group. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract List queryPermissionsByGroup(String group, + @PermissionInfoFlags int flags) throws NameNotFoundException; + /** + * Returns true if Permission Review Mode is enabled, false otherwise. + * + * @hide + */ + @TestApi + public abstract boolean isPermissionReviewModeEnabled(); + /** + * Retrieve all of the information we know about a particular group of + * permissions. + * + * @param name The fully qualified name (i.e. + * com.google.permission_group.APPS) of the permission you are + * interested in. + * @param flags Additional option flags to modify the data returned. + * @return Returns a {@link PermissionGroupInfo} containing information + * about the permission. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract PermissionGroupInfo getPermissionGroupInfo(String name, + @PermissionGroupInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve all of the known permission groups in the system. + * + * @param flags Additional option flags to modify the data returned. + * @return Returns a list of {@link PermissionGroupInfo} containing + * information about all of the known permission groups. + */ + public abstract List getAllPermissionGroups( + @PermissionGroupInfoFlags int flags); + /** + * Retrieve all of the information we know about a particular + * package/application. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of an + * application. + * @param flags Additional option flags to modify the data returned. + * @return An {@link ApplicationInfo} containing information about the + * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if + * the package is not found in the list of installed applications, + * the application information is retrieved from the list of + * uninstalled applications (which includes installed applications + * as well as applications with data directory i.e. applications + * which had been deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract ApplicationInfo getApplicationInfo(String packageName, + @ApplicationInfoFlags int flags) throws NameNotFoundException; + /** {@hide} */ + public abstract ApplicationInfo getApplicationInfoAsUser(String packageName, + @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; + /** + * Retrieve all of the information we know about a particular activity + * class. + * + * @param component The full component name (i.e. + * com.google.apps.contacts/com.google.apps.contacts. + * ContactsList) of an Activity class. + * @param flags Additional option flags to modify the data returned. + * @return An {@link ActivityInfo} containing information about the + * activity. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract ActivityInfo getActivityInfo(ComponentName component, + @ComponentInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve all of the information we know about a particular receiver + * class. + * + * @param component The full component name (i.e. + * com.google.apps.calendar/com.google.apps.calendar. + * CalendarAlarm) of a Receiver class. + * @param flags Additional option flags to modify the data returned. + * @return An {@link ActivityInfo} containing information about the + * receiver. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract ActivityInfo getReceiverInfo(ComponentName component, + @ComponentInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve all of the information we know about a particular service class. + * + * @param component The full component name (i.e. + * com.google.apps.media/com.google.apps.media. + * BackgroundPlayback) of a Service class. + * @param flags Additional option flags to modify the data returned. + * @return A {@link ServiceInfo} object containing information about the + * service. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract ServiceInfo getServiceInfo(ComponentName component, + @ComponentInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve all of the information we know about a particular content + * provider class. + * + * @param component The full component name (i.e. + * com.google.providers.media/com.google.providers.media. + * MediaProvider) of a ContentProvider class. + * @param flags Additional option flags to modify the data returned. + * @return A {@link ProviderInfo} object containing information about the + * provider. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract ProviderInfo getProviderInfo(ComponentName component, + @ComponentInfoFlags int flags) throws NameNotFoundException; + /** + * Return a List of all packages that are installed on the device. + * + * @param flags Additional option flags to modify the data returned. + * @return A List of PackageInfo objects, one for each installed package, + * containing information about the package. In the unlikely case + * there are no installed packages, an empty list is returned. If + * flag {@code MATCH_UNINSTALLED_PACKAGES} is set, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + */ + public abstract List getInstalledPackages(@PackageInfoFlags int flags); + /** + * Return a List of all installed packages that are currently holding any of + * the given permissions. + * + * @param flags Additional option flags to modify the data returned. + * @return A List of PackageInfo objects, one for each installed package + * that holds any of the permissions that were provided, containing + * information about the package. If no installed packages hold any + * of the permissions, an empty list is returned. If flag + * {@code MATCH_UNINSTALLED_PACKAGES} is set, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + */ + public abstract List getPackagesHoldingPermissions( + String[] permissions, @PackageInfoFlags int flags); + /** + * Return a List of all packages that are installed on the device, for a + * specific user. + * + * @param flags Additional option flags to modify the data returned. + * @param userId The user for whom the installed packages are to be listed + * @return A List of PackageInfo objects, one for each installed package, + * containing information about the package. In the unlikely case + * there are no installed packages, an empty list is returned. If + * flag {@code MATCH_UNINSTALLED_PACKAGES} is set, the package + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + * @hide + */ + @SystemApi + public abstract List getInstalledPackagesAsUser(@PackageInfoFlags int flags, + @UserIdInt int userId); + /** + * Check whether a particular package has been granted a particular + * permission. + * + * @param permName The name of the permission you are checking for. + * @param pkgName The name of the package you are checking against. + * + * @return If the package has the permission, PERMISSION_GRANTED is + * returned. If it does not have the permission, PERMISSION_DENIED + * is returned. + * + * @see #PERMISSION_GRANTED + * @see #PERMISSION_DENIED + */ + @CheckResult + public abstract @PermissionResult int checkPermission(String permName, String pkgName); + /** + * Checks whether a particular permissions has been revoked for a + * package by policy. Typically the device owner or the profile owner + * may apply such a policy. The user cannot grant policy revoked + * permissions, hence the only way for an app to get such a permission + * is by a policy change. + * + * @param permName The name of the permission you are checking for. + * @param pkgName The name of the package you are checking against. + * + * @return Whether the permission is restricted by policy. + */ + @CheckResult + public abstract boolean isPermissionRevokedByPolicy(@NonNull String permName, + @NonNull String pkgName); + /** + * Gets the package name of the component controlling runtime permissions. + * + * @return The package name. + * + * @hide + */ + @TestApi + public abstract String getPermissionControllerPackageName(); + /** + * Add a new dynamic permission to the system. For this to work, your + * package must have defined a permission tree through the + * {@link android.R.styleable#AndroidManifestPermissionTree + * <permission-tree>} tag in its manifest. A package can only add + * permissions to trees that were defined by either its own package or + * another with the same user id; a permission is in a tree if it + * matches the name of the permission tree + ".": for example, + * "com.foo.bar" is a member of the permission tree "com.foo". + * + *

It is good to make your permission tree name descriptive, because you + * are taking possession of that entire set of permission names. Thus, it + * must be under a domain you control, with a suffix that will not match + * any normal permissions that may be declared in any applications that + * are part of that domain. + * + *

New permissions must be added before + * any .apks are installed that use those permissions. Permissions you + * add through this method are remembered across reboots of the device. + * If the given permission already exists, the info you supply here + * will be used to update it. + * + * @param info Description of the permission to be added. + * + * @return Returns true if a new permission was created, false if an + * existing one was updated. + * + * @throws SecurityException if you are not allowed to add the + * given permission name. + * + * @see #removePermission(String) + */ + public abstract boolean addPermission(PermissionInfo info); + /** + * Like {@link #addPermission(PermissionInfo)} but asynchronously + * persists the package manager state after returning from the call, + * allowing it to return quicker and batch a series of adds at the + * expense of no guarantee the added permission will be retained if + * the device is rebooted before it is written. + */ + public abstract boolean addPermissionAsync(PermissionInfo info); + /** + * Removes a permission that was previously added with + * {@link #addPermission(PermissionInfo)}. The same ownership rules apply + * -- you are only allowed to remove permissions that you are allowed + * to add. + * + * @param name The name of the permission to remove. + * + * @throws SecurityException if you are not allowed to remove the + * given permission name. + * + * @see #addPermission(PermissionInfo) + */ + public abstract void removePermission(String name); + /** + * Permission flags set when granting or revoking a permission. + * + * @hide + */ + @SystemApi + @IntDef(prefix = { "FLAG_PERMISSION_" }, value = { + FLAG_PERMISSION_USER_SET, + FLAG_PERMISSION_USER_FIXED, + FLAG_PERMISSION_POLICY_FIXED, + FLAG_PERMISSION_REVOKE_ON_UPGRADE, + FLAG_PERMISSION_SYSTEM_FIXED, + FLAG_PERMISSION_GRANTED_BY_DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionFlags {} + /** + * Grant a runtime permission to an application which the application does not + * already have. The permission must have been requested by the application. + * If the application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. If the package or permission is + * invalid, a {@link java.lang.IllegalArgumentException} is thrown. + *

+ * Note: Using this API requires holding + * android.permission.GRANT_RUNTIME_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + *

+ * + * @param packageName The package to which to grant the permission. + * @param permissionName The permission name to grant. + * @param user The user for which to grant the permission. + * + * @see #revokeRuntimePermission(String, String, android.os.UserHandle) + * + * @hide + */ + @SystemApi + public abstract void grantRuntimePermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); + /** + * Revoke a runtime permission that was previously granted by {@link + * #grantRuntimePermission(String, String, android.os.UserHandle)}. The + * permission must have been requested by and granted to the application. + * If the application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. If the package or permission is + * invalid, a {@link java.lang.IllegalArgumentException} is thrown. + *

+ * Note: Using this API requires holding + * android.permission.REVOKE_RUNTIME_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + *

+ * + * @param packageName The package from which to revoke the permission. + * @param permissionName The permission name to revoke. + * @param user The user for which to revoke the permission. + * + * @see #grantRuntimePermission(String, String, android.os.UserHandle) + * + * @hide + */ + @SystemApi + public abstract void revokeRuntimePermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); + /** + * Gets the state flags associated with a permission. + * + * @param permissionName The permission for which to get the flags. + * @param packageName The package name for which to get the flags. + * @param user The user for which to get permission flags. + * @return The permission flags. + * + * @hide + */ + @SystemApi + public abstract @PermissionFlags int getPermissionFlags(String permissionName, + String packageName, @NonNull UserHandle user); + /** + * Updates the flags associated with a permission by replacing the flags in + * the specified mask with the provided flag values. + * + * @param permissionName The permission for which to update the flags. + * @param packageName The package name for which to update the flags. + * @param flagMask The flags which to replace. + * @param flagValues The flags with which to replace. + * @param user The user for which to update the permission flags. + * + * @hide + */ + @SystemApi + public abstract void updatePermissionFlags(String permissionName, + String packageName, @PermissionFlags int flagMask, @PermissionFlags int flagValues, + @NonNull UserHandle user); + /** + * Gets whether you should show UI with rationale for requesting a permission. + * You should do this only if you do not have the permission and the context in + * which the permission is requested does not clearly communicate to the user + * what would be the benefit from grating this permission. + * + * @param permission A permission your app wants to request. + * @return Whether you can show permission rationale UI. + * + * @hide + */ + public abstract boolean shouldShowRequestPermissionRationale(String permission); + /** + * Returns an {@link android.content.Intent} suitable for passing to + * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} + * which prompts the user to grant permissions to this application. + * + * @throws NullPointerException if {@code permissions} is {@code null} or empty. + * + * @hide + */ + public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { + if (ArrayUtils.isEmpty(permissions)) { + throw new IllegalArgumentException("permission cannot be null or empty"); + } + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions); + intent.setPackage(getPermissionControllerPackageName()); + return intent; + } + /** + * Compare the signatures of two packages to determine if the same + * signature appears in both of them. If they do contain the same + * signature, then they are allowed special privileges when working + * with each other: they can share the same user-id, run instrumentation + * against each other, etc. + * + * @param pkg1 First package name whose signature will be compared. + * @param pkg2 Second package name whose signature will be compared. + * + * @return Returns an integer indicating whether all signatures on the + * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if + * all signatures match or < 0 if there is not a match ({@link + * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}). + * + * @see #checkSignatures(int, int) + */ + @CheckResult + public abstract @SignatureResult int checkSignatures(String pkg1, String pkg2); + /** + * Like {@link #checkSignatures(String, String)}, but takes UIDs of + * the two packages to be checked. This can be useful, for example, + * when doing the check in an IPC, where the UID is the only identity + * available. It is functionally identical to determining the package + * associated with the UIDs and checking their signatures. + * + * @param uid1 First UID whose signature will be compared. + * @param uid2 Second UID whose signature will be compared. + * + * @return Returns an integer indicating whether all signatures on the + * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if + * all signatures match or < 0 if there is not a match ({@link + * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}). + * + * @see #checkSignatures(String, String) + */ + @CheckResult + public abstract @SignatureResult int checkSignatures(int uid1, int uid2); + /** + * Retrieve the names of all packages that are associated with a particular + * user id. In most cases, this will be a single package name, the package + * that has been assigned that user id. Where there are multiple packages + * sharing the same user id through the "sharedUserId" mechanism, all + * packages with that id will be returned. + * + * @param uid The user id for which you would like to retrieve the + * associated packages. + * + * @return Returns an array of one or more packages assigned to the user + * id, or null if there are no known packages with the given id. + */ + public abstract @Nullable String[] getPackagesForUid(int uid); + /** + * Retrieve the official name associated with a uid. This name is + * guaranteed to never change, though it is possible for the underlying + * uid to be changed. That is, if you are storing information about + * uids in persistent storage, you should use the string returned + * by this function instead of the raw uid. + * + * @param uid The uid for which you would like to retrieve a name. + * @return Returns a unique name for the given uid, or null if the + * uid is not currently assigned. + */ + public abstract @Nullable String getNameForUid(int uid); + /** + * Retrieves the official names associated with each given uid. + * @see #getNameForUid(int) + * + * @hide + */ + public abstract @Nullable String[] getNamesForUids(int[] uids); + /** + * Return the user id associated with a shared user name. Multiple + * applications can specify a shared user name in their manifest and thus + * end up using a common uid. This might be used for new applications + * that use an existing shared user name and need to know the uid of the + * shared user. + * + * @param sharedUserName The shared user name whose uid is to be retrieved. + * @return Returns the UID associated with the shared user. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + * @hide + */ + public abstract int getUidForSharedUser(String sharedUserName) + throws NameNotFoundException; + /** + * Return a List of all application packages that are installed on the + * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all + * applications including those deleted with {@code DONT_DELETE_DATA} + * (partially installed apps with data directory) will be returned. + * + * @param flags Additional option flags to modify the data returned. + * @return A List of ApplicationInfo objects, one for each installed + * application. In the unlikely case there are no installed + * packages, an empty list is returned. If flag + * {@code MATCH_UNINSTALLED_PACKAGES} is set, the application + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + */ + public abstract List getInstalledApplications(@ApplicationInfoFlags int flags); + /** + * Return a List of all application packages that are installed on the + * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been + * set, a list of all applications including those deleted with + * {@code DONT_DELETE_DATA} (partially installed apps with data directory) + * will be returned. + * + * @param flags Additional option flags to modify the data returned. + * @param userId The user for whom the installed applications are to be + * listed + * @return A List of ApplicationInfo objects, one for each installed + * application. In the unlikely case there are no installed + * packages, an empty list is returned. If flag + * {@code MATCH_UNINSTALLED_PACKAGES} is set, the application + * information is retrieved from the list of uninstalled + * applications (which includes installed applications as well as + * applications with data directory i.e. applications which had been + * deleted with {@code DONT_DELETE_DATA} flag set). + * @hide + */ + public abstract List getInstalledApplicationsAsUser( + @ApplicationInfoFlags int flags, @UserIdInt int userId); + /** + * Gets the instant applications the user recently used. + * + * @return The instant app list. + * + * @hide + */ + @SystemApi + public abstract @NonNull List getInstantApps(); + /** + * Gets the icon for an instant application. + * + * @param packageName The app package name. + * + * @hide + */ + @SystemApi + public abstract @Nullable Drawable getInstantAppIcon(String packageName); + /** + * Gets whether this application is an instant app. + * + * @return Whether caller is an instant app. + * + * @see #isInstantApp(String) + * @see #updateInstantAppCookie(byte[]) + * @see #getInstantAppCookie() + * @see #getInstantAppCookieMaxBytes() + */ + public abstract boolean isInstantApp(); + /** + * Gets whether the given package is an instant app. + * + * @param packageName The package to check + * @return Whether the given package is an instant app. + * + * @see #isInstantApp() + * @see #updateInstantAppCookie(byte[]) + * @see #getInstantAppCookie() + * @see #getInstantAppCookieMaxBytes() + * @see #clearInstantAppCookie() + */ + public abstract boolean isInstantApp(String packageName); + /** + * Gets the maximum size in bytes of the cookie data an instant app + * can store on the device. + * + * @return The max cookie size in bytes. + * + * @see #isInstantApp() + * @see #isInstantApp(String) + * @see #updateInstantAppCookie(byte[]) + * @see #getInstantAppCookie() + * @see #clearInstantAppCookie() + */ + public abstract int getInstantAppCookieMaxBytes(); + /** + * @deprecated + * @hide + */ + public abstract int getInstantAppCookieMaxSize(); + /** + * Gets the instant application cookie for this app. Non + * instant apps and apps that were instant but were upgraded + * to normal apps can still access this API. For instant apps + * this cookie is cached for some time after uninstall while for + * normal apps the cookie is deleted after the app is uninstalled. + * The cookie is always present while the app is installed. + * + * @return The cookie. + * + * @see #isInstantApp() + * @see #isInstantApp(String) + * @see #updateInstantAppCookie(byte[]) + * @see #getInstantAppCookieMaxBytes() + * @see #clearInstantAppCookie() + */ + public abstract @NonNull byte[] getInstantAppCookie(); + /** + * Clears the instant application cookie for the calling app. + * + * @see #isInstantApp() + * @see #isInstantApp(String) + * @see #getInstantAppCookieMaxBytes() + * @see #getInstantAppCookie() + * @see #clearInstantAppCookie() + */ + public abstract void clearInstantAppCookie(); + /** + * Updates the instant application cookie for the calling app. Non + * instant apps and apps that were instant but were upgraded + * to normal apps can still access this API. For instant apps + * this cookie is cached for some time after uninstall while for + * normal apps the cookie is deleted after the app is uninstalled. + * The cookie is always present while the app is installed. The + * cookie size is limited by {@link #getInstantAppCookieMaxBytes()}. + * Passing null or an empty array clears the cookie. + *

+ * + * @param cookie The cookie data. + * + * @see #isInstantApp() + * @see #isInstantApp(String) + * @see #getInstantAppCookieMaxBytes() + * @see #getInstantAppCookie() + * @see #clearInstantAppCookie() + * + * @throws IllegalArgumentException if the array exceeds max cookie size. + */ + public abstract void updateInstantAppCookie(@Nullable byte[] cookie); + /** + * @removed + */ + public abstract boolean setInstantAppCookie(@Nullable byte[] cookie); + /** + * Get a list of shared libraries that are available on the + * system. + * + * @return An array of shared library names that are + * available on the system, or null if none are installed. + * + */ + public abstract String[] getSystemSharedLibraryNames(); + /** + * Get a list of shared libraries on the device. + * + * @param flags To filter the libraries to return. + * @return The shared library list. + * + * @see #MATCH_UNINSTALLED_PACKAGES + */ + public abstract @NonNull List getSharedLibraries( + @InstallFlags int flags); + /** + * Get a list of shared libraries on the device. + * + * @param flags To filter the libraries to return. + * @param userId The user to query for. + * @return The shared library list. + * + * @see #MATCH_FACTORY_ONLY + * @see #MATCH_KNOWN_PACKAGES + * @see #MATCH_ANY_USER + * @see #MATCH_UNINSTALLED_PACKAGES + * + * @hide + */ + public abstract @NonNull List getSharedLibrariesAsUser( + @InstallFlags int flags, @UserIdInt int userId); + /** + * Get the name of the package hosting the services shared library. + * + * @return The library host package. + * + * @hide + */ + public abstract @NonNull String getServicesSystemSharedLibraryPackageName(); + /** + * Get the name of the package hosting the shared components shared library. + * + * @return The library host package. + * + * @hide + */ + public abstract @NonNull String getSharedSystemSharedLibraryPackageName(); + /** + * Returns the names of the packages that have been changed + * [eg. added, removed or updated] since the given sequence + * number. + *

If no packages have been changed, returns null. + *

The sequence number starts at 0 and is + * reset every boot. + * @param sequenceNumber The first sequence number for which to retrieve package changes. + * @see Settings.Global#BOOT_COUNT + */ + public abstract @Nullable ChangedPackages getChangedPackages( + @IntRange(from=0) int sequenceNumber); + /** + * Get a list of features that are available on the + * system. + * + * @return An array of FeatureInfo classes describing the features + * that are available on the system, or null if there are none(!!). + */ + public abstract FeatureInfo[] getSystemAvailableFeatures(); + /** + * Check whether the given feature name is one of the available features as + * returned by {@link #getSystemAvailableFeatures()}. This tests for the + * presence of any version of the given feature name; use + * {@link #hasSystemFeature(String, int)} to check for a minimum version. + * + * @return Returns true if the devices supports the feature, else false. + */ + public abstract boolean hasSystemFeature(String name); + /** + * Check whether the given feature name and version is one of the available + * features as returned by {@link #getSystemAvailableFeatures()}. Since + * features are defined to always be backwards compatible, this returns true + * if the available feature version is greater than or equal to the + * requested version. + * + * @return Returns true if the devices supports the feature, else false. + */ + public abstract boolean hasSystemFeature(String name, int version); + /** + * Determine the best action to perform for a given Intent. This is how + * {@link Intent#resolveActivity} finds an activity if a class has not been + * explicitly specified. + *

+ * Note: if using an implicit Intent (without an explicit + * ComponentName specified), be sure to consider whether to set the + * {@link #MATCH_DEFAULT_ONLY} only flag. You need to do so to resolve the + * activity in the same way that + * {@link android.content.Context#startActivity(Intent)} and + * {@link android.content.Intent#resolveActivity(PackageManager) + * Intent.resolveActivity(PackageManager)} do. + *

+ * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. + * @return Returns a ResolveInfo object containing the final activity intent + * that was determined to be the best action. Returns null if no + * matching activity was found. If multiple matching activities are + * found and there is no default set, returns a ResolveInfo object + * containing something else, such as the activity resolver. + */ + public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags); + /** + * Determine the best action to perform for a given Intent for a given user. + * This is how {@link Intent#resolveActivity} finds an activity if a class + * has not been explicitly specified. + *

+ * Note: if using an implicit Intent (without an explicit + * ComponentName specified), be sure to consider whether to set the + * {@link #MATCH_DEFAULT_ONLY} only flag. You need to do so to resolve the + * activity in the same way that + * {@link android.content.Context#startActivity(Intent)} and + * {@link android.content.Intent#resolveActivity(PackageManager) + * Intent.resolveActivity(PackageManager)} do. + *

+ * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. + * @param userId The user id. + * @return Returns a ResolveInfo object containing the final activity intent + * that was determined to be the best action. Returns null if no + * matching activity was found. If multiple matching activities are + * found and there is no default set, returns a ResolveInfo object + * containing something else, such as the activity resolver. + * @hide + */ + public abstract ResolveInfo resolveActivityAsUser(Intent intent, @ResolveInfoFlags int flags, + @UserIdInt int userId); + /** + * Retrieve all activities that can be performed for the given intent. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set + * {@link #MATCH_ALL} to prevent any filtering of the results. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching activity, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveActivity}. If there are no matching activities, an + * empty list is returned. + */ + public abstract List queryIntentActivities(Intent intent, + @ResolveInfoFlags int flags); + /** + * Retrieve all activities that can be performed for the given intent, for a + * specific user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set + * {@link #MATCH_ALL} to prevent any filtering of the results. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching activity, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveActivity}. If there are no matching activities, an + * empty list is returned. + * @hide + */ + public abstract List queryIntentActivitiesAsUser(Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId); + /** + * Retrieve a set of activities that should be presented to the user as + * similar options. This is like {@link #queryIntentActivities}, except it + * also allows you to supply a list of more explicit Intents that you would + * like to resolve to particular options, and takes care of returning the + * final ResolveInfo list in a reasonable order, with no duplicates, based + * on those inputs. + * + * @param caller The class name of the activity that is making the request. + * This activity will never appear in the output list. Can be + * null. + * @param specifics An array of Intents that should be resolved to the first + * specific results. Can be null. + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching activity. The list is ordered first by all of the + * intents resolved in specifics and then any additional + * activities that can handle intent but did not get + * included by one of the specifics intents. If there are + * no matching activities, an empty list is returned. + */ + public abstract List queryIntentActivityOptions(@Nullable ComponentName caller, + @Nullable Intent[] specifics, Intent intent, @ResolveInfoFlags int flags); + /** + * Retrieve all receivers that can handle a broadcast of the given intent. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching receiver, ordered from best to worst. If there are + * no matching receivers, an empty list or null is returned. + */ + public abstract List queryBroadcastReceivers(Intent intent, + @ResolveInfoFlags int flags); + /** + * Retrieve all receivers that can handle a broadcast of the given intent, + * for a specific user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. + * @param userHandle UserHandle of the user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching receiver, ordered from best to worst. If there are + * no matching receivers, an empty list or null is returned. + * @hide + */ + @SystemApi + public List queryBroadcastReceiversAsUser(Intent intent, + @ResolveInfoFlags int flags, UserHandle userHandle) { + return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier()); + } + /** + * @hide + */ + public abstract List queryBroadcastReceiversAsUser(Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId); + /** {@hide} */ + @Deprecated + public List queryBroadcastReceivers(Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId) { + throw new NotImplementedError(); + } + /** + * Determine the best service to handle for a given Intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. + * @return Returns a ResolveInfo object containing the final service intent + * that was determined to be the best action. Returns null if no + * matching service was found. + */ + public abstract ResolveInfo resolveService(Intent intent, @ResolveInfoFlags int flags); + /** + * Retrieve all services that can match the given intent. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags to modify the data returned. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching service, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveService}. If there are no matching services, an + * empty list or null is returned. + */ + public abstract List queryIntentServices(Intent intent, + @ResolveInfoFlags int flags); + /** + * Retrieve all services that can match the given intent for a given user. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags to modify the data returned. + * @param userId The user id. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching service, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveService}. If there are no matching services, an + * empty list or null is returned. + * @hide + */ + public abstract List queryIntentServicesAsUser(Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId); + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. + * @param userId The user id. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching provider, ordered from best to worst. If there are + * no matching services, an empty list or null is returned. + * @hide + */ + public abstract List queryIntentContentProvidersAsUser( + Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching provider, ordered from best to worst. If there are + * no matching services, an empty list or null is returned. + */ + public abstract List queryIntentContentProviders(Intent intent, + @ResolveInfoFlags int flags); + /** + * Find a single content provider by its base path name. + * + * @param name The name of the provider to find. + * @param flags Additional option flags to modify the data returned. + * @return A {@link ProviderInfo} object containing information about the + * provider. If a provider was not found, returns null. + */ + public abstract ProviderInfo resolveContentProvider(String name, + @ComponentInfoFlags int flags); + /** + * Find a single content provider by its base path name. + * + * @param name The name of the provider to find. + * @param flags Additional option flags to modify the data returned. + * @param userId The user id. + * @return A {@link ProviderInfo} object containing information about the + * provider. If a provider was not found, returns null. + * @hide + */ + public abstract ProviderInfo resolveContentProviderAsUser(String name, + @ComponentInfoFlags int flags, @UserIdInt int userId); + /** + * Retrieve content provider information. + *

+ * Note: unlike most other methods, an empty result set is indicated + * by a null return instead of an empty list. + * + * @param processName If non-null, limits the returned providers to only + * those that are hosted by the given process. If null, all + * content providers are returned. + * @param uid If processName is non-null, this is the required + * uid owning the requested content providers. + * @param flags Additional option flags to modify the data returned. + * @return A list of {@link ProviderInfo} objects containing one entry for + * each provider either matching processName or, if + * processName is null, all known content providers. + * If there are no matching providers, null is returned. + */ + public abstract List queryContentProviders( + String processName, int uid, @ComponentInfoFlags int flags); + /** + * Same as {@link #queryContentProviders}, except when {@code metaDataKey} is not null, + * it only returns providers which have metadata with the {@code metaDataKey} key. + * + *

DO NOT USE the {@code metaDataKey} parameter, unless you're the contacts provider. + * You really shouldn't need it. Other apps should use {@link #queryIntentContentProviders} + * instead. + * + *

The {@code metaDataKey} parameter was added to allow the contacts provider to quickly + * scan the GAL providers on the device. Unfortunately the discovery protocol used metadata + * to mark GAL providers, rather than intent filters, so we can't use + * {@link #queryIntentContentProviders} for that. + * + * @hide + */ + public List queryContentProviders( + String processName, int uid, @ComponentInfoFlags int flags, String metaDataKey) { + // Provide the default implementation for mocks. + return queryContentProviders(processName, uid, flags); + } + /** + * Retrieve all of the information we know about a particular + * instrumentation class. + * + * @param className The full name (i.e. + * com.google.apps.contacts.InstrumentList) of an Instrumentation + * class. + * @param flags Additional option flags to modify the data returned. + * @return An {@link InstrumentationInfo} object containing information + * about the instrumentation. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract InstrumentationInfo getInstrumentationInfo(ComponentName className, + @InstrumentationInfoFlags int flags) throws NameNotFoundException; + /** + * Retrieve information about available instrumentation code. May be used to + * retrieve either all instrumentation code, or only the code targeting a + * particular package. + * + * @param targetPackage If null, all instrumentation is returned; only the + * instrumentation targeting this package name is returned. + * @param flags Additional option flags to modify the data returned. + * @return A list of {@link InstrumentationInfo} objects containing one + * entry for each matching instrumentation. If there are no + * instrumentation available, returns an empty list. + */ + public abstract List queryInstrumentation(String targetPackage, + @InstrumentationInfoFlags int flags); + /** + * Retrieve an image from a package. This is a low-level API used by + * the various package manager info structures (such as + * {@link ComponentInfo} to implement retrieval of their associated + * icon. + * + * @param packageName The name of the package that this icon is coming from. + * Cannot be null. + * @param resid The resource identifier of the desired image. Cannot be 0. + * @param appInfo Overall information about packageName. This + * may be null, in which case the application information will be retrieved + * for you if needed; if you already have this information around, it can + * be much more efficient to supply it here. + * + * @return Returns a Drawable holding the requested image. Returns null if + * an image could not be found for any reason. + */ + public abstract Drawable getDrawable(String packageName, @DrawableRes int resid, + ApplicationInfo appInfo); + /** + * Retrieve the icon associated with an activity. Given the full name of + * an activity, retrieves the information about it and calls + * {@link ComponentInfo#loadIcon ComponentInfo.loadIcon()} to return its icon. + * If the activity cannot be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose icon is to be retrieved. + * + * @return Returns the image of the icon, or the default activity icon if + * it could not be found. Does not return null. + * @throws NameNotFoundException Thrown if the resources for the given + * activity could not be loaded. + * + * @see #getActivityIcon(Intent) + */ + public abstract Drawable getActivityIcon(ComponentName activityName) + throws NameNotFoundException; + /** + * Retrieve the icon associated with an Intent. If intent.getClassName() is + * set, this simply returns the result of + * getActivityIcon(intent.getClassName()). Otherwise it resolves the intent's + * component and returns the icon associated with the resolved component. + * If intent.getClassName() cannot be found or the Intent cannot be resolved + * to a component, NameNotFoundException is thrown. + * + * @param intent The intent for which you would like to retrieve an icon. + * + * @return Returns the image of the icon, or the default activity icon if + * it could not be found. Does not return null. + * @throws NameNotFoundException Thrown if the resources for application + * matching the given intent could not be loaded. + * + * @see #getActivityIcon(ComponentName) + */ + public abstract Drawable getActivityIcon(Intent intent) + throws NameNotFoundException; + /** + * Retrieve the banner associated with an activity. Given the full name of + * an activity, retrieves the information about it and calls + * {@link ComponentInfo#loadIcon ComponentInfo.loadIcon()} to return its + * banner. If the activity cannot be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose banner is to be retrieved. + * @return Returns the image of the banner, or null if the activity has no + * banner specified. + * @throws NameNotFoundException Thrown if the resources for the given + * activity could not be loaded. + * @see #getActivityBanner(Intent) + */ + public abstract Drawable getActivityBanner(ComponentName activityName) + throws NameNotFoundException; + /** + * Retrieve the banner associated with an Intent. If intent.getClassName() + * is set, this simply returns the result of + * getActivityBanner(intent.getClassName()). Otherwise it resolves the + * intent's component and returns the banner associated with the resolved + * component. If intent.getClassName() cannot be found or the Intent cannot + * be resolved to a component, NameNotFoundException is thrown. + * + * @param intent The intent for which you would like to retrieve a banner. + * @return Returns the image of the banner, or null if the activity has no + * banner specified. + * @throws NameNotFoundException Thrown if the resources for application + * matching the given intent could not be loaded. + * @see #getActivityBanner(ComponentName) + */ + public abstract Drawable getActivityBanner(Intent intent) + throws NameNotFoundException; + /** + * Return the generic icon for an activity that is used when no specific + * icon is defined. + * + * @return Drawable Image of the icon. + */ + public abstract Drawable getDefaultActivityIcon(); + /** + * Retrieve the icon associated with an application. If it has not defined + * an icon, the default app icon is returned. Does not return null. + * + * @param info Information about application being queried. + * + * @return Returns the image of the icon, or the default application icon + * if it could not be found. + * + * @see #getApplicationIcon(String) + */ + public abstract Drawable getApplicationIcon(ApplicationInfo info); + /** + * Retrieve the icon associated with an application. Given the name of the + * application's package, retrieves the information about it and calls + * getApplicationIcon() to return its icon. If the application cannot be + * found, NameNotFoundException is thrown. + * + * @param packageName Name of the package whose application icon is to be + * retrieved. + * + * @return Returns the image of the icon, or the default application icon + * if it could not be found. Does not return null. + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getApplicationIcon(ApplicationInfo) + */ + public abstract Drawable getApplicationIcon(String packageName) + throws NameNotFoundException; + /** + * Retrieve the banner associated with an application. + * + * @param info Information about application being queried. + * @return Returns the image of the banner or null if the application has no + * banner specified. + * @see #getApplicationBanner(String) + */ + public abstract Drawable getApplicationBanner(ApplicationInfo info); + /** + * Retrieve the banner associated with an application. Given the name of the + * application's package, retrieves the information about it and calls + * getApplicationIcon() to return its banner. If the application cannot be + * found, NameNotFoundException is thrown. + * + * @param packageName Name of the package whose application banner is to be + * retrieved. + * @return Returns the image of the banner or null if the application has no + * banner specified. + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * @see #getApplicationBanner(ApplicationInfo) + */ + public abstract Drawable getApplicationBanner(String packageName) + throws NameNotFoundException; + /** + * Retrieve the logo associated with an activity. Given the full name of an + * activity, retrieves the information about it and calls + * {@link ComponentInfo#loadLogo ComponentInfo.loadLogo()} to return its + * logo. If the activity cannot be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose logo is to be retrieved. + * @return Returns the image of the logo or null if the activity has no logo + * specified. + * @throws NameNotFoundException Thrown if the resources for the given + * activity could not be loaded. + * @see #getActivityLogo(Intent) + */ + public abstract Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException; + /** + * Retrieve the logo associated with an Intent. If intent.getClassName() is + * set, this simply returns the result of + * getActivityLogo(intent.getClassName()). Otherwise it resolves the intent's + * component and returns the logo associated with the resolved component. + * If intent.getClassName() cannot be found or the Intent cannot be resolved + * to a component, NameNotFoundException is thrown. + * + * @param intent The intent for which you would like to retrieve a logo. + * + * @return Returns the image of the logo, or null if the activity has no + * logo specified. + * + * @throws NameNotFoundException Thrown if the resources for application + * matching the given intent could not be loaded. + * + * @see #getActivityLogo(ComponentName) + */ + public abstract Drawable getActivityLogo(Intent intent) + throws NameNotFoundException; + /** + * Retrieve the logo associated with an application. If it has not specified + * a logo, this method returns null. + * + * @param info Information about application being queried. + * + * @return Returns the image of the logo, or null if no logo is specified + * by the application. + * + * @see #getApplicationLogo(String) + */ + public abstract Drawable getApplicationLogo(ApplicationInfo info); + /** + * Retrieve the logo associated with an application. Given the name of the + * application's package, retrieves the information about it and calls + * getApplicationLogo() to return its logo. If the application cannot be + * found, NameNotFoundException is thrown. + * + * @param packageName Name of the package whose application logo is to be + * retrieved. + * + * @return Returns the image of the logo, or null if no application logo + * has been specified. + * + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getApplicationLogo(ApplicationInfo) + */ + public abstract Drawable getApplicationLogo(String packageName) + throws NameNotFoundException; + /** + * If the target user is a managed profile, then this returns a badged copy of the given icon + * to be able to distinguish it from the original icon. For badging an arbitrary drawable use + * {@link #getUserBadgedDrawableForDensity( + * android.graphics.drawable.Drawable, UserHandle, android.graphics.Rect, int)}. + *

+ * If the original drawable is a BitmapDrawable and the backing bitmap is + * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging + * is performed in place and the original drawable is returned. + *

+ * + * @param icon The icon to badge. + * @param user The target user. + * @return A drawable that combines the original icon and a badge as + * determined by the system. + */ + public abstract Drawable getUserBadgedIcon(Drawable icon, UserHandle user); + /** + * If the target user is a managed profile of the calling user or the caller + * is itself a managed profile, then this returns a badged copy of the given + * drawable allowing the user to distinguish it from the original drawable. + * The caller can specify the location in the bounds of the drawable to be + * badged where the badge should be applied as well as the density of the + * badge to be used. + *

+ * If the original drawable is a BitmapDrawable and the backing bitmap is + * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging + * is performed in place and the original drawable is returned. + *

+ * + * @param drawable The drawable to badge. + * @param user The target user. + * @param badgeLocation Where in the bounds of the badged drawable to place + * the badge. If it's {@code null}, the badge is applied on top of the entire + * drawable being badged. + * @param badgeDensity The optional desired density for the badge as per + * {@link android.util.DisplayMetrics#densityDpi}. If it's not positive, + * the density of the display is used. + * @return A drawable that combines the original drawable and a badge as + * determined by the system. + */ + public abstract Drawable getUserBadgedDrawableForDensity(Drawable drawable, + UserHandle user, Rect badgeLocation, int badgeDensity); + /** + * If the target user is a managed profile of the calling user or the caller + * is itself a managed profile, then this returns a drawable to use as a small + * icon to include in a view to distinguish it from the original icon. + * + * @param user The target user. + * @param density The optional desired density for the badge as per + * {@link android.util.DisplayMetrics#densityDpi}. If not provided + * the density of the current display is used. + * @return the drawable or null if no drawable is required. + * @hide + */ + public abstract Drawable getUserBadgeForDensity(UserHandle user, int density); + /** + * If the target user is a managed profile of the calling user or the caller + * is itself a managed profile, then this returns a drawable to use as a small + * icon to include in a view to distinguish it from the original icon. This version + * doesn't have background protection and should be used over a light background instead of + * a badge. + * + * @param user The target user. + * @param density The optional desired density for the badge as per + * {@link android.util.DisplayMetrics#densityDpi}. If not provided + * the density of the current display is used. + * @return the drawable or null if no drawable is required. + * @hide + */ + public abstract Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density); + /** + * If the target user is a managed profile of the calling user or the caller + * is itself a managed profile, then this returns a copy of the label with + * badging for accessibility services like talkback. E.g. passing in "Email" + * and it might return "Work Email" for Email in the work profile. + * + * @param label The label to change. + * @param user The target user. + * @return A label that combines the original label and a badge as + * determined by the system. + */ + public abstract CharSequence getUserBadgedLabel(CharSequence label, UserHandle user); + /** + * Retrieve text from a package. This is a low-level API used by + * the various package manager info structures (such as + * {@link ComponentInfo} to implement retrieval of their associated + * labels and other text. + * + * @param packageName The name of the package that this text is coming from. + * Cannot be null. + * @param resid The resource identifier of the desired text. Cannot be 0. + * @param appInfo Overall information about packageName. This + * may be null, in which case the application information will be retrieved + * for you if needed; if you already have this information around, it can + * be much more efficient to supply it here. + * + * @return Returns a CharSequence holding the requested text. Returns null + * if the text could not be found for any reason. + */ + public abstract CharSequence getText(String packageName, @StringRes int resid, + ApplicationInfo appInfo); + /** + * Retrieve an XML file from a package. This is a low-level API used to + * retrieve XML meta data. + * + * @param packageName The name of the package that this xml is coming from. + * Cannot be null. + * @param resid The resource identifier of the desired xml. Cannot be 0. + * @param appInfo Overall information about packageName. This + * may be null, in which case the application information will be retrieved + * for you if needed; if you already have this information around, it can + * be much more efficient to supply it here. + * + * @return Returns an XmlPullParser allowing you to parse out the XML + * data. Returns null if the xml resource could not be found for any + * reason. + */ + public abstract XmlResourceParser getXml(String packageName, @XmlRes int resid, + ApplicationInfo appInfo); + /** + * Return the label to use for this application. + * + * @return Returns the label associated with this application, or null if + * it could not be found for any reason. + * @param info The application to get the label of. + */ + public abstract CharSequence getApplicationLabel(ApplicationInfo info); + /** + * Retrieve the resources associated with an activity. Given the full + * name of an activity, retrieves the information about it and calls + * getResources() to return its application's resources. If the activity + * cannot be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose resources are to be + * retrieved. + * + * @return Returns the application's Resources. + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getResourcesForApplication(ApplicationInfo) + */ + public abstract Resources getResourcesForActivity(ComponentName activityName) + throws NameNotFoundException; + /** + * Retrieve the resources for an application. Throws NameNotFoundException + * if the package is no longer installed. + * + * @param app Information about the desired application. + * + * @return Returns the application's Resources. + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded (most likely because it was uninstalled). + */ + public abstract Resources getResourcesForApplication(ApplicationInfo app) + throws NameNotFoundException; + /** + * Retrieve the resources associated with an application. Given the full + * package name of an application, retrieves the information about it and + * calls getResources() to return its application's resources. If the + * appPackageName cannot be found, NameNotFoundException is thrown. + * + * @param appPackageName Package name of the application whose resources + * are to be retrieved. + * + * @return Returns the application's Resources. + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getResourcesForApplication(ApplicationInfo) + */ + public abstract Resources getResourcesForApplication(String appPackageName) + throws NameNotFoundException; + /** @hide */ + public abstract Resources getResourcesForApplicationAsUser(String appPackageName, + @UserIdInt int userId) throws NameNotFoundException; + /** + * Retrieve overall information about an application package defined in a + * package archive file + * + * @param archiveFilePath The path to the archive file + * @param flags Additional option flags to modify the data returned. + * @return A PackageInfo object containing information about the package + * archive. If the package could not be parsed, returns null. + */ + public PackageInfo getPackageArchiveInfo(String archiveFilePath, @PackageInfoFlags int flags) { + //TODO + throw new NotImplementedError(); + /* + final PackageParser parser = new PackageParser(); + parser.setCallback(new PackageParser.CallbackImpl(this)); + final File apkFile = new File(archiveFilePath); + try { + if ((flags & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) { + // Caller expressed an explicit opinion about what encryption + // aware/unaware components they want to see, so fall through and + // give them what they want + } else { + // Caller expressed no opinion, so match everything + flags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + } + PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); + if ((flags & GET_SIGNATURES) != 0) { + PackageParser.collectCertificates(pkg, 0); + } + PackageUserState state = new PackageUserState(); + return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); + } catch (PackageParserException e) { + return null; + }*/ + } + /** + * @deprecated replaced by {@link PackageInstaller} + * @hide + */ + @Deprecated + public abstract void installPackage( + Uri packageURI, + PackageInstallObserver observer, + @InstallFlags int flags, + String installerPackageName); + /** + * If there is already an application with the given package name installed + * on the system for other users, also install it for the calling user. + * @hide + */ + @SystemApi + public abstract int installExistingPackage(String packageName) throws NameNotFoundException; + /** + * If there is already an application with the given package name installed + * on the system for other users, also install it for the calling user. + * @hide + */ + @SystemApi + public abstract int installExistingPackage(String packageName, @InstallReason int installReason) + throws NameNotFoundException; + /** + * If there is already an application with the given package name installed + * on the system for other users, also install it for the specified user. + * @hide + */ + public abstract int installExistingPackageAsUser(String packageName, @UserIdInt int userId) + throws NameNotFoundException; + /** + * Allows a package listening to the + * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification + * broadcast} to respond to the package manager. The response must include + * the {@code verificationCode} which is one of + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}. + * + * @param id pending package identifier as passed via the + * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra. + * @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW} + * or {@link PackageManager#VERIFICATION_REJECT}. + * @throws SecurityException if the caller does not have the + * PACKAGE_VERIFICATION_AGENT permission. + */ + public abstract void verifyPendingInstall(int id, int verificationCode); + /** + * Allows a package listening to the + * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification + * broadcast} to extend the default timeout for a response and declare what + * action to perform after the timeout occurs. The response must include + * the {@code verificationCodeAtTimeout} which is one of + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}. + * + * This method may only be called once per package id. Additional calls + * will have no effect. + * + * @param id pending package identifier as passed via the + * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra. + * @param verificationCodeAtTimeout either + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}. If + * {@code verificationCodeAtTimeout} is neither + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}, then + * {@code verificationCodeAtTimeout} will default to + * {@link PackageManager#VERIFICATION_REJECT}. + * @param millisecondsToDelay the amount of time requested for the timeout. + * Must be positive and less than + * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. If + * {@code millisecondsToDelay} is out of bounds, + * {@code millisecondsToDelay} will be set to the closest in + * bounds value; namely, 0 or + * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. + * @throws SecurityException if the caller does not have the + * PACKAGE_VERIFICATION_AGENT permission. + */ + public abstract void extendVerificationTimeout(int id, + int verificationCodeAtTimeout, long millisecondsToDelay); + /** + * Allows a package listening to the + * {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} intent filter verification + * broadcast to respond to the package manager. The response must include + * the {@code verificationCode} which is one of + * {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} or + * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}. + * + * @param verificationId pending package identifier as passed via the + * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra. + * @param verificationCode either {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} + * or {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}. + * @param failedDomains a list of failed domains if the verificationCode is + * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}, otherwise null; + * @throws SecurityException if the caller does not have the + * INTENT_FILTER_VERIFICATION_AGENT permission. + * + * @hide + */ + @SystemApi + public abstract void verifyIntentFilter(int verificationId, int verificationCode, + List failedDomains); + /** + * Get the status of a Domain Verification Result for an IntentFilter. This is + * related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and + * {@link android.content.IntentFilter#getAutoVerify()} + * + * This is used by the ResolverActivity to change the status depending on what the User select + * in the Disambiguation Dialog and also used by the Settings App for changing the default App + * for a domain. + * + * @param packageName The package name of the Activity associated with the IntentFilter. + * @param userId The user id. + * + * @return The status to set to. This can be + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED} + * + * @hide + */ + @SystemApi + public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId); + /** + * Allow to change the status of a Intent Verification status for all IntentFilter of an App. + * This is related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and + * {@link android.content.IntentFilter#getAutoVerify()} + * + * This is used by the ResolverActivity to change the status depending on what the User select + * in the Disambiguation Dialog and also used by the Settings App for changing the default App + * for a domain. + * + * @param packageName The package name of the Activity associated with the IntentFilter. + * @param status The status to set to. This can be + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or + * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} + * @param userId The user id. + * + * @return true if the status has been set. False otherwise. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) + public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status, + @UserIdInt int userId); + /** + * Get the list of IntentFilterVerificationInfo for a specific package and User. + * + * @param packageName the package name. When this parameter is set to a non null value, + * the results will be filtered by the package name provided. + * Otherwise, there will be no filtering and it will return a list + * corresponding for all packages + * + * @return a list of IntentFilterVerificationInfo for a specific package. + * + * @hide + */ + @SystemApi + public abstract List getIntentFilterVerifications( + String packageName); + /** + * Get the list of IntentFilter for a specific package. + * + * @param packageName the package name. This parameter is set to a non null value, + * the list will contain all the IntentFilter for that package. + * Otherwise, the list will be empty. + * + * @return a list of IntentFilter for a specific package. + * + * @hide + */ + @SystemApi + public abstract List getAllIntentFilters(String packageName); + /** + * Get the default Browser package name for a specific user. + * + * @param userId The user id. + * + * @return the package name of the default Browser for the specified user. If the user id passed + * is -1 (all users) it will return a null value. + * + * @hide + */ + @TestApi + @SystemApi + public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId); + /** + * Set the default Browser package name for a specific user. + * + * @param packageName The package name of the default Browser. + * @param userId The user id. + * + * @return true if the default Browser for the specified user has been set, + * otherwise return false. If the user id passed is -1 (all users) this call will not + * do anything and just return false. + * + * @hide + */ + @SystemApi + public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName, + @UserIdInt int userId); + /** + * Change the installer associated with a given package. There are limitations + * on how the installer package can be changed; in particular: + *
    + *
  • A SecurityException will be thrown if installerPackageName + * is not signed with the same certificate as the calling application. + *
  • A SecurityException will be thrown if targetPackage already + * has an installer package, and that installer package is not signed with + * the same certificate as the calling application. + *
+ * + * @param targetPackage The installed package whose installer will be changed. + * @param installerPackageName The package name of the new installer. May be + * null to clear the association. + */ + public abstract void setInstallerPackageName(String targetPackage, + String installerPackageName); + /** @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) + public abstract void setUpdateAvailable(String packageName, boolean updateAvaialble); + /** + * Attempts to delete a package. Since this may take a little while, the + * result will be posted back to the given observer. A deletion will fail if + * the calling context lacks the + * {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the + * named package cannot be found, or if the named package is a system + * package. + * + * @param packageName The name of the package to delete + * @param observer An observer callback to get notified when the package + * deletion is complete. + * {@link android.content.pm.IPackageDeleteObserver#packageDeleted} + * will be called when that happens. observer may be null to + * indicate that no callback is desired. + * @hide + */ +// @RequiresPermission(Manifest.permission.DELETE_PACKAGES) +// public abstract void deletePackage(String packageName, IPackageDeleteObserver observer, +// @DeleteFlags int flags); + /** + * Attempts to delete a package. Since this may take a little while, the + * result will be posted back to the given observer. A deletion will fail if + * the named package cannot be found, or if the named package is a system + * package. + * + * @param packageName The name of the package to delete + * @param observer An observer callback to get notified when the package + * deletion is complete. + * {@link android.content.pm.IPackageDeleteObserver#packageDeleted} + * will be called when that happens. observer may be null to + * indicate that no callback is desired. + * @param userId The user Id + * @hide + */ +// @RequiresPermission(anyOf = { +// Manifest.permission.DELETE_PACKAGES, +// Manifest.permission.INTERACT_ACROSS_USERS_FULL}) +// public abstract void deletePackageAsUser(@NonNull String packageName, +// IPackageDeleteObserver observer, @DeleteFlags int flags, @UserIdInt int userId); + /** + * Retrieve the package name of the application that installed a package. This identifies + * which market the package came from. + * + * @param packageName The name of the package to query + */ + public abstract String getInstallerPackageName(String packageName); + /** + * Attempts to clear the user data directory of an application. + * Since this may take a little while, the result will + * be posted back to the given observer. A deletion will fail if the + * named package cannot be found, or if the named package is a "system package". + * + * @param packageName The name of the package + * @param observer An observer callback to get notified when the operation is finished + * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)} + * will be called when that happens. observer may be null to indicate that + * no callback is desired. + * + * @hide + */ +// public abstract void clearApplicationUserData(String packageName, +// IPackageDataObserver observer); + /** + * Attempts to delete the cache files associated with an application. + * Since this may take a little while, the result will + * be posted back to the given observer. A deletion will fail if the calling context + * lacks the {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the + * named package cannot be found, or if the named package is a "system package". + * + * @param packageName The name of the package to delete + * @param observer An observer callback to get notified when the cache file deletion + * is complete. + * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)} + * will be called when that happens. observer may be null to indicate that + * no callback is desired. + * + * @hide + */ +// public abstract void deleteApplicationCacheFiles(String packageName, +// IPackageDataObserver observer); + /** + * Attempts to delete the cache files associated with an application for a given user. Since + * this may take a little while, the result will be posted back to the given observer. A + * deletion will fail if the calling context lacks the + * {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the named package + * cannot be found, or if the named package is a "system package". If {@code userId} does not + * belong to the calling user, the caller must have + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * + * @param packageName The name of the package to delete + * @param userId the user for which the cache files needs to be deleted + * @param observer An observer callback to get notified when the cache file deletion is + * complete. + * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)} + * will be called when that happens. observer may be null to indicate that no + * callback is desired. + * @hide + */ +// public abstract void deleteApplicationCacheFilesAsUser(String packageName, int userId, +// IPackageDataObserver observer); + /** + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. + * @param freeStorageSize The number of bytes of storage to be + * freed by the system. Say if freeStorageSize is XX, + * and the current free storage is YY, + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param observer call back used to notify when + * the operation is completed + * + * @hide + */ +// public void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer) { +// freeStorageAndNotify(null, freeStorageSize, observer); +// } + /** {@hide} */ +// public abstract void freeStorageAndNotify(String volumeUuid, long freeStorageSize, +// IPackageDataObserver observer); + /** + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. + * @param freeStorageSize The number of bytes of storage to be + * freed by the system. Say if freeStorageSize is XX, + * and the current free storage is YY, + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param pi IntentSender call back used to + * notify when the operation is completed.May be null + * to indicate that no call back is desired. + * + * @hide + */ + public void freeStorage(long freeStorageSize, IntentSender pi) { + freeStorage(null, freeStorageSize, pi); + } + /** {@hide} */ + public abstract void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi); + /** + * Retrieve the size information for a package. + * Since this may take a little while, the result will + * be posted back to the given observer. The calling context + * should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission. + * + * @param packageName The name of the package whose size information is to be retrieved + * @param userId The user whose size information should be retrieved. + * @param observer An observer callback to get notified when the operation + * is complete. + * {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)} + * The observer's callback is invoked with a PackageStats object(containing the + * code, data and cache sizes of the package) and a boolean value representing + * the status of the operation. observer may be null to indicate that + * no callback is desired. + * + * @deprecated use {@link StorageStatsManager} instead. + * @hide + */ +// @Deprecated +// public abstract void getPackageSizeInfoAsUser(String packageName, @UserIdInt int userId, +// IPackageStatsObserver observer); + /** + * Like {@link #getPackageSizeInfoAsUser(String, int, IPackageStatsObserver)}, but + * returns the size for the calling user. + * + * @deprecated use {@link StorageStatsManager} instead. + * @hide + */ +// @Deprecated +// public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { +// getPackageSizeInfoAsUser(packageName, UserHandle.myUserId(), observer); +// } + /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + */ + @Deprecated + public abstract void addPackageToPreferred(String packageName); + /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + */ + @Deprecated + public abstract void removePackageFromPreferred(String packageName); + /** + * Retrieve the list of all currently configured preferred packages. The + * first package on the list is the most preferred, the last is the least + * preferred. + * + * @param flags Additional option flags to modify the data returned. + * @return A List of PackageInfo objects, one for each preferred + * application, in order of preference. + */ + public abstract List getPreferredPackages(@PackageInfoFlags int flags); + /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * + * Add a new preferred activity mapping to the system. This will be used + * to automatically select the given activity component when + * {@link Context#startActivity(Intent) Context.startActivity()} finds + * multiple matching activities and also matches the given filter. + * + * @param filter The set of intents under which this activity will be + * made preferred. + * @param match The IntentFilter match category that this preference + * applies to. + * @param set The set of activities that the user was picking from when + * this preference was made. + * @param activity The component name of the activity that is to be + * preferred. + */ + @Deprecated + public abstract void addPreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity); + /** + * Same as {@link #addPreferredActivity(IntentFilter, int, + ComponentName[], ComponentName)}, but with a specific userId to apply the preference + to. + * @hide + */ + public void addPreferredActivityAsUser(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, @UserIdInt int userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * + * Replaces an existing preferred activity mapping to the system, and if that were not present + * adds a new preferred activity. This will be used + * to automatically select the given activity component when + * {@link Context#startActivity(Intent) Context.startActivity()} finds + * multiple matching activities and also matches the given filter. + * + * @param filter The set of intents under which this activity will be + * made preferred. + * @param match The IntentFilter match category that this preference + * applies to. + * @param set The set of activities that the user was picking from when + * this preference was made. + * @param activity The component name of the activity that is to be + * preferred. + * @hide + */ + @Deprecated + public abstract void replacePreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity); + /** + * @hide + */ + @Deprecated + public void replacePreferredActivityAsUser(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, @UserIdInt int userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** + * Remove all preferred activity mappings, previously added with + * {@link #addPreferredActivity}, from the + * system whose activities are implemented in the given package name. + * An application can only clear its own package(s). + * + * @param packageName The name of the package whose preferred activity + * mappings are to be removed. + */ + public abstract void clearPackagePreferredActivities(String packageName); + /** + * Retrieve all preferred activities, previously added with + * {@link #addPreferredActivity}, that are + * currently registered with the system. + * + * @param outFilters A required list in which to place the filters of all of the + * preferred activities. + * @param outActivities A required list in which to place the component names of + * all of the preferred activities. + * @param packageName An optional package in which you would like to limit + * the list. If null, all activities will be returned; if non-null, only + * those activities in the given package are returned. + * + * @return Returns the total number of registered preferred activities + * (the number of distinct IntentFilter records, not the number of unique + * activity components) that were found. + */ + public abstract int getPreferredActivities(@NonNull List outFilters, + @NonNull List outActivities, String packageName); + /** + * Ask for the set of available 'home' activities and the current explicit + * default, if any. + * @hide + */ + public abstract ComponentName getHomeActivities(List outActivities); + /** + * Set the enabled setting for a package component (activity, receiver, service, provider). + * This setting will override any enabled state which may have been set by the component in its + * manifest. + * + * @param componentName The component to enable + * @param newState The new enabled state for the component. + * @param flags Optional behavior flags. + */ + public abstract void setComponentEnabledSetting(ComponentName componentName, + @EnabledState int newState, @EnabledFlags int flags); + /** + * Return the enabled setting for a package component (activity, + * receiver, service, provider). This returns the last value set by + * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most + * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since + * the value originally specified in the manifest has not been modified. + * + * @param componentName The component to retrieve. + * @return Returns the current enabled state for the component. + */ + public abstract @EnabledState int getComponentEnabledSetting( + ComponentName componentName); + /** + * Set the enabled setting for an application + * This setting will override any enabled state which may have been set by the application in + * its manifest. It also overrides the enabled state set in the manifest for any of the + * application's components. It does not override any enabled state set by + * {@link #setComponentEnabledSetting} for any of the application's components. + * + * @param packageName The package name of the application to enable + * @param newState The new enabled state for the application. + * @param flags Optional behavior flags. + */ + public abstract void setApplicationEnabledSetting(String packageName, + @EnabledState int newState, @EnabledFlags int flags); + /** + * Return the enabled setting for an application. This returns + * the last value set by + * {@link #setApplicationEnabledSetting(String, int, int)}; in most + * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since + * the value originally specified in the manifest has not been modified. + * + * @param packageName The package name of the application to retrieve. + * @return Returns the current enabled state for the application. + * @throws IllegalArgumentException if the named package does not exist. + */ + public abstract @EnabledState int getApplicationEnabledSetting(String packageName); + /** + * Flush the package restrictions for a given user to disk. This forces the package restrictions + * like component and package enabled settings to be written to disk and avoids the delay that + * is otherwise present when changing those settings. + * + * @param userId Ther userId of the user whose restrictions are to be flushed. + * @hide + */ + public abstract void flushPackageRestrictionsAsUser(int userId); + /** + * Puts the package in a hidden state, which is almost like an uninstalled state, + * making the package unavailable, but it doesn't remove the data or the actual + * package file. Application can be unhidden by either resetting the hidden state + * or by installing it, such as with {@link #installExistingPackage(String)} + * @hide + */ + public abstract boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, + UserHandle userHandle); + /** + * Returns the hidden state of a package. + * @see #setApplicationHiddenSettingAsUser(String, boolean, UserHandle) + * @hide + */ + public abstract boolean getApplicationHiddenSettingAsUser(String packageName, + UserHandle userHandle); + /** + * Return whether the device has been booted into safe mode. + */ + public abstract boolean isSafeMode(); + /** + * Adds a listener for permission changes for installed packages. + * + * @param listener The listener to add. + * + * @hide + */ + @SystemApi + public abstract void addOnPermissionsChangeListener(OnPermissionsChangedListener listener); + /** + * Remvoes a listener for permission changes for installed packages. + * + * @param listener The listener to remove. + * + * @hide + */ + @SystemApi + public abstract void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener); + /** + * Return the {@link KeySet} associated with the String alias for this + * application. + * + * @param alias The alias for a given {@link KeySet} as defined in the + * application's AndroidManifest.xml. + * @hide + */ + public abstract KeySet getKeySetByAlias(String packageName, String alias); + /** Return the signing {@link KeySet} for this application. + * @hide + */ + public abstract KeySet getSigningKeySet(String packageName); + /** + * Return whether the package denoted by packageName has been signed by all + * of the keys specified by the {@link KeySet} ks. This will return true if + * the package has been signed by additional keys (a superset) as well. + * Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}. + * @hide + */ + public abstract boolean isSignedBy(String packageName, KeySet ks); + /** + * Return whether the package denoted by packageName has been signed by all + * of, and only, the keys specified by the {@link KeySet} ks. Compare to + * {@link #isSignedBy(String packageName, KeySet ks)}. + * @hide + */ + public abstract boolean isSignedByExactly(String packageName, KeySet ks); + /** + * Puts the package in a suspended state, where attempts at starting activities are denied. + * + *

It doesn't remove the data or the actual package file. The application notifications + * will be hidden, the application will not show up in recents, will not be able to show + * toasts or dialogs or ring the device. + * + *

The package must already be installed. If the package is uninstalled while suspended + * the package will no longer be suspended. + * + * @param packageNames The names of the packages to set the suspended status. + * @param suspended If set to {@code true} than the packages will be suspended, if set to + * {@code false} the packages will be unsuspended. + * @param userId The user id. + * + * @return an array of package names for which the suspended status is not set as requested in + * this method. + * + * @hide + */ + public abstract String[] setPackagesSuspendedAsUser( + String[] packageNames, boolean suspended, @UserIdInt int userId); + /** + * @see #setPackageSuspendedAsUser(String, boolean, int) + * @param packageName The name of the package to get the suspended status of. + * @param userId The user id. + * @return {@code true} if the package is suspended or {@code false} if the package is not + * suspended or could not be found. + * @hide + */ + public abstract boolean isPackageSuspendedForUser(String packageName, int userId); + /** + * Provide a hint of what the {@link ApplicationInfo#category} value should + * be for the given package. + *

+ * This hint can only be set by the app which installed this package, as + * determined by {@link #getInstallerPackageName(String)}. + * + * @param packageName the package to change the category hint for. + * @param categoryHint the category hint to set. + */ +// public abstract void setApplicationCategoryHint(@NonNull String packageName, +// @ApplicationInfo.Category int categoryHint); + /** {@hide} */ + public static boolean isMoveStatusFinished(int status) { + return (status < 0 || status > 100); + } + /** {@hide} */ + public static abstract class MoveCallback { + public void onCreated(int moveId, Bundle extras) {} + public abstract void onStatusChanged(int moveId, int status, long estMillis); + } + /** {@hide} */ + public abstract int getMoveStatus(int moveId); + /** {@hide} */ + public abstract void registerMoveCallback(MoveCallback callback, Handler handler); + /** {@hide} */ + public abstract void unregisterMoveCallback(MoveCallback callback); + /** {@hide} */ +// public abstract int movePackage(String packageName, VolumeInfo vol); + /** {@hide} */ +// public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app); + /** {@hide} */ +// public abstract @NonNull List getPackageCandidateVolumes(ApplicationInfo app); + /** {@hide} */ +// public abstract int movePrimaryStorage(VolumeInfo vol); + /** {@hide} */ +// public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume(); + /** {@hide} */ +// public abstract @NonNull List getPrimaryStorageCandidateVolumes(); + /** + * Returns the device identity that verifiers can use to associate their scheme to a particular + * device. This should not be used by anything other than a package verifier. + * + * @return identity that uniquely identifies current device + * @hide + */ +// public abstract VerifierDeviceIdentity getVerifierDeviceIdentity(); + /** + * Returns true if the device is upgrading, such as first boot after OTA. + * + * @hide + */ + public abstract boolean isUpgrade(); + /** + * Return interface that offers the ability to install, upgrade, and remove + * applications on the device. + */ + public abstract @NonNull PackageInstaller getPackageInstaller(); + /** + * Adds a {@code CrossProfileIntentFilter}. After calling this method all + * intents sent from the user with id sourceUserId can also be be resolved + * by activities in the user with id targetUserId if they match the + * specified intent filter. + * + * @param filter The {@link IntentFilter} the intent has to match + * @param sourceUserId The source user id. + * @param targetUserId The target user id. + * @param flags The possible values are {@link #SKIP_CURRENT_PROFILE} and + * {@link #ONLY_IF_NO_MATCH_FOUND}. + * @hide + */ + public abstract void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, + int targetUserId, int flags); + /** + * Clearing {@code CrossProfileIntentFilter}s which have the specified user + * as their source, and have been set by the app calling this method. + * + * @param sourceUserId The source user id. + * @hide + */ + public abstract void clearCrossProfileIntentFilters(int sourceUserId); + /** + * @hide + */ + public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo); + /** + * @hide + */ + public abstract Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo); + /** {@hide} */ + public abstract boolean isPackageAvailable(String packageName); + /** {@hide} */ + public static String installStatusToString(int status, String msg) { + final String str = installStatusToString(status); + if (msg != null) { + return str + ": " + msg; + } else { + return str; + } + } + /** {@hide} */ + public static String installStatusToString(int status) { + switch (status) { + case INSTALL_SUCCEEDED: return "INSTALL_SUCCEEDED"; + case INSTALL_FAILED_ALREADY_EXISTS: return "INSTALL_FAILED_ALREADY_EXISTS"; + case INSTALL_FAILED_INVALID_APK: return "INSTALL_FAILED_INVALID_APK"; + case INSTALL_FAILED_INVALID_URI: return "INSTALL_FAILED_INVALID_URI"; + case INSTALL_FAILED_INSUFFICIENT_STORAGE: return "INSTALL_FAILED_INSUFFICIENT_STORAGE"; + case INSTALL_FAILED_DUPLICATE_PACKAGE: return "INSTALL_FAILED_DUPLICATE_PACKAGE"; + case INSTALL_FAILED_NO_SHARED_USER: return "INSTALL_FAILED_NO_SHARED_USER"; + case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return "INSTALL_FAILED_UPDATE_INCOMPATIBLE"; + case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return "INSTALL_FAILED_SHARED_USER_INCOMPATIBLE"; + case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return "INSTALL_FAILED_MISSING_SHARED_LIBRARY"; + case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return "INSTALL_FAILED_REPLACE_COULDNT_DELETE"; + case INSTALL_FAILED_DEXOPT: return "INSTALL_FAILED_DEXOPT"; + case INSTALL_FAILED_OLDER_SDK: return "INSTALL_FAILED_OLDER_SDK"; + case INSTALL_FAILED_CONFLICTING_PROVIDER: return "INSTALL_FAILED_CONFLICTING_PROVIDER"; + case INSTALL_FAILED_NEWER_SDK: return "INSTALL_FAILED_NEWER_SDK"; + case INSTALL_FAILED_TEST_ONLY: return "INSTALL_FAILED_TEST_ONLY"; + case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return "INSTALL_FAILED_CPU_ABI_INCOMPATIBLE"; + case INSTALL_FAILED_MISSING_FEATURE: return "INSTALL_FAILED_MISSING_FEATURE"; + case INSTALL_FAILED_CONTAINER_ERROR: return "INSTALL_FAILED_CONTAINER_ERROR"; + case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return "INSTALL_FAILED_INVALID_INSTALL_LOCATION"; + case INSTALL_FAILED_MEDIA_UNAVAILABLE: return "INSTALL_FAILED_MEDIA_UNAVAILABLE"; + case INSTALL_FAILED_VERIFICATION_TIMEOUT: return "INSTALL_FAILED_VERIFICATION_TIMEOUT"; + case INSTALL_FAILED_VERIFICATION_FAILURE: return "INSTALL_FAILED_VERIFICATION_FAILURE"; + case INSTALL_FAILED_PACKAGE_CHANGED: return "INSTALL_FAILED_PACKAGE_CHANGED"; + case INSTALL_FAILED_UID_CHANGED: return "INSTALL_FAILED_UID_CHANGED"; + case INSTALL_FAILED_VERSION_DOWNGRADE: return "INSTALL_FAILED_VERSION_DOWNGRADE"; + case INSTALL_PARSE_FAILED_NOT_APK: return "INSTALL_PARSE_FAILED_NOT_APK"; + case INSTALL_PARSE_FAILED_BAD_MANIFEST: return "INSTALL_PARSE_FAILED_BAD_MANIFEST"; + case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return "INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION"; + case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return "INSTALL_PARSE_FAILED_NO_CERTIFICATES"; + case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return "INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES"; + case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return "INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING"; + case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return "INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME"; + case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return "INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID"; + case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED"; + case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return "INSTALL_PARSE_FAILED_MANIFEST_EMPTY"; + case INSTALL_FAILED_INTERNAL_ERROR: return "INSTALL_FAILED_INTERNAL_ERROR"; + case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED"; + case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; + case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; + case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED"; + default: return Integer.toString(status); + } + } + /** {@hide} */ + public static int installStatusToPublicStatus(int status) { + switch (status) { + case INSTALL_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS; + case INSTALL_FAILED_ALREADY_EXISTS: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_INVALID_APK: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_INVALID_URI: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_INSUFFICIENT_STORAGE: return PackageInstaller.STATUS_FAILURE_STORAGE; + case INSTALL_FAILED_DUPLICATE_PACKAGE: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_NO_SHARED_USER: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_DEXOPT: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_OLDER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_CONFLICTING_PROVIDER: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_NEWER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_TEST_ONLY: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_MISSING_FEATURE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_CONTAINER_ERROR: return PackageInstaller.STATUS_FAILURE_STORAGE; + case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return PackageInstaller.STATUS_FAILURE_STORAGE; + case INSTALL_FAILED_MEDIA_UNAVAILABLE: return PackageInstaller.STATUS_FAILURE_STORAGE; + case INSTALL_FAILED_VERIFICATION_TIMEOUT: return PackageInstaller.STATUS_FAILURE_ABORTED; + case INSTALL_FAILED_VERIFICATION_FAILURE: return PackageInstaller.STATUS_FAILURE_ABORTED; + case INSTALL_FAILED_PACKAGE_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_UID_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_VERSION_DOWNGRADE: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_NOT_APK: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_MANIFEST: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID; + case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE; + case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT; + case INSTALL_FAILED_NO_MATCHING_ABIS: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED; + default: return PackageInstaller.STATUS_FAILURE; + } + } + /** {@hide} */ + public static String deleteStatusToString(int status, String msg) { + final String str = deleteStatusToString(status); + if (msg != null) { + return str + ": " + msg; + } else { + return str; + } + } + /** {@hide} */ + public static String deleteStatusToString(int status) { + switch (status) { + case DELETE_SUCCEEDED: return "DELETE_SUCCEEDED"; + case DELETE_FAILED_INTERNAL_ERROR: return "DELETE_FAILED_INTERNAL_ERROR"; + case DELETE_FAILED_DEVICE_POLICY_MANAGER: return "DELETE_FAILED_DEVICE_POLICY_MANAGER"; + case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED"; + case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED"; + case DELETE_FAILED_ABORTED: return "DELETE_FAILED_ABORTED"; + case DELETE_FAILED_USED_SHARED_LIBRARY: return "DELETE_FAILED_USED_SHARED_LIBRARY"; + default: return Integer.toString(status); + } + } + /** {@hide} */ + public static int deleteStatusToPublicStatus(int status) { + switch (status) { + case DELETE_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS; + case DELETE_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE; + case DELETE_FAILED_DEVICE_POLICY_MANAGER: return PackageInstaller.STATUS_FAILURE_BLOCKED; + case DELETE_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_BLOCKED; + case DELETE_FAILED_OWNER_BLOCKED: return PackageInstaller.STATUS_FAILURE_BLOCKED; + case DELETE_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED; + case DELETE_FAILED_USED_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_CONFLICT; + default: return PackageInstaller.STATUS_FAILURE; + } + } + /** {@hide} */ + public static String permissionFlagToString(int flag) { + switch (flag) { + case FLAG_PERMISSION_GRANTED_BY_DEFAULT: return "GRANTED_BY_DEFAULT"; + case FLAG_PERMISSION_POLICY_FIXED: return "POLICY_FIXED"; + case FLAG_PERMISSION_SYSTEM_FIXED: return "SYSTEM_FIXED"; + case FLAG_PERMISSION_USER_SET: return "USER_SET"; + case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE"; + case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED"; + case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED"; + default: return Integer.toString(flag); + } + } + /** {@hide} */ + /*public static class LegacyPackageInstallObserver extends PackageInstallObserver { + private final IPackageInstallObserver mLegacy; + public LegacyPackageInstallObserver(IPackageInstallObserver legacy) { + mLegacy = legacy; + } + @Override + public void onPackageInstalled(String basePackageName, int returnCode, String msg, + Bundle extras) { + if (mLegacy == null) return; + try { + mLegacy.packageInstalled(basePackageName, returnCode); + } catch (RemoteException ignored) { + } + } + }*/ + /** {@hide} */ + /*public static class LegacyPackageDeleteObserver extends PackageDeleteObserver { + private final IPackageDeleteObserver mLegacy; + public LegacyPackageDeleteObserver(IPackageDeleteObserver legacy) { + mLegacy = legacy; + } + @Override + public void onPackageDeleted(String basePackageName, int returnCode, String msg) { + if (mLegacy == null) return; + try { + mLegacy.packageDeleted(basePackageName, returnCode); + } catch (RemoteException ignored) { + } + } + }*/ + /** + * Return the install reason that was recorded when a package was first + * installed for a specific user. Requesting the install reason for another + * user will require the permission INTERACT_ACROSS_USERS_FULL. + * + * @param packageName The package for which to retrieve the install reason + * @param user The user for whom to retrieve the install reason + * @return The install reason. If the package is not installed for the given + * user, {@code INSTALL_REASON_UNKNOWN} is returned. + * @hide + */ + @TestApi + public abstract @InstallReason int getInstallReason(String packageName, + @NonNull UserHandle user); + /** + * Checks whether the calling package is allowed to request package installs through package + * installer. Apps are encouraged to call this API before launching the package installer via + * intent {@link android.content.Intent#ACTION_INSTALL_PACKAGE}. Starting from Android O, the + * user can explicitly choose what external sources they trust to install apps on the device. + * If this API returns false, the install request will be blocked by the package installer and + * a dialog will be shown to the user with an option to launch settings to change their + * preference. An application must target Android O or higher and declare permission + * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} in order to use this API. + * + * @return true if the calling package is trusted by the user to request install packages on + * the device, false otherwise. + * @see android.content.Intent#ACTION_INSTALL_PACKAGE + * @see android.provider.Settings#ACTION_MANAGE_UNKNOWN_APP_SOURCES + */ + public abstract boolean canRequestPackageInstalls(); + /** + * Return the {@link ComponentName} of the activity providing Settings for the Instant App + * resolver. + * + * @see {@link android.content.Intent#ACTION_INSTANT_APP_RESOLVER_SETTINGS} + * @hide + */ + @SystemApi + public abstract ComponentName getInstantAppResolverSettingsComponent(); + /** + * Return the {@link ComponentName} of the activity responsible for installing instant + * applications. + * + * @see {@link android.content.Intent#ACTION_INSTALL_INSTANT_APP_PACKAGE} + * @hide + */ + @SystemApi + public abstract ComponentName getInstantAppInstallerComponent(); + /** + * Return the Android Id for a given Instant App. + * + * @see {@link android.provider.Settings.Secure#ANDROID_ID} + * @hide + */ + public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user); + /** + * Callback use to notify the callers of module registration that the operation + * has finished. + * + * @hide + */ + @SystemApi + public static abstract class DexModuleRegisterCallback { + public abstract void onDexModuleRegistered(String dexModulePath, boolean success, + String message); + } + /** + * Register an application dex module with the package manager. + * The package manager will keep track of the given module for future optimizations. + * + * Dex module optimizations will disable the classpath checking at runtime. The client bares + * the responsibility to ensure that the static assumptions on classes in the optimized code + * hold at runtime (e.g. there's no duplicate classes in the classpath). + * + * Note that the package manager already keeps track of dex modules loaded with + * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}. + * This can be called for an eager registration. + * + * The call might take a while and the results will be posted on the main thread, using + * the given callback. + * + * If the module is intended to be shared with other apps, make sure that the file + * permissions allow for it. + * If at registration time the permissions allow for others to read it, the module would + * be marked as a shared module which might undergo a different optimization strategy. + * (usually shared modules will generated larger optimizations artifacts, + * taking more disk space). + * + * @param dexModulePath the absolute path of the dex module. + * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will + * be called once the registration finishes. + * + * @hide + */ + @SystemApi + public abstract void registerDexModule(String dexModulePath, + @Nullable DexModuleRegisterCallback callback); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/PackageParser.java b/AndroidCompat/src/main/java/android/content/pm/PackageParser.java new file mode 100644 index 00000000..d6f5bde1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/PackageParser.java @@ -0,0 +1,15 @@ +package android.content.pm; + +public class PackageParser { + public static class PackageParserException extends Exception { + public final int error; + public PackageParserException(int error, String detailMessage) { + super(detailMessage); + this.error = error; + } + public PackageParserException(int error, String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + this.error = error; + } + } +} diff --git a/AndroidCompat/src/main/java/android/content/pm/Signature.java b/AndroidCompat/src/main/java/android/content/pm/Signature.java new file mode 100644 index 00000000..f82b4af4 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/Signature.java @@ -0,0 +1,178 @@ +/* + * 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 android.content.pm; +import android.os.Parcel; +import android.os.Parcelable; +import java.io.ByteArrayInputStream; +import java.lang.ref.SoftReference; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +/** + * Opaque, immutable representation of a signature associated with an + * application package. + */ +public class Signature implements Parcelable { + private final byte[] mSignature; + private int mHashCode; + private boolean mHaveHashCode; + private SoftReference mStringRef; + /** + * Create Signature from an existing raw byte array. + */ + public Signature(byte[] signature) { + mSignature = signature.clone(); + } + private static final int parseHexDigit(int nibble) { + if ('0' <= nibble && nibble <= '9') { + return nibble - '0'; + } else if ('a' <= nibble && nibble <= 'f') { + return nibble - 'a' + 10; + } else if ('A' <= nibble && nibble <= 'F') { + return nibble - 'A' + 10; + } else { + throw new IllegalArgumentException("Invalid character " + nibble + " in hex string"); + } + } + /** + * Create Signature from a text representation previously returned by + * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to + * be a hex-encoded ASCII string. + * + * @param text hex-encoded string representing the signature + * @throws IllegalArgumentException when signature is odd-length + */ + public Signature(String text) { + final byte[] input = text.getBytes(); + final int N = input.length; + if (N % 2 != 0) { + throw new IllegalArgumentException("text size " + N + " is not even"); + } + final byte[] sig = new byte[N / 2]; + int sigIndex = 0; + for (int i = 0; i < N;) { + final int hi = parseHexDigit(input[i++]); + final int lo = parseHexDigit(input[i++]); + sig[sigIndex++] = (byte) ((hi << 4) | lo); + } + mSignature = sig; + } + /** + * Encode the Signature as ASCII text. + */ + public char[] toChars() { + return toChars(null, null); + } + /** + * Encode the Signature as ASCII text in to an existing array. + * + * @param existingArray Existing char array or null. + * @param outLen Output parameter for the number of characters written in + * to the array. + * @return Returns either existingArray if it was large enough + * to hold the ASCII representation, or a newly created char[] array if + * needed. + */ + public char[] toChars(char[] existingArray, int[] outLen) { + byte[] sig = mSignature; + final int N = sig.length; + final int N2 = N*2; + char[] text = existingArray == null || N2 > existingArray.length + ? new char[N2] : existingArray; + for (int j=0; j>4)&0xf; + text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); + d = v&0xf; + text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); + } + if (outLen != null) outLen[0] = N; + return text; + } + /** + * Return the result of {@link #toChars()} as a String. + */ + public String toCharsString() { + String str = mStringRef == null ? null : mStringRef.get(); + if (str != null) { + return str; + } + str = new String(toChars()); + mStringRef = new SoftReference(str); + return str; + } + /** + * @return the contents of this signature as a byte array. + */ + public byte[] toByteArray() { + byte[] bytes = new byte[mSignature.length]; + System.arraycopy(mSignature, 0, bytes, 0, mSignature.length); + return bytes; + } + /** + * Returns the public key for this signature. + * + * @throws CertificateException when Signature isn't a valid X.509 + * certificate; shouldn't happen. + * @hide + */ + public PublicKey getPublicKey() throws CertificateException { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature); + final Certificate cert = certFactory.generateCertificate(bais); + return cert.getPublicKey(); + } + @Override + public boolean equals(Object obj) { + try { + if (obj != null) { + Signature other = (Signature)obj; + return this == other || Arrays.equals(mSignature, other.mSignature); + } + } catch (ClassCastException e) { + } + return false; + } + @Override + public int hashCode() { + if (mHaveHashCode) { + return mHashCode; + } + mHashCode = Arrays.hashCode(mSignature); + mHaveHashCode = true; + return mHashCode; + } + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeByteArray(mSignature); + } + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public Signature createFromParcel(Parcel source) { + return new Signature(source); + } + public Signature[] newArray(int size) { + return new Signature[size]; + } + }; + private Signature(Parcel source) { + mSignature = source.createByteArray(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/pm/VersionedPackage.java b/AndroidCompat/src/main/java/android/content/pm/VersionedPackage.java new file mode 100644 index 00000000..f600e3d2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/pm/VersionedPackage.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 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 android.content.pm; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** + * Encapsulates a package and its version code. + */ +public final class VersionedPackage implements Parcelable { + private final String mPackageName; + private final int mVersionCode; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntRange(from = PackageManager.VERSION_CODE_HIGHEST) + public @interface VersionCode{} + /** + * Creates a new instance. Use {@link PackageManager#VERSION_CODE_HIGHEST} + * to refer to the highest version code of this package. + * @param packageName The package name. + * @param versionCode The version code. + */ + public VersionedPackage(@NonNull String packageName, + @VersionCode int versionCode) { + mPackageName = packageName; + mVersionCode = versionCode; + } + private VersionedPackage(Parcel parcel) { + mPackageName = parcel.readString(); + mVersionCode = parcel.readInt(); + } + /** + * Gets the package name. + * + * @return The package name. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + /** + * Gets the version code. + * + * @return The version code. + */ + public @VersionCode int getVersionCode() { + return mVersionCode; + } + @Override + public String toString() { + return "VersionedPackage[" + mPackageName + "/" + mVersionCode + "]"; + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mPackageName); + parcel.writeInt(mVersionCode); + } + public static final Creator CREATOR = new Creator() { + @Override + public VersionedPackage createFromParcel(Parcel source) { + return new VersionedPackage(source); + } + @Override + public VersionedPackage[] newArray(int size) { + return new VersionedPackage[size]; + } + }; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/content/res/CompatibilityInfo.java b/AndroidCompat/src/main/java/android/content/res/CompatibilityInfo.java new file mode 100644 index 00000000..e215f6f0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/content/res/CompatibilityInfo.java @@ -0,0 +1,296 @@ +/* + * 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 android.content.res; + +import android.content.pm.ApplicationInfo; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; + +/** + * CompatibilityInfo class keeps the information about compatibility mode that the application is + * running under. + * + * {@hide} + */ +public class CompatibilityInfo implements Parcelable { + + /** default compatibility info object for compatible applications */ + public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = null; + + /** + * This is the number of pixels we would like to have along the + * short axis of an app that needs to run on a normal size screen. + */ + public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320; + + /** + * This is the maximum aspect ratio we will allow while keeping + * applications in a compatible screen size. + */ + public static final float MAXIMUM_ASPECT_RATIO = (854f/480f); + + /** + * A compatibility flags + */ + private final int mCompatibilityFlags; + + /** + * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f) + * {@see compatibilityFlag} + */ + private static final int SCALING_REQUIRED = 1; + + /** + * Application must always run in compatibility mode? + */ + private static final int ALWAYS_NEEDS_COMPAT = 2; + + /** + * Application never should run in compatibility mode? + */ + private static final int NEVER_NEEDS_COMPAT = 4; + + /** + * Set if the application needs to run in screen size compatibility mode. + */ + private static final int NEEDS_SCREEN_COMPAT = 8; + + /** + * The effective screen density we have selected for this application. + */ + public final int applicationDensity; + + /** + * Application's scale. + */ + public final float applicationScale; + + /** + * Application's inverted scale. + */ + public final float applicationInvertedScale; + + public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat) { + throw new RuntimeException("Stub!"); + } + + private CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale) { + throw new RuntimeException("Stub!"); + } + + private CompatibilityInfo() { + throw new RuntimeException("Stub!"); + } + + /** + * @return true if the scaling is required + */ + public boolean isScalingRequired() { + throw new RuntimeException("Stub!"); + } + + public boolean supportsScreen() { + throw new RuntimeException("Stub!"); + } + + public boolean neverSupportsScreen() { + throw new RuntimeException("Stub!"); + } + + public boolean alwaysSupportsScreen() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the translator which translates the coordinates in compatibility mode. + * @param params the window's parameter + */ + public Translator getTranslator() { + throw new RuntimeException("Stub!"); + } + + /** + * A helper object to translate the screen and window coordinates back and forth. + * @hide + */ + public class Translator { + + public final float applicationScale; + + public final float applicationInvertedScale; + + private Rect mContentInsetsBuffer = null; + + private Rect mVisibleInsetsBuffer = null; + + private Region mTouchableAreaBuffer = null; + + Translator(float applicationScale, float applicationInvertedScale) { + throw new RuntimeException("Stub!"); + } + + Translator() { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the screen rect to the application frame. + */ + public void translateRectInScreenToAppWinFrame(Rect rect) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the region in window to screen. + */ + public void translateRegionInWindowToScreen(Region transparentRegion) { + throw new RuntimeException("Stub!"); + } + + /** + * Apply translation to the canvas that is necessary to draw the content. + */ + public void translateCanvas(Canvas canvas) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the motion event captured on screen to the application's window. + */ + public void translateEventInScreenToAppWindow(MotionEvent event) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the window's layout parameter, from application's view to + * Screen's view. + */ + public void translateWindowLayout(WindowManager.LayoutParams params) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate a Rect in application's window to screen. + */ + public void translateRectInAppWindowToScreen(Rect rect) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate a Rect in screen coordinates into the app window's coordinates. + */ + public void translateRectInScreenToAppWindow(Rect rect) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate a Point in screen coordinates into the app window's coordinates. + */ + public void translatePointInScreenToAppWindow(PointF point) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the location of the sub window. + * @param params + */ + public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the content insets in application window to Screen. This uses + * the internal buffer for content insets to avoid extra object allocation. + */ + public Rect getTranslatedContentInsets(Rect contentInsets) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the visible insets in application window to Screen. This uses + * the internal buffer for visible insets to avoid extra object allocation. + */ + public Rect getTranslatedVisibleInsets(Rect visibleInsets) { + throw new RuntimeException("Stub!"); + } + + /** + * Translate the touchable area in application window to Screen. This uses + * the internal buffer for touchable area to avoid extra object allocation. + */ + public Region getTranslatedTouchableArea(Region touchableArea) { + throw new RuntimeException("Stub!"); + } + } + + public void applyToDisplayMetrics(DisplayMetrics inoutDm) { + throw new RuntimeException("Stub!"); + } + + public void applyToConfiguration(int displayDensity, Configuration inoutConfig) { + throw new RuntimeException("Stub!"); + } + + /** + * Compute the frame Rect for applications runs under compatibility mode. + * + * @param dm the display metrics used to compute the frame size. + * @param outDm If non-null the width and height will be set to their scaled values. + * @return Returns the scaling factor for the window. + */ + public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { + throw new RuntimeException("Stub!"); + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("Stub!"); + } + + @Override + public String toString() { + throw new RuntimeException("Stub!"); + } + + @Override + public int hashCode() { + throw new RuntimeException("Stub!"); + } + + @Override + public int describeContents() { + throw new RuntimeException("Stub!"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new RuntimeException("Stub!"); + } + + public static final Parcelable.Creator CREATOR = null; + + private CompatibilityInfo(Parcel source) { + throw new RuntimeException("Stub!"); + } +} diff --git a/AndroidCompat/src/main/java/android/database/AbstractCursor.java b/AndroidCompat/src/main/java/android/database/AbstractCursor.java new file mode 100644 index 00000000..c01da62a --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/AbstractCursor.java @@ -0,0 +1,414 @@ +/* + * 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 android.database; +import android.content.ContentResolver; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import kotlin.NotImplementedError; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +/** + * 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 { + private static final String TAG = "Cursor"; + /** + * @removed This field should not be used. + */ + protected HashMap> mUpdatedRows; + /** + * @removed This field should not be used. + */ + protected int mRowIdColumnIndex; + /** + * @removed This field should not be used. + */ + protected Long mCurrentRowID; + /** + * @deprecated Use {@link #getPosition()} instead. + */ + @Deprecated + protected int mPos; + /** + * @deprecated Use {@link #isClosed()} instead. + */ + @Deprecated + protected boolean mClosed; + /** + * @deprecated Do not use. + */ + @Deprecated + protected ContentResolver mContentResolver; + private Uri mNotifyUri; + private final Object mSelfObserverLock = new Object(); + private ContentObserver mSelfObserver; + private boolean mSelfObserverRegistered; + private final DataSetObservable mDataSetObservable = new DataSetObservable(); + private final ContentObservable mContentObservable = new ContentObservable(); + private Bundle mExtras = Bundle.EMPTY; + /* -------------------------------------------------------- */ + /* These need to be implemented by subclasses */ + @Override + abstract public int getCount(); + @Override + abstract public String[] getColumnNames(); + @Override + abstract public String getString(int column); + @Override + abstract public short getShort(int column); + @Override + abstract public int getInt(int column); + @Override + abstract public long getLong(int column); + @Override + abstract public float getFloat(int column); + @Override + abstract public double getDouble(int column); + @Override + abstract public boolean isNull(int column); + @Override + public int getType(int column) { + // Reflects the assumption that all commonly used field types (meaning everything + // but blobs) are convertible to strings so it should be safe to call + // getString to retrieve them. + return FIELD_TYPE_STRING; + } + // TODO implement getBlob in all cursor types + @Override + public byte[] getBlob(int column) { + throw new UnsupportedOperationException("getBlob is not supported"); + } + /* -------------------------------------------------------- */ + /* Methods that may optionally be implemented by subclasses */ + /** + * If the cursor is backed by a {@link CursorWindow}, returns a pre-filled + * window with the contents of the cursor, otherwise null. + * + * @return The pre-filled window that backs this cursor, or null if none. + */ + @Override + public CursorWindow getWindow() { + return null; + } + @Override + public int getColumnCount() { + return getColumnNames().length; + } + @Override + public void deactivate() { + onDeactivateOrClose(); + } + /** @hide */ + protected void onDeactivateOrClose() { + if (mSelfObserver != null) { + mContentResolver.unregisterContentObserver(mSelfObserver); + mSelfObserverRegistered = false; + } + mDataSetObservable.notifyInvalidated(); + } + @Override + public boolean requery() { + if (mSelfObserver != null && mSelfObserverRegistered == false) { + mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); + mSelfObserverRegistered = true; + } + mDataSetObservable.notifyChanged(); + return true; + } + @Override + public boolean isClosed() { + return mClosed; + } + @Override + public void close() { + mClosed = true; + mContentObservable.unregisterAll(); + onDeactivateOrClose(); + } + /** + * 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 + */ + @Override + public boolean onMove(int oldPosition, int newPosition) { + return true; + } + @Override + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + // Default implementation, uses getString + String result = getString(columnIndex); + if (result != null) { + char[] data = buffer.data; + if (data == null || data.length < result.length()) { + buffer.data = result.toCharArray(); + } else { + result.getChars(0, result.length(), data, 0); + } + buffer.sizeCopied = result.length(); + } else { + buffer.sizeCopied = 0; + } + } + /* -------------------------------------------------------- */ + /* Implementation */ + public AbstractCursor() { + mPos = -1; + } + @Override + public final int getPosition() { + return mPos; + } + @Override + public final boolean moveToPosition(int position) { + // Make sure position isn't past the end of the cursor + final int count = getCount(); + if (position >= count) { + mPos = count; + return false; + } + // Make sure position isn't before the beginning of the cursor + if (position < 0) { + mPos = -1; + return false; + } + // Check for no-op moves, and skip the rest of the work for them + if (position == mPos) { + return true; + } + boolean result = onMove(mPos, position); + if (result == false) { + mPos = -1; + } else { + mPos = position; + } + return result; + } + @Override + public void fillWindow(int position, CursorWindow window) { + DatabaseUtils.cursorFillWindow(this, position, window); + } + @Override + public final boolean move(int offset) { + return moveToPosition(mPos + offset); + } + @Override + public final boolean moveToFirst() { + return moveToPosition(0); + } + @Override + public final boolean moveToLast() { + return moveToPosition(getCount() - 1); + } + @Override + public final boolean moveToNext() { + return moveToPosition(mPos + 1); + } + @Override + public final boolean moveToPrevious() { + return moveToPosition(mPos - 1); + } + @Override + public final boolean isFirst() { + return mPos == 0 && getCount() != 0; + } + @Override + public final boolean isLast() { + int cnt = getCount(); + return mPos == (cnt - 1) && cnt != 0; + } + @Override + public final boolean isBeforeFirst() { + if (getCount() == 0) { + return true; + } + return mPos == -1; + } + @Override + public final boolean isAfterLast() { + if (getCount() == 0) { + return true; + } + return mPos == getCount(); + } + @Override + public int getColumnIndex(String columnName) { + // Hack according to bug 903852 + 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); + } + String columnNames[] = getColumnNames(); + int length = columnNames.length; + for (int i = 0; i < length; i++) { + if (columnNames[i].equalsIgnoreCase(columnName)) { + return i; + } + } + if (false) { + if (getCount() > 0) { + Log.w("AbstractCursor", "Unknown column " + columnName); + } + } + return -1; + } + @Override + public int getColumnIndexOrThrow(String columnName) { + final int index = getColumnIndex(columnName); + if (index < 0) { + throw new IllegalArgumentException("column '" + columnName + "' does not exist"); + } + return index; + } + @Override + public String getColumnName(int columnIndex) { + return getColumnNames()[columnIndex]; + } + @Override + public void registerContentObserver(ContentObserver observer) { + mContentObservable.registerObserver(observer); + } + @Override + public void unregisterContentObserver(ContentObserver observer) { + // cursor will unregister all observers when it close + if (!mClosed) { + mContentObservable.unregisterObserver(observer); + } + } + @Override + public void registerDataSetObserver(DataSetObserver observer) { + mDataSetObservable.registerObserver(observer); + } + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + mDataSetObservable.unregisterObserver(observer); + } + /** + * Subclasses must call this method when they finish committing updates to notify all + * observers. + * + * @param selfChange + */ + protected void onChange(boolean selfChange) { + synchronized (mSelfObserverLock) { + mContentObservable.dispatchChange(selfChange, null); + if (mNotifyUri != null && selfChange) { + mContentResolver.notifyChange(mNotifyUri, mSelfObserver); + } + } + } + /** + * Specifies a content URI to watch for changes. + * + * @param cr The content resolver from the caller's context. + * @param notifyUri The URI to watch for changes. This can be a + * specific row URI, or a base URI for a whole class of content. + */ + @Override + public void setNotificationUri(ContentResolver cr, Uri notifyUri) { + throw new NotImplementedError("Not implemented!"); + } + @Override + public Uri getNotificationUri() { + synchronized (mSelfObserverLock) { + return mNotifyUri; + } + } + @Override + public boolean getWantsAllOnMoveCalls() { + return false; + } + @Override + public void setExtras(Bundle extras) { + mExtras = (extras == null) ? Bundle.EMPTY : extras; + } + @Override + public Bundle getExtras() { + return mExtras; + } + @Override + public Bundle respond(Bundle extras) { + return Bundle.EMPTY; + } + /** + * @deprecated Always returns false since Cursors do not support updating rows + */ + @Deprecated + protected boolean isFieldUpdated(int columnIndex) { + return false; + } + /** + * @deprecated Always returns null since Cursors do not support updating rows + */ + @Deprecated + protected Object getUpdatedField(int columnIndex) { + return null; + } + /** + * This function throws CursorIndexOutOfBoundsException if + * the cursor position is out of bounds. Subclass implementations of + * the get functions should call this before attempting + * to retrieve data. + * + * @throws CursorIndexOutOfBoundsException + */ + protected void checkPosition() { + if (-1 == mPos || getCount() == mPos) { + throw new CursorIndexOutOfBoundsException(mPos, getCount()); + } + } + @Override + protected void finalize() { + if (mSelfObserver != null && mSelfObserverRegistered == true) { + mContentResolver.unregisterContentObserver(mSelfObserver); + } + try { + if (!mClosed) close(); + } catch(Exception e) { } + } + /** + * Cursors use this class to track changes others make to their URI. + */ + protected static class SelfContentObserver extends ContentObserver { + WeakReference mCursor; + public SelfContentObserver(AbstractCursor cursor) { + super(null); + mCursor = new WeakReference(cursor); + } + @Override + public boolean deliverSelfNotifications() { + return false; + } + @Override + public void onChange(boolean selfChange) { + AbstractCursor cursor = mCursor.get(); + if (cursor != null) { + cursor.onChange(false); + } + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/AbstractWindowedCursor.java b/AndroidCompat/src/main/java/android/database/AbstractWindowedCursor.java new file mode 100644 index 00000000..889de483 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/AbstractWindowedCursor.java @@ -0,0 +1,189 @@ +/* + * 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 android.database; + +import xyz.nulldev.androidcompat.db.ScrollableResultSet; + +/** + * A base class for Cursors that store their data in {@link CursorWindow}s. + *

+ * The cursor owns the cursor window it uses. When the cursor is closed, + * its window is also closed. Likewise, when the window used by the cursor is + * changed, its old window is closed. This policy of strict ownership ensures + * that cursor windows are not leaked. + *

+ * Subclasses are responsible for filling the cursor window with data during + * {@link #onMove(int, int)}, allocating a new cursor window if necessary. + * During {@link #requery()}, the existing cursor window should be cleared and + * filled with new data. + *

+ * If the contents of the cursor change or become invalid, the old window must be closed + * (because it is owned by the cursor) and set to null. + *

+ */ +public abstract class AbstractWindowedCursor extends AbstractCursor { + /** + * The cursor window owned by this cursor. + */ + protected CursorWindow mWindow; + @Override + public byte[] getBlob(int columnIndex) { + checkPosition(); + return mWindow.getBlob(mPos, columnIndex); + } + @Override + public String getString(int columnIndex) { + checkPosition(); + return mWindow.getString(mPos, columnIndex); + } + @Override + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + checkPosition(); + mWindow.copyStringToBuffer(mPos, columnIndex, buffer); + } + @Override + public short getShort(int columnIndex) { + checkPosition(); + return mWindow.getShort(mPos, columnIndex); + } + @Override + public int getInt(int columnIndex) { + checkPosition(); + return mWindow.getInt(mPos, columnIndex); + } + @Override + public long getLong(int columnIndex) { + checkPosition(); + return mWindow.getLong(mPos, columnIndex); + } + @Override + public float getFloat(int columnIndex) { + checkPosition(); + return mWindow.getFloat(mPos, columnIndex); + } + @Override + public double getDouble(int columnIndex) { + checkPosition(); + return mWindow.getDouble(mPos, columnIndex); + } + @Override + public boolean isNull(int columnIndex) { + checkPosition(); + return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL; + } + /** + * @deprecated Use {@link #getType} + */ + @Deprecated + public boolean isBlob(int columnIndex) { + return getType(columnIndex) == Cursor.FIELD_TYPE_BLOB; + } + /** + * @deprecated Use {@link #getType} + */ + @Deprecated + public boolean isString(int columnIndex) { + return getType(columnIndex) == Cursor.FIELD_TYPE_STRING; + } + /** + * @deprecated Use {@link #getType} + */ + @Deprecated + public boolean isLong(int columnIndex) { + return getType(columnIndex) == Cursor.FIELD_TYPE_INTEGER; + } + /** + * @deprecated Use {@link #getType} + */ + @Deprecated + public boolean isFloat(int columnIndex) { + return getType(columnIndex) == Cursor.FIELD_TYPE_FLOAT; + } + @Override + public int getType(int columnIndex) { + checkPosition(); + return mWindow.getType(mPos, columnIndex); + } + @Override + protected void checkPosition() { + super.checkPosition(); + + if (mWindow == null) { + throw new StaleDataException("Attempting to access a closed CursorWindow." + + "Most probable cause: cursor is deactivated prior to calling this method."); + } + } + @Override + public CursorWindow getWindow() { + return mWindow; + } + /** + * Sets a new cursor window for the cursor to use. + *

+ * The cursor takes ownership of the provided cursor window; the cursor window + * will be closed when the cursor is closed or when the cursor adopts a new + * cursor window. + *

+ * If the cursor previously had a cursor window, then it is closed when the + * new cursor window is assigned. + *

+ * + * @param window The new cursor window, typically a remote cursor window. + */ + public void setWindow(CursorWindow window) { + if (window != mWindow) { + closeWindow(); + mWindow = window; + } + } + /** + * Returns true if the cursor has an associated cursor window. + * + * @return True if the cursor has an associated cursor window. + */ + public boolean hasWindow() { + return mWindow != null; + } + /** + * Closes the cursor window and sets {@link #mWindow} to null. + * @hide + */ + protected void closeWindow() { + if (mWindow != null) { + mWindow.close(); + mWindow = null; + } + } + /** + * If there is a window, clear it. + * Otherwise, creates a new window. + * + * @hide + */ + protected void clearOrCreateWindow(ScrollableResultSet rs) { + if (mWindow == null) { + mWindow = new CursorWindow(rs); + } else { + mWindow.clear(); + } + } + /** @hide */ + @Override + protected void onDeactivateOrClose() { + super.onDeactivateOrClose(); + closeWindow(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/CharArrayBuffer.java b/AndroidCompat/src/main/java/android/database/CharArrayBuffer.java new file mode 100644 index 00000000..086214fb --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/CharArrayBuffer.java @@ -0,0 +1,31 @@ +/* + * 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 android.database; +/** + * This is used for {@link Cursor#copyStringToBuffer} + */ +public final class CharArrayBuffer { + public CharArrayBuffer(int size) { + data = new char[size]; + } + + public CharArrayBuffer(char[] buf) { + data = buf; + } + + public char[] data; // In and out parameter + public int sizeCopied; // Out parameter +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/ContentObservable.java b/AndroidCompat/src/main/java/android/database/ContentObservable.java new file mode 100644 index 00000000..a57ec78c --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/ContentObservable.java @@ -0,0 +1,91 @@ +/* + * 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 android.database; + +import android.net.Uri; + +/** + * A specialization of {@link Observable} for {@link ContentObserver} + * that provides methods for sending notifications to a list of + * {@link ContentObserver} objects. + */ +public class ContentObservable extends Observable { + // Even though the generic method defined in Observable would be perfectly + // fine on its own, we can't delete this overridden method because it would + // potentially break binary compatibility with existing applications. + @Override + public void registerObserver(ContentObserver observer) { + super.registerObserver(observer); + } + + /** + * Invokes {@link ContentObserver#dispatchChange(boolean)} on each observer. + *

+ * If selfChange is true, only delivers the notification + * to the observer if it has indicated that it wants to receive self-change + * notifications by implementing {@link ContentObserver#deliverSelfNotifications} + * to return true. + *

+ * + * @param selfChange True if this is a self-change notification. + * + * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead. + */ + @Deprecated + public void dispatchChange(boolean selfChange) { + dispatchChange(selfChange, null); + } + + /** + * Invokes {@link ContentObserver#dispatchChange(boolean, Uri)} on each observer. + * Includes the changed content Uri when available. + *

+ * If selfChange is true, only delivers the notification + * to the observer if it has indicated that it wants to receive self-change + * notifications by implementing {@link ContentObserver#deliverSelfNotifications} + * to return true. + *

+ * + * @param selfChange True if this is a self-change notification. + * @param uri The Uri of the changed content, or null if unknown. + */ + public void dispatchChange(boolean selfChange, Uri uri) { + synchronized(mObservers) { + for (ContentObserver observer : mObservers) { + if (!selfChange || observer.deliverSelfNotifications()) { + observer.dispatchChange(selfChange, uri); + } + } + } + } + + /** + * Invokes {@link ContentObserver#onChange} on each observer. + * + * @param selfChange True if this is a self-change notification. + * + * @deprecated Use {@link #dispatchChange} instead. + */ + @Deprecated + public void notifyChange(boolean selfChange) { + synchronized(mObservers) { + for (ContentObserver observer : mObservers) { + observer.onChange(selfChange, null); + } + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/CursorWindow.java b/AndroidCompat/src/main/java/android/database/CursorWindow.java new file mode 100644 index 00000000..42bc840a --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/CursorWindow.java @@ -0,0 +1,493 @@ + +/* + * 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 android.database; +import android.database.sqlite.SQLiteClosable; +import android.database.sqlite.SQLiteException; +import android.os.Parcel; +import kotlin.NotImplementedError; +import xyz.nulldev.androidcompat.db.ScrollableResultSet; + +import java.sql.SQLException; + +/** + * A buffer containing multiple cursor rows. + *

+ * A {@link CursorWindow} is read-write when initially created and used locally. + * When sent to a remote process (by writing it to a {@link Parcel}), the remote process + * receives a read-only view of the cursor window. Typically the cursor window + * will be allocated by the producer, filled with data, and then sent to the + * consumer for reading. + *

+ */ +public class CursorWindow extends SQLiteClosable { + private ScrollableResultSet resultSet; + public CursorWindow(ScrollableResultSet resultSet) { + this.resultSet = resultSet; + } + /** + * Clears out the existing contents of the window, making it safe to reuse + * for new data. + *

+ * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), + * and number of columns in the cursor are all reset to zero. + *

+ */ + public void clear() { + } + /** + * Gets the start position of this cursor window. + *

+ * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + *

+ * + * @return The zero-based start position. + */ + public int getStartPosition() { + return 0; + } + /** + * Sets the start position of this cursor window. + *

+ * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + *

+ * + * @param pos The new zero-based start position. + */ + public void setStartPosition(int pos) { + } + /** + * Gets the number of rows in this window. + * + * @return The number of rows in this cursor window. + */ + public int getNumRows() { + return resultSet.getResultSetLength(); + } + /** + * Sets the number of columns in this window. + *

+ * This method must be called before any rows are added to the window, otherwise + * it will fail to set the number of columns if it differs from the current number + * of columns. + *

+ * + * @param columnNum The new number of columns. + * @return True if successful. + */ + public boolean setNumColumns(int columnNum) { + return true; + } + /** + * Allocates a new row at the end of this cursor window. + * + * @return True if successful, false if the cursor window is out of memory. + */ + public boolean allocRow() { + return true; + } + /** + * Frees the last row in this cursor window. + */ + public void freeLastRow(){ + } + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isNull(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_NULL; + } + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or + * {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isBlob(int row, int column) { + int type = getType(row, column); + return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; + } + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_INTEGER}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isLong(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; + } + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_FLOAT}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isFloat(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; + } + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} + * or {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isString(int row, int column) { + int type = getType(row, column); + return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; + } + /** + * Returns the type of the field at the specified row and column index. + *

+ * The returned field 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 zero-based row index. + * @param column The zero-based column index. + * @return The field type. + */ + public int getType(int row, int column) { + acquireReference(); + try { + jumpToRow(row); + String clazz = resultSet.getMetaData().getColumnClassName(column + 1); + resultSet.getObject(column + 1); + if(resultSet.wasNull()) + return Cursor.FIELD_TYPE_NULL; + if(clazz.equals(String.class.getName())) + return Cursor.FIELD_TYPE_STRING; + else if(clazz.equals(Integer.class.getName()) || clazz.equals(Long.class.getName()) || clazz.equals(Short.class.getName()) || clazz.equals(Byte.class.getName()) || clazz.equals(Boolean.class.getName())) + return Cursor.FIELD_TYPE_INTEGER; + else if(clazz.equals(Double.class.getName()) || clazz.equals(Float.class.getName())) + return Cursor.FIELD_TYPE_FLOAT; + else + throw new SQLiteException("Unknown field type: " + clazz); + } catch (SQLException e) { + throw new SQLiteException("Failed to get type of field at: (" + row + ", " + column + ")!", e); + } finally { + releaseReference(); + } + } + /** + * Gets the value of the field at the specified row and column index as a byte array. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is null.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result + * is the blob value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the array of bytes that make up the internal representation of the + * string value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or + * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a byte array. + */ + public byte[] getBlob(int row, int column) { + throw new NotImplementedError("Not implemented!"); + } + /** + * Gets the value of the field at the specified row and column index as a string. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is null.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the string value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is a string representation of the integer in decimal, obtained by formatting the + * value with the printf family of functions using + * format specifier %lld.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is a string representation of the floating-point value in decimal, obtained by + * formatting the value with the printf family of functions using + * format specifier %g.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a string. + */ + public String getString(int row, int column) { + acquireReference(); + try { + jumpToRow(row); + return resultSet.getString(column + 1); + } finally { + releaseReference(); + } + } + /** + * Copies the text of the field at the specified row and column index into + * a {@link CharArrayBuffer}. + *

+ * The buffer is populated as follows: + *

    + *
  • If the buffer is too small for the value to be copied, then it is + * automatically resized.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer + * is set to an empty string.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer + * is set to the contents of the string.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer + * is set to a string representation of the integer in decimal, obtained by formatting the + * value with the printf family of functions using + * format specifier %lld.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is + * set to a string representation of the floating-point value in decimal, obtained by + * formatting the value with the printf family of functions using + * format specifier %g.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically + * resized if the requested string is larger than the buffer's current capacity. + */ + public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("CharArrayBuffer should not be null"); + } + acquireReference(); + try { + jumpToRow(row); + buffer.data = resultSet.getString(column + 1).toCharArray(); + } finally { + releaseReference(); + } + } + /** + * Gets the value of the field at the specified row and column index as a long. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is 0L.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with strtoll. + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the long value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the floating-point value converted to a long.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a long. + */ + public long getLong(int row, int column) { + acquireReference(); + try { + jumpToRow(row); + return resultSet.getLong(column + 1); + } finally { + releaseReference(); + } + } + /** + * Gets the value of the field at the specified row and column index as a + * double. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is 0.0.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with strtod. + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the integer value converted to a double.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the double value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a double. + */ + public double getDouble(int row, int column) { + acquireReference(); + try { + jumpToRow(row); + return resultSet.getDouble(column + 1); + } finally { + releaseReference(); + } + } + /** + * Gets the value of the field at the specified row and column index as a + * short. + *

+ * The result is determined by invoking {@link #getLong} and converting the + * result to short. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a short. + */ + public short getShort(int row, int column) { + return (short) getLong(row, column); + } + /** + * Gets the value of the field at the specified row and column index as an + * int. + *

+ * The result is determined by invoking {@link #getLong} and converting the + * result to int. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as an int. + */ + public int getInt(int row, int column) { + return (int) getLong(row, column); + } + /** + * Gets the value of the field at the specified row and column index as a + * float. + *

+ * The result is determined by invoking {@link #getDouble} and converting the + * result to float. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as an float. + */ + public float getFloat(int row, int column) { + return (float) getDouble(row, column); + } + /** + * Copies a byte array into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putBlob(byte[] value, int row, int column) { + return true; + } + /** + * Copies a string into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putString(String value, int row, int column) { + return true; + } + /** + * Puts a long integer into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putLong(long value, int row, int column) { + return true; + } + /** + * Puts a double-precision floating point value into the field at the + * specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putDouble(double value, int row, int column) { + return true; + } + /** + * Puts a null value into the field at the specified row and column index. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putNull(int row, int column) { + return true; + } + + @Override + protected void onAllReferencesReleased() { + } + + private void jumpToRow(int row) { + // TODO Optimize + resultSet.first(); + for(int i = 0; i < row; i++) { + resultSet.next(); + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/DataSetObservable.java b/AndroidCompat/src/main/java/android/database/DataSetObservable.java new file mode 100644 index 00000000..a42609d1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/DataSetObservable.java @@ -0,0 +1,54 @@ +/* + * 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 android.database; + +/** + * A specialization of {@link Observable} for {@link DataSetObserver} + * that provides methods for sending notifications to a list of + * {@link DataSetObserver} objects. + */ +public class DataSetObservable extends Observable { + /** + * Invokes {@link DataSetObserver#onChanged} on each observer. + * Called when the contents of the data set have changed. The recipient + * will obtain the new contents the next time it queries the data set. + */ + public void notifyChanged() { + synchronized(mObservers) { + // since onChanged() is implemented by the app, it could do anything, including + // removing itself from {@link mObservers} - and that could cause problems if + // an iterator is used on the ArrayList {@link mObservers}. + // to avoid such problems, just march thru the list in the reverse order. + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onChanged(); + } + } + } + + /** + * Invokes {@link DataSetObserver#onInvalidated} on each observer. + * Called when the data set is no longer valid and cannot be queried again, + * such as when the data set has been closed. + */ + public void notifyInvalidated() { + synchronized (mObservers) { + for (int i = mObservers.size() - 1; i >= 0; i--) { + mObservers.get(i).onInvalidated(); + } + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/DatabaseUtils.java b/AndroidCompat/src/main/java/android/database/DatabaseUtils.java new file mode 100644 index 00000000..466ae14b --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/DatabaseUtils.java @@ -0,0 +1,1288 @@ +/* + * 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 android.database; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.sqlite.SQLiteAbortException; +import android.database.sqlite.SQLiteConstraintException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabaseCorruptException; +import android.database.sqlite.SQLiteDiskIOException; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteFullException; +import android.database.sqlite.SQLiteProgram; +import android.database.sqlite.SQLiteStatement; +import android.os.OperationCanceledException; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.util.Log; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.text.Collator; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +/** + * Static utility methods for dealing with databases and {@link Cursor}s. + */ +public class DatabaseUtils { + private static final String TAG = "DatabaseUtils"; + private static final boolean DEBUG = false; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_SELECT = 1; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_UPDATE = 2; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_ATTACH = 3; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_BEGIN = 4; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_COMMIT = 5; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_ABORT = 6; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_PRAGMA = 7; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_DDL = 8; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_UNPREPARED = 9; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_OTHER = 99; + /** + * Special function for writing an exception result at the header of + * a parcel, to be used when returning an exception from a transaction. + * exception will be re-thrown by the function in another process + * @param reply Parcel to write to + * @param e The Exception to be written. + * @see Parcel#writeNoException + * @see Parcel#writeException + */ + public static final void writeExceptionToParcel(Parcel reply, Exception e) { + int code = 0; + boolean logException = true; + if (e instanceof FileNotFoundException) { + code = 1; + logException = false; + } else if (e instanceof IllegalArgumentException) { + code = 2; + } else if (e instanceof UnsupportedOperationException) { + code = 3; + } else if (e instanceof SQLiteAbortException) { + code = 4; + } else if (e instanceof SQLiteConstraintException) { + code = 5; + } else if (e instanceof SQLiteDatabaseCorruptException) { + code = 6; + } else if (e instanceof SQLiteFullException) { + code = 7; + } else if (e instanceof SQLiteDiskIOException) { + code = 8; + } else if (e instanceof SQLiteException) { + code = 9; + } else if (e instanceof OperationApplicationException) { + code = 10; + } else if (e instanceof OperationCanceledException) { + code = 11; + logException = false; + } else { + reply.writeException(e); + Log.e(TAG, "Writing exception to parcel", e); + return; + } + reply.writeInt(code); + reply.writeString(e.getMessage()); + if (logException) { + Log.e(TAG, "Writing exception to parcel", e); + } + } + /** + * Binds the given Object to the given SQLiteProgram using the proper + * typing. For example, bind numbers as longs/doubles, and everything else + * as a string by call toString() on it. + * + * @param prog the program to bind the object to + * @param index the 1-based index to bind at + * @param value the value to bind + */ + public static void bindObjectToProgram(SQLiteProgram prog, int index, + Object value) { + if (value == null) { + prog.bindNull(index); + } else if (value instanceof Double || value instanceof Float) { + prog.bindDouble(index, ((Number)value).doubleValue()); + } else if (value instanceof Number) { + prog.bindLong(index, ((Number)value).longValue()); + } else if (value instanceof Boolean) { + Boolean bool = (Boolean)value; + if (bool) { + prog.bindLong(index, 1); + } else { + prog.bindLong(index, 0); + } + } else if (value instanceof byte[]){ + prog.bindBlob(index, (byte[]) value); + } else { + prog.bindString(index, value.toString()); + } + } + /** + * 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 Cursor.FIELD_TYPE_NULL; + } else if (obj instanceof byte[]) { + return Cursor.FIELD_TYPE_BLOB; + } else if (obj instanceof Float || obj instanceof Double) { + return Cursor.FIELD_TYPE_FLOAT; + } else if (obj instanceof Long || obj instanceof Integer + || obj instanceof Short || obj instanceof Byte) { + return Cursor.FIELD_TYPE_INTEGER; + } else { + return Cursor.FIELD_TYPE_STRING; + } + } + /** + * Fills the specified cursor window by iterating over the contents of the cursor. + * The window is filled until the cursor is exhausted or the window runs out + * of space. + * + * The original position of the cursor is left unchanged by this operation. + * + * @param cursor The cursor that contains the data to put in the window. + * @param position The start position for filling the window. + * @param window The window to fill. + * @hide + */ + public static void cursorFillWindow(final Cursor cursor, + int position, final 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)) { + rowloop: 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 rowloop; + } + } + position += 1; + } while (cursor.moveToNext()); + } + cursor.moveToPosition(oldPos); + } + /** + * Appends an SQL string to the given StringBuilder, including the opening + * and closing single quotes. Any single quotes internal to sqlString will + * be escaped. + * + * This method is deprecated because we want to encourage everyone + * to use the "?" binding form. However, when implementing a + * ContentProvider, one may want to add WHERE clauses that were + * not provided by the caller. Since "?" is a positional form, + * using it in this case could break the caller because the + * indexes would be shifted to accomodate the ContentProvider's + * internal bindings. In that case, it may be necessary to + * construct a WHERE clause manually. This method is useful for + * those cases. + * + * @param sb the StringBuilder that the SQL string will be appended to + * @param sqlString the raw string to be appended, which may contain single + * quotes + */ + public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { + sb.append('\''); + if (sqlString.indexOf('\'') != -1) { + int length = sqlString.length(); + for (int i = 0; i < length; i++) { + char c = sqlString.charAt(i); + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } + } else + sb.append(sqlString); + sb.append('\''); + } + /** + * SQL-escape a string. + */ + public static String sqlEscapeString(String value) { + StringBuilder escaper = new StringBuilder(); + DatabaseUtils.appendEscapedSQLString(escaper, value); + return escaper.toString(); + } + /** + * Appends an Object to an SQL string with the proper escaping, etc. + */ + public static final void appendValueToSql(StringBuilder sql, Object value) { + if (value == null) { + sql.append("NULL"); + } else if (value instanceof Boolean) { + Boolean bool = (Boolean)value; + if (bool) { + sql.append('1'); + } else { + sql.append('0'); + } + } else { + appendEscapedSQLString(sql, value.toString()); + } + } + /** + * Concatenates two SQL WHERE clauses, handling empty or null values. + */ + public static String concatenateWhere(String a, String b) { + if (TextUtils.isEmpty(a)) { + return b; + } + if (TextUtils.isEmpty(b)) { + return a; + } + return "(" + a + ") AND (" + b + ")"; + } + /** + * return the collation key + * @param name + * @return the collation key + */ + public static String getCollationKey(String name) { + byte [] arr = getCollationKeyInBytes(name); + try { + return new String(arr, 0, getKeyLen(arr), "ISO8859_1"); + } catch (Exception ex) { + return ""; + } + } + /** + * return the collation key in hex format + * @param name + * @return the collation key in hex format + */ + public static String getHexCollationKey(String name) { + byte[] arr = getCollationKeyInBytes(name); + char[] keys = encodeHex(arr); + return new String(keys, 0, getKeyLen(arr) * 2); + } + /** + * Used building output as Hex + */ + private static final char[] DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + private static char[] encodeHex(byte[] input) { + int l = input.length; + char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = DIGITS[(0xF0 & input[i]) >>> 4 ]; + out[j++] = DIGITS[ 0x0F & input[i] ]; + } + return out; + } + private static int getKeyLen(byte[] arr) { + if (arr[arr.length - 1] != 0) { + return arr.length; + } else { + // remove zero "termination" + return arr.length-1; + } + } + private static byte[] getCollationKeyInBytes(String name) { + if (mColl == null) { + mColl = Collator.getInstance(); + mColl.setStrength(Collator.PRIMARY); + } + return mColl.getCollationKey(name).toByteArray(); + } + private static Collator mColl = null; + /** + * Prints the contents of a Cursor to System.out. The position is restored + * after printing. + * + * @param cursor the cursor to print + */ + public static void dumpCursor(Cursor cursor) { + dumpCursor(cursor, System.out); + } + /** + * Prints the contents of a Cursor to a PrintSteam. The position is restored + * after printing. + * + * @param cursor the cursor to print + * @param stream the stream to print to + */ + public static void dumpCursor(Cursor cursor, PrintStream stream) { + stream.println(">>>>> Dumping cursor " + cursor); + if (cursor != null) { + int startPos = cursor.getPosition(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + dumpCurrentRow(cursor, stream); + } + cursor.moveToPosition(startPos); + } + stream.println("<<<<<"); + } + /** + * Prints the contents of a Cursor to a StringBuilder. The position + * is restored after printing. + * + * @param cursor the cursor to print + * @param sb the StringBuilder to print to + */ + public static void dumpCursor(Cursor cursor, StringBuilder sb) { + sb.append(">>>>> Dumping cursor " + cursor + "\n"); + if (cursor != null) { + int startPos = cursor.getPosition(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + dumpCurrentRow(cursor, sb); + } + cursor.moveToPosition(startPos); + } + sb.append("<<<<<\n"); + } + /** + * Prints the contents of a Cursor to a String. The position is restored + * after printing. + * + * @param cursor the cursor to print + * @return a String that contains the dumped cursor + */ + public static String dumpCursorToString(Cursor cursor) { + StringBuilder sb = new StringBuilder(); + dumpCursor(cursor, sb); + return sb.toString(); + } + /** + * Prints the contents of a Cursor's current row to System.out. + * + * @param cursor the cursor to print from + */ + public static void dumpCurrentRow(Cursor cursor) { + dumpCurrentRow(cursor, System.out); + } + /** + * Prints the contents of a Cursor's current row to a PrintSteam. + * + * @param cursor the cursor to print + * @param stream the stream to print to + */ + public static void dumpCurrentRow(Cursor cursor, PrintStream stream) { + String[] cols = cursor.getColumnNames(); + stream.println("" + cursor.getPosition() + " {"); + int length = cols.length; + for (int i = 0; i< length; i++) { + String value; + try { + value = cursor.getString(i); + } catch (SQLiteException e) { + // assume that if the getString threw this exception then the column is not + // representable by a string, e.g. it is a BLOB. + value = ""; + } + stream.println(" " + cols[i] + '=' + value); + } + stream.println("}"); + } + /** + * Prints the contents of a Cursor's current row to a StringBuilder. + * + * @param cursor the cursor to print + * @param sb the StringBuilder to print to + */ + public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) { + String[] cols = cursor.getColumnNames(); + sb.append("" + cursor.getPosition() + " {\n"); + int length = cols.length; + for (int i = 0; i < length; i++) { + String value; + try { + value = cursor.getString(i); + } catch (SQLiteException e) { + // assume that if the getString threw this exception then the column is not + // representable by a string, e.g. it is a BLOB. + value = ""; + } + sb.append(" " + cols[i] + '=' + value + "\n"); + } + sb.append("}\n"); + } + /** + * Dump the contents of a Cursor's current row to a String. + * + * @param cursor the cursor to print + * @return a String that contains the dumped cursor row + */ + public static String dumpCurrentRowToString(Cursor cursor) { + StringBuilder sb = new StringBuilder(); + dumpCurrentRow(cursor, sb); + return sb.toString(); + } + /** + * Reads a String out of a field in a Cursor and writes it to a Map. + * + * @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 + */ + public static void cursorStringToContentValues(Cursor cursor, String field, + ContentValues values) { + cursorStringToContentValues(cursor, field, values, field); + } + /** + * Reads a String out of a field in a Cursor and writes it to an InsertHelper. + * + * @param cursor The cursor to read from + * @param field The TEXT field to read + * @param inserter The InsertHelper to bind into + * @param index the index of the bind entry in the InsertHelper + */ + public static void cursorStringToInsertHelper(Cursor cursor, String field, + InsertHelper inserter, int index) { + inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field))); + } + /** + * Reads a String out of a field in a Cursor and writes it to a Map. + * + * @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 key The key to store the value with in the map + */ + public static void cursorStringToContentValues(Cursor cursor, String field, + ContentValues values, String key) { + values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field))); + } + /** + * Reads an Integer out of a field in a Cursor and writes it to a Map. + * + * @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 + */ + public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) { + cursorIntToContentValues(cursor, field, values, field); + } + /** + * Reads a Integer out of a field in a Cursor and writes it to a Map. + * + * @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 key The key to store the value with in the map + */ + public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values, + String key) { + int colIndex = cursor.getColumnIndex(field); + if (!cursor.isNull(colIndex)) { + values.put(key, cursor.getInt(colIndex)); + } else { + values.put(key, (Integer) null); + } + } + /** + * Reads a Long out of a field in a Cursor and writes it to a Map. + * + * @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 + */ + public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values) + { + cursorLongToContentValues(cursor, field, values, field); + } + /** + * Reads a Long out of a field in a Cursor and writes it to a Map. + * + * @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 key The key to store the value with in the map + */ + public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values, + String key) { + int colIndex = cursor.getColumnIndex(field); + if (!cursor.isNull(colIndex)) { + Long value = Long.valueOf(cursor.getLong(colIndex)); + values.put(key, value); + } else { + values.put(key, (Long) null); + } + } + /** + * Reads a Double out of a field in a Cursor and writes it to a Map. + * + * @param cursor The cursor to read from + * @param field The REAL field to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values) + { + cursorDoubleToContentValues(cursor, field, values, field); + } + /** + * Reads a Double out of a field in a Cursor and writes it to a Map. + * + * @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 key The key to store the value with in the map + */ + public static void cursorDoubleToContentValues(Cursor cursor, String field, + ContentValues values, String key) { + int colIndex = cursor.getColumnIndex(field); + if (!cursor.isNull(colIndex)) { + values.put(key, cursor.getDouble(colIndex)); + } else { + values.put(key, (Double) null); + } + } + /** + * 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. + */ + public static void cursorRowToContentValues(Cursor cursor, ContentValues values) { + AbstractWindowedCursor awc = + (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null; + String[] columns = cursor.getColumnNames(); + int length = columns.length; + for (int i = 0; i < length; i++) { + if (awc != null && awc.isBlob(i)) { + values.put(columns[i], cursor.getBlob(i)); + } else { + values.put(columns[i], cursor.getString(i)); + } + } + } + /** + * Picks a start position for {@link Cursor#fillWindow} such that the + * window will contain the requested row and a useful range of rows + * around it. + * + * When the data set is too large to fit in a cursor window, seeking the + * cursor can become a very expensive operation since we have to run the + * query again when we move outside the bounds of the current window. + * + * We try to choose a start position for the cursor window such that + * 1/3 of the window's capacity is used to hold rows before the requested + * position and 2/3 of the window's capacity is used to hold rows after the + * requested position. + * + * @param cursorPosition The row index of the row we want to get. + * @param cursorWindowCapacity The estimated number of rows that can fit in + * a cursor window, or 0 if unknown. + * @return The recommended start position, always less than or equal to + * the requested row. + * @hide + */ + public static int cursorPickFillWindowStartPosition( + int cursorPosition, int cursorWindowCapacity) { + return Math.max(cursorPosition - cursorWindowCapacity / 3, 0); + } + /** + * Query the table for the number of rows in the table. + * @param db the database the table is in + * @param table the name of the table to query + * @return the number of rows in the table + */ + public static long queryNumEntries(SQLiteDatabase db, String table) { + return queryNumEntries(db, table, null, null); + } + /** + * Query the table for the number of rows in the table. + * @param db the database the table is in + * @param table the name of the table to query + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE itself). + * Passing null will count all rows for the given table + * @return the number of rows in the table filtered by the selection + */ + public static long queryNumEntries(SQLiteDatabase db, String table, String selection) { + return queryNumEntries(db, table, selection, null); + } + /** + * Query the table for the number of rows in the table. + * @param db the database the table is in + * @param table the name of the table to query + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE itself). + * Passing null will count 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. + * @return the number of rows in the table filtered by the selection + */ + public static long queryNumEntries(SQLiteDatabase db, String table, String selection, + String[] selectionArgs) { + String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : ""; + return longForQuery(db, "select count(*) from " + table + s, + selectionArgs); + } + /** + * Query the table to check whether a table is empty or not + * @param db the database the table is in + * @param table the name of the table to query + * @return True if the table is empty + * @hide + */ + public static boolean queryIsEmpty(SQLiteDatabase db, String table) { + long isEmpty = longForQuery(db, "select exists(select 1 from " + table + ")", null); + return isEmpty == 0; + } + /** + * Utility method to run the query on the db and return the value in the + * first column of the first row. + */ + public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { + SQLiteStatement prog = db.compileStatement(query); + try { + return longForQuery(prog, selectionArgs); + } finally { + prog.close(); + } + } + /** + * Utility method to run the pre-compiled query and return the value in the + * first column of the first row. + */ + public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) { + prog.bindAllArgsAsStrings(selectionArgs); + return prog.simpleQueryForLong(); + } + /** + * Utility method to run the query on the db and return the value in the + * first column of the first row. + */ + public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { + SQLiteStatement prog = db.compileStatement(query); + try { + return stringForQuery(prog, selectionArgs); + } finally { + prog.close(); + } + } + /** + * Utility method to run the pre-compiled query and return the value in the + * first column of the first row. + */ + public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) { + prog.bindAllArgsAsStrings(selectionArgs); + return prog.simpleQueryForString(); + } + /** + * Utility method to run the query on the db and return the blob value in the + * first column of the first row. + * + * @return A read-only file descriptor for a copy of the blob value. + */ + public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db, + String query, String[] selectionArgs) { + SQLiteStatement prog = db.compileStatement(query); + try { + return blobFileDescriptorForQuery(prog, selectionArgs); + } finally { + prog.close(); + } + } + /** + * Utility method to run the pre-compiled query and return the blob value in the + * first column of the first row. + * + * @return A read-only file descriptor for a copy of the blob value. + */ + public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog, + String[] selectionArgs) { + prog.bindAllArgsAsStrings(selectionArgs); + return prog.simpleQueryForBlobFileDescriptor(); + } + /** + * Reads a String out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getString(index)); + } + } + /** + * Reads a Long out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getLong(index)); + } + } + /** + * Reads a Short out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getShort(index)); + } + } + /** + * Reads a Integer out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getInt(index)); + } + } + /** + * Reads a Float out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getFloat(index)); + } + } + /** + * Reads a Double out of a column in a Cursor and writes it to a ContentValues. + * Adds nothing to the ContentValues if the column isn't present or if its value is null. + * + * @param cursor The cursor to read from + * @param column The column to read + * @param values The {@link ContentValues} to put the value into + */ + public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, + String column) { + final int index = cursor.getColumnIndex(column); + if (index != -1 && !cursor.isNull(index)) { + values.put(column, cursor.getDouble(index)); + } + } + /** + * This class allows users to do multiple inserts into a table using + * the same statement. + *

+ * This class is not thread-safe. + *

+ * + * @deprecated Use {@link SQLiteStatement} instead. + */ + @Deprecated + public static class InsertHelper { + private final SQLiteDatabase mDb; + private final String mTableName; + private HashMap mColumns; + private String mInsertSQL = null; + private SQLiteStatement mInsertStatement = null; + private SQLiteStatement mReplaceStatement = null; + private SQLiteStatement mPreparedStatement = null; + /** + * {@hide} + * + * These are the columns returned by sqlite's "PRAGMA + * table_info(...)" command that we depend on. + */ + public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1; + /** + * This field was accidentally exposed in earlier versions of the platform + * so we can hide it but we can't remove it. + * + * @hide + */ + public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4; + /** + * @param db the SQLiteDatabase to insert into + * @param tableName the name of the table to insert into + */ + public InsertHelper(SQLiteDatabase db, String tableName) { + mDb = db; + mTableName = tableName; + } + private void buildSQL() throws SQLException { + StringBuilder sb = new StringBuilder(128); + sb.append("INSERT INTO "); + sb.append(mTableName); + sb.append(" ("); + StringBuilder sbv = new StringBuilder(128); + sbv.append("VALUES ("); + int i = 1; + Cursor cur = null; + try { + cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null); + mColumns = new HashMap(cur.getCount()); + while (cur.moveToNext()) { + String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX); + String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX); + mColumns.put(columnName, i); + sb.append("'"); + sb.append(columnName); + sb.append("'"); + if (defaultValue == null) { + sbv.append("?"); + } else { + sbv.append("COALESCE(?, "); + sbv.append(defaultValue); + sbv.append(")"); + } + sb.append(i == cur.getCount() ? ") " : ", "); + sbv.append(i == cur.getCount() ? ");" : ", "); + ++i; + } + } finally { + if (cur != null) cur.close(); + } + sb.append(sbv); + mInsertSQL = sb.toString(); + if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL); + } + private SQLiteStatement getStatement(boolean allowReplace) throws SQLException { + if (allowReplace) { + if (mReplaceStatement == null) { + if (mInsertSQL == null) buildSQL(); + // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead. + String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6); + mReplaceStatement = mDb.compileStatement(replaceSQL); + } + return mReplaceStatement; + } else { + if (mInsertStatement == null) { + if (mInsertSQL == null) buildSQL(); + mInsertStatement = mDb.compileStatement(mInsertSQL); + } + return mInsertStatement; + } + } + /** + * Performs an insert, adding a new row with the given values. + * + * @param values the set of values with which to populate the + * new row + * @param allowReplace if true, the statement does "INSERT OR + * REPLACE" instead of "INSERT", silently deleting any + * previously existing rows that would cause a conflict + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + private long insertInternal(ContentValues values, boolean allowReplace) { + // Start a transaction even though we don't really need one. + // This is to help maintain compatibility with applications that + // access InsertHelper from multiple threads even though they never should have. + // The original code used to lock the InsertHelper itself which was prone + // to deadlocks. Starting a transaction achieves the same mutual exclusion + // effect as grabbing a lock but without the potential for deadlocks. + mDb.beginTransactionNonExclusive(); + try { + SQLiteStatement stmt = getStatement(allowReplace); + stmt.clearBindings(); + if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName); + for (Map.Entry e: values.valueSet()) { + final String key = e.getKey(); + int i = getColumnIndex(key); + DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue()); + if (DEBUG) { + Log.v(TAG, "binding " + e.getValue() + " to column " + + i + " (" + key + ")"); + } + } + long result = stmt.executeInsert(); + mDb.setTransactionSuccessful(); + return result; + } catch (SQLException e) { + Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e); + return -1; + } finally { + mDb.endTransaction(); + } + } + /** + * Returns the index of the specified column. This is index is suitagble for use + * in calls to bind(). + * @param key the column name + * @return the index of the column + */ + public int getColumnIndex(String key) { + getStatement(false); + final Integer index = mColumns.get(key); + if (index == null) { + throw new IllegalArgumentException("column '" + key + "' is invalid"); + } + return index; + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, double value) { + mPreparedStatement.bindDouble(index, value); + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, float value) { + mPreparedStatement.bindDouble(index, value); + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, long value) { + mPreparedStatement.bindLong(index, value); + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, int value) { + mPreparedStatement.bindLong(index, value); + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, boolean value) { + mPreparedStatement.bindLong(index, value ? 1 : 0); + } + /** + * Bind null to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + */ + public void bindNull(int index) { + mPreparedStatement.bindNull(index); + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, byte[] value) { + if (value == null) { + mPreparedStatement.bindNull(index); + } else { + mPreparedStatement.bindBlob(index, value); + } + } + /** + * Bind the value to an index. A prepareForInsert() or prepareForReplace() + * without a matching execute() must have already have been called. + * @param index the index of the slot to which to bind + * @param value the value to bind + */ + public void bind(int index, String value) { + if (value == null) { + mPreparedStatement.bindNull(index); + } else { + mPreparedStatement.bindString(index, value); + } + } + /** + * Performs an insert, adding a new row with the given values. + * If the table contains conflicting rows, an error is + * returned. + * + * @param values the set of values with which to populate the + * new row + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long insert(ContentValues values) { + return insertInternal(values, false); + } + /** + * Execute the previously prepared insert or replace using the bound values + * since the last call to prepareForInsert or prepareForReplace. + * + *

Note that calling bind() and then execute() is not thread-safe. The only thread-safe + * way to use this class is to call insert() or replace(). + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long execute() { + if (mPreparedStatement == null) { + throw new IllegalStateException("you must prepare this inserter before calling " + + "execute"); + } + try { + if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName); + return mPreparedStatement.executeInsert(); + } catch (SQLException e) { + Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e); + return -1; + } finally { + // you can only call this once per prepare + mPreparedStatement = null; + } + } + /** + * Prepare the InsertHelper for an insert. The pattern for this is: + *

    + *
  • prepareForInsert() + *
  • bind(index, value); + *
  • bind(index, value); + *
  • ... + *
  • bind(index, value); + *
  • execute(); + *
+ */ + public void prepareForInsert() { + mPreparedStatement = getStatement(false); + mPreparedStatement.clearBindings(); + } + /** + * Prepare the InsertHelper for a replace. The pattern for this is: + *
    + *
  • prepareForReplace() + *
  • bind(index, value); + *
  • bind(index, value); + *
  • ... + *
  • bind(index, value); + *
  • execute(); + *
+ */ + public void prepareForReplace() { + mPreparedStatement = getStatement(true); + mPreparedStatement.clearBindings(); + } + /** + * Performs an insert, adding a new row with the given values. + * If the table contains conflicting rows, they are deleted + * and replaced with the new row. + * + * @param values the set of values with which to populate the + * new row + * + * @return the row ID of the newly inserted row, or -1 if an + * error occurred + */ + public long replace(ContentValues values) { + return insertInternal(values, true); + } + /** + * Close this object and release any resources associated with + * it. The behavior of calling insert() after + * calling this method is undefined. + */ + public void close() { + if (mInsertStatement != null) { + mInsertStatement.close(); + mInsertStatement = null; + } + if (mReplaceStatement != null) { + mReplaceStatement.close(); + mReplaceStatement = null; + } + mInsertSQL = null; + mColumns = null; + } + } + /** + * Creates a db and populates it with the sql statements in sqlStatements. + * + * @param context the context to use to create the db + * @param dbName the name of the db to create + * @param dbVersion the version to set on the db + * @param sqlStatements the statements to use to populate the db. This should be a single string + * of the form returned by sqlite3's .dump command (statements separated by + * semicolons) + */ + static public void createDbFromSqlStatements( + Context context, String dbName, int dbVersion, String sqlStatements) { + 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. + String[] statements = TextUtils.split(sqlStatements, ";\n"); + for (String statement : statements) { + if (TextUtils.isEmpty(statement)) continue; + db.execSQL(statement); + } + db.setVersion(dbVersion); + db.close(); + } + /** + * Returns one of the following which represent the type of the given SQL statement. + *
    + *
  1. {@link #STATEMENT_SELECT}
  2. + *
  3. {@link #STATEMENT_UPDATE}
  4. + *
  5. {@link #STATEMENT_ATTACH}
  6. + *
  7. {@link #STATEMENT_BEGIN}
  8. + *
  9. {@link #STATEMENT_COMMIT}
  10. + *
  11. {@link #STATEMENT_ABORT}
  12. + *
  13. {@link #STATEMENT_OTHER}
  14. + *
+ * @param sql the SQL statement whose type is returned by this method + * @return one of the values listed above + */ + public static int getSqlStatementType(String sql) { + sql = sql.trim(); + if (sql.length() < 3) { + return STATEMENT_OTHER; + } + String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT); + if (prefixSql.equals("SEL")) { + return STATEMENT_SELECT; + } else if (prefixSql.equals("INS") || + prefixSql.equals("UPD") || + prefixSql.equals("REP") || + prefixSql.equals("DEL")) { + return STATEMENT_UPDATE; + } else if (prefixSql.equals("ATT")) { + return STATEMENT_ATTACH; + } else if (prefixSql.equals("COM")) { + return STATEMENT_COMMIT; + } else if (prefixSql.equals("END")) { + return STATEMENT_COMMIT; + } else if (prefixSql.equals("ROL")) { + return STATEMENT_ABORT; + } else if (prefixSql.equals("BEG")) { + return STATEMENT_BEGIN; + } else if (prefixSql.equals("PRA")) { + return STATEMENT_PRAGMA; + } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") || + prefixSql.equals("ALT")) { + return STATEMENT_DDL; + } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) { + return STATEMENT_UNPREPARED; + } + return STATEMENT_OTHER; + } + /** + * Appends one set of selection args to another. This is useful when adding a selection + * argument to a user provided set. + */ + public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) { + if (originalValues == null || originalValues.length == 0) { + return newValues; + } + String[] result = new String[originalValues.length + newValues.length ]; + System.arraycopy(originalValues, 0, result, 0, originalValues.length); + System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); + return result; + } + /** + * Returns column index of "_id" column, or -1 if not found. + * @hide + */ + public static int findRowIdColumnIndex(String[] columnNames) { + int length = columnNames.length; + for (int i = 0; i < length; i++) { + if (columnNames[i].equals("_id")) { + return i; + } + } + return -1; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/DefaultDatabaseErrorHandler.java b/AndroidCompat/src/main/java/android/database/DefaultDatabaseErrorHandler.java new file mode 100644 index 00000000..017e242b --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/DefaultDatabaseErrorHandler.java @@ -0,0 +1,101 @@ +/* + * 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 android.database; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; +import android.util.Pair; + +import java.io.File; +import java.util.List; +/** + * Default class used to define the action to take when database corruption is reported + * by sqlite. + *

+ * An application can specify an implementation of {@link DatabaseErrorHandler} on the + * following: + *

    + *
  • {@link SQLiteDatabase#openOrCreateDatabase(String, + * android.database.sqlite.SQLiteDatabase.CursorFactory, DatabaseErrorHandler)}
  • + *
  • {@link SQLiteDatabase#openDatabase(String, + * android.database.sqlite.SQLiteDatabase.CursorFactory, int, DatabaseErrorHandler)}
  • + *
+ * The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they + * occur. + *

+ * If null is specified for the DatabaseErrorHandler param in the above calls, this class is used + * as the default {@link DatabaseErrorHandler}. + */ +public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler { + private static final String TAG = "DefaultDatabaseErrorHandler"; + /** + * 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) { + Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath()); + // is the corruption detected even before database could be 'opened'? + if (!dbObj.isOpen()) { + // database files are not even openable. delete this database file. + // NOTE if the database has attached databases, then any of them could be corrupt. + // and not deleting all of them could cause corrupted database file to remain and + // make the application crash on database open operation. To avoid this problem, + // the application should provide its own {@link DatabaseErrorHandler} impl class + // to delete ALL files of the database (including the attached databases). + deleteDatabaseFile(dbObj.getPath()); + return; + } + List> attachedDbs = null; + try { + // Close the database, which will cause subsequent operations to fail. + // before that, get the attached database list first. + try { + attachedDbs = dbObj.getAttachedDbs(); + } catch (SQLiteException e) { + /* ignore */ + } + try { + dbObj.close(); + } catch (SQLiteException e) { + /* ignore */ + } + } finally { + // Delete all files of this corrupt database and/or attached databases + if (attachedDbs != null) { + for (Pair p : attachedDbs) { + deleteDatabaseFile(p.second); + } + } else { + // attachedDbs = null is possible when the database is so corrupt that even + // "PRAGMA database_list;" also fails. delete the main database file + 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 { + SQLiteDatabase.deleteDatabase(new File(fileName)); + } catch (Exception e) { + /* print warning and ignore exception */ + Log.w(TAG, "delete failed: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/Observable.java b/AndroidCompat/src/main/java/android/database/Observable.java new file mode 100644 index 00000000..bee8a7ae --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/Observable.java @@ -0,0 +1,83 @@ +/* + * 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 android.database; + +import java.util.ArrayList; + +/** + * Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}. + * + * This abstract class is intended to be subclassed and specialized to maintain + * a registry of observers of specific types and dispatch notifications to them. + * + * @param T The observer type. + */ +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 observers. + */ + public void unregisterAll() { + synchronized(mObservers) { + mObservers.clear(); + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/SQLException.java b/AndroidCompat/src/main/java/android/database/SQLException.java new file mode 100644 index 00000000..121bfbe7 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/SQLException.java @@ -0,0 +1,29 @@ +/* + * 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 android.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); + } + public SQLException(String error, Throwable cause) { + super(error, cause); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/database/sqlite/DatabaseObjectNotClosedException.java b/AndroidCompat/src/main/java/android/database/sqlite/DatabaseObjectNotClosedException.java new file mode 100644 index 00000000..f28c70fe --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/DatabaseObjectNotClosedException.java @@ -0,0 +1,31 @@ +/* + * 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 android.database.sqlite; + +/** + * An exception that indicates that garbage-collector is finalizing a database object + * that is not explicitly closed + * @hide + */ +public class DatabaseObjectNotClosedException extends RuntimeException { + private static final String s = "Application did not close the cursor or database object " + + "that was opened here"; + + public DatabaseObjectNotClosedException() { + super(s); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAbortException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAbortException.java new file mode 100644 index 00000000..64dc4b73 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAbortException.java @@ -0,0 +1,30 @@ +/* + * 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 android.database.sqlite; + +/** + * 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/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAccessPermException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAccessPermException.java new file mode 100644 index 00000000..238da4bd --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteAccessPermException.java @@ -0,0 +1,29 @@ +/* + * 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 android.database.sqlite; + +/** + * This exception class is used when sqlite can't access the database file + * due to lack of permissions on the file. + */ +public class SQLiteAccessPermException extends SQLiteException { + public SQLiteAccessPermException() {} + + public SQLiteAccessPermException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java new file mode 100644 index 00000000..41f2f9c2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java @@ -0,0 +1,28 @@ +/* + * 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 android.database.sqlite; + +/** + * Thrown if the the bind or column parameter index is out of range + */ +public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException { + public SQLiteBindOrColumnIndexOutOfRangeException() {} + + public SQLiteBindOrColumnIndexOutOfRangeException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBlobTooBigException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBlobTooBigException.java new file mode 100644 index 00000000..a82676b8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteBlobTooBigException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteBlobTooBigException extends SQLiteException { + public SQLiteBlobTooBigException() {} + + public SQLiteBlobTooBigException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java new file mode 100644 index 00000000..6f01796c --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteCantOpenDatabaseException extends SQLiteException { + public SQLiteCantOpenDatabaseException() {} + + public SQLiteCantOpenDatabaseException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteClosable.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteClosable.java new file mode 100644 index 00000000..adfbc6e1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteClosable.java @@ -0,0 +1,108 @@ +/* + * 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 android.database.sqlite; + +import java.io.Closeable; + +/** + * An object created from a SQLiteDatabase that can be closed. + * + * This class implements a primitive reference counting scheme for database objects. + */ +public abstract class SQLiteClosable implements Closeable { + private int mReferenceCount = 1; + + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReference()} or {@link #close()}. + */ + protected abstract void onAllReferencesReleased(); + + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReferenceFromContainer()}. + * + * @deprecated Do not use. + */ + @Deprecated + protected void onAllReferencesReleasedFromContainer() { + onAllReferencesReleased(); + } + + /** + * Acquires a reference to the object. + * + * @throws IllegalStateException if the last reference to the object has already + * been released. + */ + public void acquireReference() { + synchronized(this) { + if (mReferenceCount <= 0) { + throw new IllegalStateException( + "attempt to re-open an already-closed object: " + this); + } + mReferenceCount++; + } + } + + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * @see #onAllReferencesReleased() + */ + public void releaseReference() { + boolean refCountIsZero = false; + synchronized(this) { + refCountIsZero = --mReferenceCount == 0; + } + if (refCountIsZero) { + onAllReferencesReleased(); + } + } + + /** + * Releases a reference to the object that was owned by the container of the object, + * closing the object if the last reference was released. + * + * @see #onAllReferencesReleasedFromContainer() + * @deprecated Do not use. + */ + @Deprecated + public void releaseReferenceFromContainer() { + boolean refCountIsZero = false; + synchronized(this) { + refCountIsZero = --mReferenceCount == 0; + } + if (refCountIsZero) { + onAllReferencesReleasedFromContainer(); + } + } + + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * Calling this method is equivalent to calling {@link #releaseReference}. + * + * @see #releaseReference() + * @see #onAllReferencesReleased() + */ + public void close() { + releaseReference(); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteConstraintException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteConstraintException.java new file mode 100644 index 00000000..e3119eba --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteConstraintException.java @@ -0,0 +1,28 @@ +/* + * 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 android.database.sqlite; + +/** + * 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/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursor.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursor.java new file mode 100644 index 00000000..5d347457 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursor.java @@ -0,0 +1,271 @@ +/* + * 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 android.database.sqlite; + +import android.database.AbstractWindowedCursor; +import android.database.CursorWindow; +import android.util.Log; + +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +/** + * A Cursor implementation that exposes results from a query on a + * {@link SQLiteDatabase}. + * + * 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 { + static final String TAG = "SQLiteCursor"; + static final int NO_COUNT = -1; + + /** The name of the table to edit */ + private final String mEditTable; + + /** The names of the columns in the rows */ + private final String[] mColumns; + + /** The query object for the cursor */ + private final SQLiteQuery mQuery; + + /** The compiled query this cursor came from */ + private final SQLiteCursorDriver mDriver; + + /** The number of rows in the cursor */ + private int mCount = NO_COUNT; + + /** The number of rows that can fit in the cursor window, 0 if unknown */ + private int mCursorWindowCapacity; + + /** 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 final Throwable mStackTrace; + + /** + * 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 + * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, + * phone) would be in the projection argument and everything from + * {@code FROM} onward would be in the params argument. + * + * @param db a reference to a Database object that is already constructed + * and opened. This param is not used any longer + * @param editTable the name of the table used for this query + * @param query the rest of the query terms + * cursor is finalized + * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead + */ + @Deprecated + public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, + String editTable, SQLiteQuery query) { + this(driver, editTable, query); + } + + /** + * 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 + * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, + * phone) would be in the projection argument and everything from + * {@code FROM} onward would be in the params argument. + * + * @param editTable the name of the table used for this query + * @param query the {@link SQLiteQuery} object associated with this cursor object. + */ + public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { + if (query == null) { + throw new IllegalArgumentException("query object cannot be null"); + } + mStackTrace = null; + mDriver = driver; + mEditTable = editTable; + mColumnNameMap = null; + mQuery = query; + + try { + query.B_setBindArgs(); + query.getPreparedStatement().execute(); + + ResultSetMetaData metaData = query.getResultSet().getMetaData(); + mColumns = new String[metaData.getColumnCount()]; + for(int i = 1; i <= metaData.getColumnCount(); i++) { + mColumns[i - 1] = metaData.getColumnLabel(i); + } + } catch (SQLException e) { + throw new SQLiteException("Failed to get column names!", e); + } + } + + /** + * Get the database that this cursor is associated with. + * @return the SQLiteDatabase that this cursor is associated with. + */ + public SQLiteDatabase getDatabase() { + return mQuery.getDatabase(); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + // Make sure the row at newPosition is present in the window + if (mWindow == null || newPosition < mWindow.getStartPosition() || + newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { + fillWindow(newPosition); + } + + return true; + } + + @Override + public int getCount() { + return mQuery.getResultSet().getResultSetLength(); + } + + private void fillWindow(int requiredPos) { + clearOrCreateWindow(mQuery.getResultSet()); + + try { + } catch (RuntimeException ex) { + // Close the cursor window if the query failed and therefore will + // not produce any results. This helps to avoid accidentally leaking + // the cursor window if the client does not correctly handle exceptions + // and fails to close the cursor. + closeWindow(); + throw ex; + } + } + + @Override + public int getColumnIndex(String columnName) { + // Create mColumnNameMap on demand + if (mColumnNameMap == null) { + String[] columns = mColumns; + int columnCount = columns.length; + HashMap map = new HashMap(columnCount, 1); + for (int i = 0; i < columnCount; i++) { + map.put(columns[i], i); + } + mColumnNameMap = map; + } + + // Hack according to bug 903852 + 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); + } + + Integer i = mColumnNameMap.get(columnName); + if (i != null) { + return i.intValue(); + } else { + return -1; + } + } + + @Override + public String[] getColumnNames() { + return mColumns; + } + + @Override + public void deactivate() { + super.deactivate(); + mDriver.cursorDeactivated(); + } + + @Override + public void close() { + super.close(); + synchronized (this) { + mQuery.close(); + mDriver.cursorClosed(); + } + } + + @Override + public boolean requery() { + if (isClosed()) { + return false; + } + + synchronized (this) { + if (!mQuery.getDatabase().isOpen()) { + return false; + } + + if (mWindow != null) { + mWindow.clear(); + } + mPos = -1; + mCount = NO_COUNT; + + mDriver.cursorRequeried(this); + } + + try { + return super.requery(); + } catch (IllegalStateException e) { + // for backwards compatibility, just return false + Log.w(TAG, "requery() failed " + e.getMessage(), e); + return false; + } + } + + @Override + public void setWindow(CursorWindow window) { + super.setWindow(window); + mCount = NO_COUNT; + } + + /** + * Changes the selection arguments. The new values take effect after a call to requery(). + */ + public void setSelectionArguments(String[] selectionArgs) { + mDriver.setBindArguments(selectionArgs); + } + + /** + * Release the native resources, if they haven't been released yet. + */ + @Override + protected void finalize() { + try { + // if the cursor hasn't been closed yet, close it first + if (mWindow != null) { + //TODO Finish +// if (mStackTrace != null) { +// String sql = mQuery.getSql(); +// int len = sql.length(); +// StrictMode.onSqliteObjectLeaked( +// "Finalizing a Cursor that has not been deactivated or closed. " + +// "database = " + mQuery.getDatabase().getLabel() + +// ", table = " + mEditTable + +// ", query = " + sql.substring(0, (len > 1000) ? 1000 : len), +// mStackTrace); +// } + close(); + } + } finally { + super.finalize(); + } + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursorDriver.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursorDriver.java new file mode 100644 index 00000000..ad2cdd22 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteCursorDriver.java @@ -0,0 +1,56 @@ +/* + * 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 android.database.sqlite; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase.CursorFactory; + +/** + * A driver for SQLiteCursors that is used to create them and gets notified + * by the cursors it creates on significant events in their lifetimes. + */ +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 + */ + Cursor query(CursorFactory factory, String[] bindArgs); + + /** + * Called by a SQLiteCursor when it is released. + */ + void cursorDeactivated(); + + /** + * Called by a SQLiteCursor when it is requeried. + */ + void cursorRequeried(Cursor cursor); + + /** + * Called by a SQLiteCursor when it it closed to destroy this object as well. + */ + void cursorClosed(); + + /** + * Set new bind arguments. These will take effect in cursorRequeried(). + * @param bindArgs the new arguments + */ + public void setBindArguments(String[] bindArgs); +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabase.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabase.java new file mode 100644 index 00000000..7970bf74 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabase.java @@ -0,0 +1,2208 @@ +/* + * 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 android.database.sqlite; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentValues; +import android.database.*; +import android.database.sqlite.SQLiteDebug.DbStats; +import android.os.CancellationSignal; +import android.os.Looper; +import android.os.OperationCanceledException; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import android.util.Pair; +import dalvik.system.CloseGuard; + +import java.io.File; +import java.io.FileFilter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 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, + * and UNICODE, which is the Unicode Collation Algorithm and not tailored + * to the current locale. + *

+ */ +public final class SQLiteDatabase extends SQLiteClosable { + private static final String TAG = "SQLiteDatabase"; + + private static final int EVENT_DB_CORRUPT = 75004; + + // 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(); + + // The optional factory to use when creating new Cursors. May be null. + // INVARIANT: Immutable. + private final CursorFactory mCursorFactory; + + // Error handler to be used when SQLite returns corruption errors. + // INVARIANT: Immutable. + private final DatabaseErrorHandler mErrorHandler; + + // Shared database state lock. + // This lock guards all of the shared state of the database, such as its + // configuration, whether it is open or closed, and so on. This lock should + // be held for as little time as possible. + // + // The lock MUST NOT be held while attempting to acquire database connections or + // while executing SQL statements on behalf of the client as it can lead to deadlock. + // + // It is ok to hold the lock while reconfiguring the connection pool or dumping + // statistics because those operations are non-reentrant and do not try to acquire + // connections that might be held by other threads. + // + // Basic rule: grab the lock, access or modify global state, release the lock, then + // do the required SQL work. + private final Object mLock = new Object(); + + // Warns if the database is finalized without being closed properly. + // INVARIANT: Guarded by mLock. + private final CloseGuard mCloseGuardLocked = CloseGuard.get(); + + // The database configuration. + // INVARIANT: Guarded by mLock. + private final SQLiteDatabaseConfiguration mConfigurationLocked; + + // True if the database has attached databases. + // INVARIANT: Guarded by mLock. + private boolean mHasAttachedDbsLocked; + + /** + * 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; + + /** + * Open flag: 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 + + /** + * Open flag: 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 + + /** + * Open flag: 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 + + /** + * Open flag: 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 + + /** + * Open flag: Flag for {@link #openDatabase} to open the database file with + * write-ahead logging enabled by default. Using this flag is more efficient + * than calling {@link #enableWriteAheadLogging}. + * + * 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. + * + * @see #enableWriteAheadLogging + */ + public static final int ENABLE_WRITE_AHEAD_LOGGING = 0x20000000; + + /** + * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}. + * + * Each prepared-statement is between 1K - 6K, depending on the complexity of the + * SQL statement & schema. A large SQL cache may use a significant amount of memory. + */ + public static final int MAX_SQL_CACHE_SIZE = 100; + + private SQLiteDatabase(String path, int openFlags, CursorFactory cursorFactory, + DatabaseErrorHandler errorHandler) { + mCursorFactory = cursorFactory; + mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler(); + mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags); + + B_initDriver(); + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(true); + } finally { + super.finalize(); + } + } + + @Override + protected void onAllReferencesReleased() { + dispose(false); + } + + private void dispose(boolean finalized) { + synchronized (mLock) { + if (mCloseGuardLocked != null) { + if (finalized) { + mCloseGuardLocked.warnIfOpen(); + } + mCloseGuardLocked.close(); + } + } + + if (!finalized) { + synchronized (sActiveDatabases) { + sActiveDatabases.remove(this); + } + } + + //Actually close DB connection + try { + if(!connection.isClosed()) + connection.close(); + } catch (java.sql.SQLException ignored) {} + } + + /** + * 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 + */ + public static int releaseMemory() { + return SQLiteGlobal.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 + * + * @deprecated This method now does nothing. Do not use. + */ + @Deprecated + public void setLockingEnabled(boolean lockingEnabled) { + } + + /** + * Gets a label to use when describing the database in log messages. + * @return The label. + */ + String getLabel() { + synchronized (mLock) { + return mConfigurationLocked.label; + } + } + + /** + * Sends a corruption message to the database error handler. + */ + void onCorruption() { + EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel()); + mErrorHandler.onCorruption(this); + } + + private static boolean isMainThread() { + // FIXME: There should be a better way to do this. + // Would also be nice to have something that would work across Binder calls. + Looper looper = Looper.myLooper(); + return looper != null && looper == Looper.getMainLooper(); + } + + /** + * Begins a transaction in EXCLUSIVE 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.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + public void beginTransaction() { + beginTransaction(null /* transactionStatusCallback */, true); + } + + /** + * Begins a transaction in IMMEDIATE 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.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * 
+ */ + public void beginTransactionNonExclusive() { + beginTransaction(null /* transactionStatusCallback */, false); + } + + /** + * Begins a transaction in EXCLUSIVE 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}. + */ + public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { + beginTransaction(transactionListener, true); + } + + /** + * Begins a transaction in IMMEDIATE 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.beginTransactionWithListenerNonExclusive(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 beginTransactionWithListenerNonExclusive( + SQLiteTransactionListener transactionListener) { + beginTransaction(transactionListener, false); + } + + private void beginTransaction(SQLiteTransactionListener transactionListener, + boolean exclusive) { + acquireReference(); + try { + B_beginTransaction(transactionListener, exclusive); + } finally { + releaseReference(); + } + } + + /** + * End a transaction. See beginTransaction for notes about how to use this and when transactions + * are committed and rolled back. + */ + public void endTransaction() { + acquireReference(); + try { + B_endTransaction(); + } finally { + releaseReference(); + } + } + + /** + * 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() { + acquireReference(); + try { + B_setTransactionSuccessful(); + } finally { + releaseReference(); + } + } + + /** + * Returns true if the current thread has a transaction pending. + * + * @return True if the current thread is in a transaction. + */ + public boolean inTransaction() { + acquireReference(); + try { + return B_inTransaction(); + } finally { + releaseReference(); + } + } + + /** + * Returns true if the current thread is holding an active connection to the database. + *

+ * The name of this method comes from a time when having an active connection + * to the database meant that the thread was holding an actual lock on the + * database. Nowadays, there is no longer a true "database lock" although threads + * may block if they cannot acquire a database connection to perform a + * particular operation. + *

+ * + * @return True if the current thread is holding an active connection to the database. + */ + public boolean isDbLockedByCurrentThread() { + acquireReference(); + try { + return true; + } finally { + releaseReference(); + } + } + + /** + * Always returns false. + *

+ * There is no longer the concept of a database lock, so this method always returns false. + *

+ * + * @return False. + * @deprecated Always returns false. Do not use this method. + */ + @Deprecated + public boolean isDbLockedByOtherThreads() { + return false; + } + + /** + * 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 throwIfUnsafe, long sleepAfterYieldDelay) { + acquireReference(); + try { + return B_yieldIfContendedHelper(throwIfUnsafe, sleepAfterYieldDelay); + } finally { + releaseReference(); + } + } + + /** + * Deprecated. + * @deprecated This method no longer serves any useful purpose and has been deprecated. + */ + @Deprecated + public Map getSyncedTables() { + return new HashMap(0); + } + + /** + * 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, CursorFactory factory, int flags) { + return openDatabase(path, 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 the system's current locale. + * Call {@link #setLocale} if you would like something else.

+ * + *

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

+ * + * @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 + * @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption + * when sqlite reports database corruption + * @return the newly opened database + * @throws SQLiteException if the database cannot be opened + */ + public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags, + DatabaseErrorHandler errorHandler) { + SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler); + db.open(); + return db; + } + + /** + * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) { + return openOrCreateDatabase(file.getPath(), factory); + } + + /** + * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) { + return openDatabase(path, factory, CREATE_IF_NECESSARY, null); + } + + /** + * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler). + */ + public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler); + } + + /** + * Deletes a database including its journal file and other auxiliary files + * that may have been created by the database engine. + * + * @param file The database file path. + * @return True if the database was successfully deleted. + */ + public static boolean deleteDatabase(File file) { + if (file == null) { + throw new IllegalArgumentException("file must not be null"); + } + + boolean deleted = false; + deleted |= file.delete(); + deleted |= new File(file.getPath() + "-journal").delete(); + deleted |= new File(file.getPath() + "-shm").delete(); + deleted |= new File(file.getPath() + "-wal").delete(); + + File dir = file.getParentFile(); + if (dir != null) { + final String prefix = file.getName() + "-mj"; + File[] files = dir.listFiles(new FileFilter() { + @Override + public boolean accept(File candidate) { + return candidate.getName().startsWith(prefix); + } + }); + if (files != null) { + for (File masterJournal : files) { + deleted |= masterJournal.delete(); + } + } + } + return deleted; + } + + /** + * Reopens the database in read-write mode. + * If the database is already read-write, does nothing. + * + * @throws SQLiteException if the database could not be reopened as requested, in which + * case it remains open in read only mode. + * @throws IllegalStateException if the database is not open. + * + * @see #isReadOnly() + * @hide + */ + public void reopenReadWrite() { + synchronized (mLock) { + throwIfNotOpenLocked(); + + if (!isReadOnlyLocked()) { + return; // nothing to do + } + + // Reopen the database in read-write mode. + final int oldOpenFlags = mConfigurationLocked.openFlags; + mConfigurationLocked.openFlags = (mConfigurationLocked.openFlags & ~OPEN_READ_MASK) + | OPEN_READWRITE; + } + } + + private void open() { + try { + try { + openInner(); + } catch (SQLiteDatabaseCorruptException ex) { + onCorruption(); + openInner(); + } + } catch (SQLiteException ex) { + Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex); + close(); + throw ex; + } + } + + private void openInner() { + synchronized (mLock) { + mCloseGuardLocked.open("close"); + } + + B_openInner(); + + synchronized (sActiveDatabases) { + sActiveDatabases.put(this, 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 + * @return a SQLiteDatabase object, or null if the database can't be created + */ + public static SQLiteDatabase create(CursorFactory factory) { + // This is a magic string with special meaning for SQLite. + return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, + factory, CREATE_IF_NECESSARY); + } + + /** + * Gets the database version. + * + * @return the database version + */ + public int getVersion() { + return ((Long) DatabaseUtils.longForQuery(this, "PRAGMA user_version;", null)).intValue(); + } + + /** + * 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() { + long pageCount = DatabaseUtils.longForQuery(this, "PRAGMA max_page_count;", null); + return pageCount * getPageSize(); + } + + /** + * 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) { + long pageSize = getPageSize(); + long numPages = numBytes / pageSize; + // If numBytes isn't a multiple of pageSize, bump up a page + if ((numBytes % pageSize) != 0) { + numPages++; + } + long newPageCount = DatabaseUtils.longForQuery(this, "PRAGMA max_page_count = " + numPages, + null); + return newPageCount * pageSize; + } + + /** + * Returns the current database page size, in bytes. + * + * @return the database page size, in bytes + */ + public long getPageSize() { + return DatabaseUtils.longForQuery(this, "PRAGMA page_size;", null); + } + + /** + * 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 + * @deprecated This method no longer serves any useful purpose and has been deprecated. + */ + @Deprecated + public void markTableSyncable(String table, String 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 + * @deprecated This method no longer serves any useful purpose and has been deprecated. + */ + @Deprecated + public void markTableSyncable(String table, String foreignKey, String updateTable) { + } + + /** + * 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. + *

+ * No two threads should be using the same {@link SQLiteStatement} at the same time. + * + * @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 { + acquireReference(); + try { + return new SQLiteStatement(this, sql, null); + } finally { + releaseReference(); + } + } + + /** + * 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, null); + } + + /** + * 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. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @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, CancellationSignal cancellationSignal) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit, cancellationSignal); + } + + /** + * 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) { + return queryWithFactory(cursorFactory, distinct, table, columns, selection, + selectionArgs, groupBy, having, orderBy, limit, null); + } + + /** + * 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. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @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, CancellationSignal cancellationSignal) { + acquireReference(); + try { + String sql = SQLiteQueryBuilder.buildQueryString( + distinct, table, columns, selection, groupBy, having, orderBy, limit); + + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, + findEditTable(table), cancellationSignal); + } finally { + releaseReference(); + } + } + + /** + * 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, null); + } + + /** + * 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. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @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, + CancellationSignal cancellationSignal) { + return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal); + } + + /** + * 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) { + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, 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 + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @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, CancellationSignal cancellationSignal) { + acquireReference(); + try { + SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, + cancellationSignal); + return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, + selectionArgs); + } finally { + releaseReference(); + } + } + + /** + * Convenience method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack optional; may be null. + * SQL doesn't allow inserting a completely empty row without + * naming at least one column name. If your provided values is + * empty, no column names are known and an empty row can't be inserted. + * If not set to null, the nullColumnHack parameter + * provides the name of nullable column name to explicitly insert a NULL into + * in the case where your values is empty. + * @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 optional; may be null. + * SQL doesn't allow inserting a completely empty row without + * naming at least one column name. If your provided values is + * empty, no column names are known and an empty row can't be inserted. + * If not set to null, the nullColumnHack parameter + * provides the name of nullable column name to explicitly insert a NULL into + * in the case where your values is empty. + * @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. + * Inserts a new row if a row does not already exist. + * + * @param table the table in which to replace the row + * @param nullColumnHack optional; may be null. + * SQL doesn't allow inserting a completely empty row without + * naming at least one column name. If your provided initialValues is + * empty, no column names are known and an empty row can't be inserted. + * If not set to null, the nullColumnHack parameter + * provides the name of nullable column name to explicitly insert a NULL into + * in the case where your initialValues is empty. + * @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. + * @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. + * Inserts a new row if a row does not already exist. + * + * @param table the table in which to replace the row + * @param nullColumnHack optional; may be null. + * SQL doesn't allow inserting a completely empty row without + * naming at least one column name. If your provided initialValues is + * empty, no column names are known and an empty row can't be inserted. + * If not set to null, the nullColumnHack parameter + * provides the name of nullable column name to explicitly insert a NULL into + * in the case where your initialValues is empty. + * @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. + * @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 optional; may be null. + * SQL doesn't allow inserting a completely empty row without + * naming at least one column name. If your provided initialValues is + * empty, no column names are known and an empty row can't be inserted. + * If not set to null, the nullColumnHack parameter + * provides the name of nullable column name to explicitly insert a NULL into + * in the case where your initialValues is empty. + * @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 -1 if either the + * input parameter conflictAlgorithm = {@link #CONFLICT_IGNORE} + * or an error occurred. + */ + public long insertWithOnConflict(String table, String nullColumnHack, + ContentValues initialValues, int conflictAlgorithm) { + acquireReference(); + try { + StringBuilder sql = new StringBuilder(); + sql.append("INSERT"); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(" INTO "); + sql.append(table); + sql.append('('); + + Object[] bindArgs = null; + int size = (initialValues != null && initialValues.size() > 0) + ? initialValues.size() : 0; + if (size > 0) { + bindArgs = new Object[size]; + int i = 0; + for (String colName : initialValues.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = initialValues.get(colName); + } + sql.append(')'); + sql.append(" VALUES ("); + for (i = 0; i < size; i++) { + sql.append((i > 0) ? ",?" : "?"); + } + } else { + sql.append(nullColumnHack + ") VALUES (NULL"); + } + sql.append(')'); + + SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + try { + return statement.executeInsert(); + } finally { + statement.close(); + } + } finally { + releaseReference(); + } + } + + /** + * 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. + * @param whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @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) { + acquireReference(); + try { + SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } + } finally { + releaseReference(); + } + } + + /** + * 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 whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @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 whereArgs You may include ?s in the where clause, which + * will be replaced by the values from whereArgs. The values + * will be bound as Strings. + * @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"); + } + + acquireReference(); + try { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(table); + sql.append(" SET "); + + // move all bind args to one array + int setValuesSize = values.size(); + int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); + Object[] bindArgs = new Object[bindArgsSize]; + int i = 0; + for (String colName : values.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = values.get(colName); + sql.append("=?"); + } + if (whereArgs != null) { + for (i = setValuesSize; i < bindArgsSize; i++) { + bindArgs[i] = whereArgs[i - setValuesSize]; + } + } + if (!TextUtils.isEmpty(whereClause)) { + sql.append(" WHERE "); + sql.append(whereClause); + } + + SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } + } finally { + releaseReference(); + } + } + + /** + * Execute a single SQL statement that is NOT a SELECT + * or any other SQL statement that returns data. + *

+ * It has no means to return any data (such as the number of affected rows). + * Instead, you're encouraged to use {@link #insert(String, String, ContentValues)}, + * {@link #update(String, ContentValues, String, String[])}, et al, when possible. + *

+ *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are + * not supported. + * @throws SQLException if the SQL string is invalid + */ + public void execSQL(String sql) throws SQLException { + executeSql(sql, null); + } + + /** + * Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE. + *

+ * For INSERT statements, use any of the following instead. + *

    + *
  • {@link #insert(String, String, ContentValues)}
  • + *
  • {@link #insertOrThrow(String, String, ContentValues)}
  • + *
  • {@link #insertWithOnConflict(String, String, ContentValues, int)}
  • + *
+ *

+ * For UPDATE statements, use any of the following instead. + *

    + *
  • {@link #update(String, ContentValues, String, String[])}
  • + *
  • {@link #updateWithOnConflict(String, ContentValues, String, String[], int)}
  • + *
+ *

+ * For DELETE statements, use any of the following instead. + *

    + *
  • {@link #delete(String, String, String[])}
  • + *
+ *

+ * For example, the following are good candidates for using this method: + *

    + *
  • ALTER TABLE
  • + *
  • CREATE or DROP table / trigger / view / index / virtual table
  • + *
  • REINDEX
  • + *
  • RELEASE
  • + *
  • SAVEPOINT
  • + *
  • PRAGMA that returns no data
  • + *
+ *

+ *

+ * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'" statement if your app is using + * {@link #enableWriteAheadLogging()} + *

+ * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are + * not supported. + * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. + * @throws SQLException if the SQL string is invalid + */ + public void execSQL(String sql, Object[] bindArgs) throws SQLException { + if (bindArgs == null) { + throw new IllegalArgumentException("Empty bindArgs"); + } + executeSql(sql, bindArgs); + } + + private int executeSql(String sql, Object[] bindArgs) throws SQLException { + acquireReference(); + try { + if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { + boolean disableWal = false; + synchronized (mLock) { + if (!mHasAttachedDbsLocked) { + mHasAttachedDbsLocked = true; + disableWal = true; + } + } + if (disableWal) { + disableWriteAheadLogging(); + } + } + + SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } + } finally { + releaseReference(); + } + } + + /** + * Verifies that a SQL SELECT statement is valid by compiling it. + * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. + * + * @param sql SQL to be validated + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @throws SQLiteException if {@code sql} is invalid + */ + public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal) { + B_validateSql(sql, cancellationSignal); + } + + /** + * Returns true if the database is opened as read only. + * + * @return True if database is opened as read only. + */ + public boolean isReadOnly() { + synchronized (mLock) { + return isReadOnlyLocked(); + } + } + + private boolean isReadOnlyLocked() { + return (mConfigurationLocked.openFlags & OPEN_READ_MASK) == OPEN_READONLY; + } + + /** + * Returns true if the database is in-memory db. + * + * @return True if the database is in-memory. + * @hide + */ + public boolean isInMemoryDatabase() { + synchronized (mLock) { + return mConfigurationLocked.isInMemoryDb(); + } + } + + /** + * Returns true if the database is currently open. + * + * @return True if the database is currently open (has not been closed). + */ + public boolean isOpen() { + synchronized (mLock) { + try { + return !connection.isClosed(); + } catch (java.sql.SQLException e) { + return false; + } + } + } + + /** + * Returns true if the new version code is greater than the current database version. + * + * @param newVersion The new version code. + * @return True if the new version code is greater than the current database version. + */ + public boolean needUpgrade(int newVersion) { + return newVersion > getVersion(); + } + + /** + * Gets the path to the database file. + * + * @return The path to the database file. + */ + public final String getPath() { + synchronized (mLock) { + return mConfigurationLocked.path; + } + } + + /** + * Sets the locale for this database. Does nothing if this database has + * the {@link #NO_LOCALIZED_COLLATORS} flag set or was opened read only. + * + * @param locale The new locale. + * + * @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) { + if (locale == null) { + throw new IllegalArgumentException("locale must not be null."); + } + + synchronized (mLock) { + throwIfNotOpenLocked(); + + final Locale oldLocale = mConfigurationLocked.locale; + mConfigurationLocked.locale = locale; + } + } + + /** + * Sets the maximum size of the prepared-statement cache for this database. + * (size of the cache = number of compiled-sql-statements stored in the cache). + *

+ * Maximum cache size can ONLY be increased from its current size (default = 10). + * If this method is called with smaller size than the current maximum value, + * then IllegalStateException is thrown. + *

+ * This method is thread-safe. + * + * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE}) + * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE}. + */ + public void setMaxSqlCacheSize(int cacheSize) { + if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { + throw new IllegalStateException( + "expected value between 0 and " + MAX_SQL_CACHE_SIZE); + } + + synchronized (mLock) { + throwIfNotOpenLocked(); + + final int oldMaxSqlCacheSize = mConfigurationLocked.maxSqlCacheSize; + mConfigurationLocked.maxSqlCacheSize = cacheSize; + } + } + + /** + * Sets whether foreign key constraints are enabled for the database. + *

+ * By default, foreign key constraints are not enforced by the database. + * This method allows an application to enable foreign key constraints. + * It must be called each time the database is opened to ensure that foreign + * key constraints are enabled for the session. + *

+ * A good time to call this method is right after calling {@link #openOrCreateDatabase} + * or in the {@link SQLiteOpenHelper#onConfigure} callback. + *

+ * When foreign key constraints are disabled, the database does not check whether + * changes to the database will violate foreign key constraints. Likewise, when + * foreign key constraints are disabled, the database will not execute cascade + * delete or update triggers. As a result, it is possible for the database + * state to become inconsistent. To perform a database integrity check, + * call {@link #isDatabaseIntegrityOk}. + *

+ * This method must not be called while a transaction is in progress. + *

+ * See also SQLite Foreign Key Constraints + * for more details about foreign key constraint support. + *

+ * + * @param enable True to enable foreign key constraints, false to disable them. + * + * @throws IllegalStateException if the are transactions is in progress + * when this method is called. + */ + public void setForeignKeyConstraintsEnabled(boolean enable) { + synchronized (mLock) { + throwIfNotOpenLocked(); + + if (mConfigurationLocked.foreignKeyConstraintsEnabled == enable) { + return; + } + + mConfigurationLocked.foreignKeyConstraintsEnabled = enable; + + B_reconfigureConnection(); + } + } + + /** + * This method enables parallel execution of queries from multiple threads on the + * same database. It does this by opening multiple connections to the database + * and using a different database connection for each query. The database + * journal mode is also changed to enable writes to proceed concurrently with reads. + *

+ * When write-ahead logging is not enabled (the default), it is not possible for + * reads and writes to occur on the database at the same time. Before modifying the + * database, the writer implicitly acquires an exclusive lock on the database which + * prevents readers from accessing the database until the write is completed. + *

+ * In contrast, when write-ahead logging is enabled (by calling this method), write + * operations occur in a separate log file which allows reads to proceed concurrently. + * While a write is in progress, readers on other threads will perceive the state + * of the database as it was before the write began. When the write completes, readers + * on other threads will then perceive the new state of the database. + *

+ * It is a good idea to enable write-ahead logging whenever a database will be + * concurrently accessed and modified by multiple threads at the same time. + * However, write-ahead logging uses significantly more memory than ordinary + * journaling because there are multiple connections to the same database. + * So if a database will only be used by a single thread, or if optimizing + * concurrency is not very important, then write-ahead logging should be disabled. + *

+ * After calling this method, execution of queries in parallel is enabled as long as + * the database remains open. To disable execution of queries in parallel, either + * call {@link #disableWriteAheadLogging} or close the database and reopen it. + *

+ * The maximum number of connections used to execute queries in parallel is + * dependent upon the device memory and possibly other properties. + *

+ * If a query is part of a transaction, then it is executed on the same database handle the + * transaction was begun. + *

+ * Writers should use {@link #beginTransactionNonExclusive()} or + * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)} + * to start a transaction. Non-exclusive mode allows database file to be in readable + * by other threads executing queries. + *

+ * If the database has any attached databases, then execution of queries in parallel is NOT + * possible. Likewise, write-ahead logging is not supported for read-only databases + * or memory databases. In such cases, {@link #enableWriteAheadLogging} returns false. + *

+ * The best way to enable write-ahead logging is to pass the + * {@link #ENABLE_WRITE_AHEAD_LOGGING} flag to {@link #openDatabase}. This is + * more efficient than calling {@link #enableWriteAheadLogging}. + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging} + * after opening the database. + *

+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * 
+ *

+ * See also SQLite Write-Ahead Logging for + * more details about how write-ahead logging works. + *

+ * + * @return True if write-ahead logging is enabled. + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when there are no + * transactions in progress. + * + * @see #ENABLE_WRITE_AHEAD_LOGGING + * @see #disableWriteAheadLogging + */ + public boolean enableWriteAheadLogging() { + synchronized (mLock) { + throwIfNotOpenLocked(); + + if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) { + return true; + } + + if (isReadOnlyLocked()) { + // WAL doesn't make sense for readonly-databases. + // TODO: True, but connection pooling does still make sense... + return false; + } + + if (mConfigurationLocked.isInMemoryDb()) { + Log.i(TAG, "can't enable WAL for memory databases."); + return false; + } + + // make sure this database has NO attached databases because sqlite's write-ahead-logging + // doesn't work for databases with attached databases + if (mHasAttachedDbsLocked) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "this database: " + mConfigurationLocked.label + + " has attached databases. can't enable WAL."); + } + return false; + } + + mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; + } + return true; + } + + /** + * This method disables the features enabled by {@link #enableWriteAheadLogging()}. + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when there are no + * transactions in progress. + * + * @see #enableWriteAheadLogging + */ + public void disableWriteAheadLogging() { + synchronized (mLock) { + throwIfNotOpenLocked(); + + if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) { + return; + } + + mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; + } + } + + /** + * Returns true if write-ahead logging has been enabled for this database. + * + * @return True if write-ahead logging has been enabled for this database. + * + * @see #enableWriteAheadLogging + * @see #ENABLE_WRITE_AHEAD_LOGGING + */ + public boolean isWriteAheadLoggingEnabled() { + synchronized (mLock) { + throwIfNotOpenLocked(); + + return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0; + } + } + + /** + * Collect statistics about all open databases in the current process. + * Used by bug report. + */ + static ArrayList getDbStats() { + ArrayList dbStatsList = new ArrayList(); + for (SQLiteDatabase db : getActiveDatabases()) { + db.collectDbStats(dbStatsList); + } + return dbStatsList; + } + + private void collectDbStats(ArrayList dbStatsList) { + synchronized (mLock) { + } + } + + private static ArrayList getActiveDatabases() { + ArrayList databases = new ArrayList(); + synchronized (sActiveDatabases) { + databases.addAll(sActiveDatabases.keySet()); + } + return databases; + } + + /** + * Returns list of full pathnames of all attached databases including the main database + * by executing 'pragma database_list' on the database. + * + * @return ArrayList of pairs of (database name, database file path) or null if the database + * is not open. + */ + public List> getAttachedDbs() { + ArrayList> attachedDbs = new ArrayList>(); + synchronized (mLock) { + if (!mHasAttachedDbsLocked) { + // No attached databases. + // There is a small window where attached databases exist but this flag is not + // set yet. This can occur when this thread is in a race condition with another + // thread that is executing the SQL statement: "attach database as " + // If this thread is NOT ok with such a race condition (and thus possibly not + // receivethe entire list of attached databases), then the caller should ensure + // that no thread is executing any SQL statements while a thread is calling this + // method. Typically, this method is called when 'adb bugreport' is done or the + // caller wants to collect stats on the database and all its attached databases. + attachedDbs.add(new Pair("main", mConfigurationLocked.path)); + return attachedDbs; + } + + acquireReference(); + } + + try { + // has attached databases. query sqlite to get the list of attached databases. + Cursor c = null; + try { + c = rawQuery("pragma database_list;", null); + while (c.moveToNext()) { + // sqlite returns a row for each database in the returned list of databases. + // in each row, + // 1st column is the database name such as main, or the database + // name specified on the "ATTACH" command + // 2nd column is the database file path. + attachedDbs.add(new Pair(c.getString(1), c.getString(2))); + } + } finally { + if (c != null) { + c.close(); + } + } + return attachedDbs; + } finally { + releaseReference(); + } + } + + /** + * Runs 'pragma integrity_check' on the given database (and all the attached databases) + * and returns true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + *

+ * If the result is false, then this method logs the errors reported by the integrity_check + * command execution. + *

+ * Note that 'pragma integrity_check' on a database can take a long time. + * + * @return true if the given database (and all its attached databases) pass integrity_check, + * false otherwise. + */ + public boolean isDatabaseIntegrityOk() { + acquireReference(); + try { + List> attachedDbs = null; + try { + attachedDbs = getAttachedDbs(); + if (attachedDbs == null) { + throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + + "be retrieved. probably because the database is closed"); + } + } catch (SQLiteException e) { + // can't get attachedDb list. do integrity check on the main database + attachedDbs = new ArrayList>(); + attachedDbs.add(new Pair("main", getPath())); + } + + for (int i = 0; i < attachedDbs.size(); i++) { + Pair p = attachedDbs.get(i); + SQLiteStatement prog = null; + try { + prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);"); + String rslt = prog.simpleQueryForString(); + if (!rslt.equalsIgnoreCase("ok")) { + // integrity_checker failed on main or attached databases + Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); + return false; + } + } finally { + if (prog != null) prog.close(); + } + } + } finally { + releaseReference(); + } + return true; + } + + @Override + public String toString() { + return "SQLiteDatabase: " + getPath(); + } + + private void throwIfNotOpenLocked() { + } + + private synchronized void B_openInner() { + try { + File newDbFile = new File(getPath()); + newDbFile.getParentFile().mkdirs(); + connection = DriverManager.getConnection("jdbc:sqlite:" + newDbFile.getAbsolutePath()); + B_reconfigureConnection(); + } catch (java.sql.SQLException e) { + //TODO Figure out how to detect corrupt databases + throw new SQLiteException("Failed to open database!", e); + } + } + + // === BRIDGE CODE START === + private static final String DRIVER_CLASS = "org.sqlite.JDBC"; + + private Connection connection; + private Deque transactionStack = new ArrayDeque<>(); + private boolean transactionStackInvalidated = false; + private ReentrantLock transactionLock = new ReentrantLock(); + + private void B_initDriver() { + try { + Class.forName(DRIVER_CLASS); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Database driver " + DRIVER_CLASS + " not found!"); + } + } + + private synchronized void B_reconfigureConnection() { + if (transactionLock.isLocked()) + throw new IllegalStateException("Transaction in progress!"); + + if (connection != null) { + try { + connection.createStatement() + .execute("PRAGMA foreign_keys = " + (mConfigurationLocked.foreignKeyConstraintsEnabled ? "ON" : "OFF")); + } catch (java.sql.SQLException e) { + throw new SQLiteException("Failed to (re)configure connection!", e); + } + } + } + + public void B_validateSql(String sql, CancellationSignal cancellationSignal) { + try { + connection.prepareStatement(sql).close(); + } catch (java.sql.SQLException e) { + throw new SQLiteException("Invalid SQL statement!", e); + } finally { + if (cancellationSignal.isCanceled()) { + throw new OperationCanceledException(); + } + } + } + + private synchronized boolean B_yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) { + if(transactionStack.size() > 1) + throw new SQLiteException("This method cannot be used when there are nested transactions!"); + try { + //Does not 100% replicate original behavior, but better than nothing + if(B_inTransaction()) { + connection.commit(); + connection.setAutoCommit(true); + Thread.sleep(sleepAfterYieldDelay); + connection.setAutoCommit(false); + return true; + } else { + return false; + } + } catch (java.sql.SQLException e) { + throw new SQLiteException("Failed to commit current transaction!", e); + } catch (InterruptedException e) { + return true; + } + } + + private boolean B_inTransaction() { + try { + return connection.getAutoCommit(); + } catch (java.sql.SQLException e) { + throw new SQLiteException("Failed to get transaction status!", e); + } + } + + private synchronized void B_setTransactionSuccessful() { + Transaction currentTransaction = transactionStack.peek(); + if(currentTransaction.successful) + throw new IllegalStateException("Transaction already marked as successful!"); + + currentTransaction.successful = true; + } + + private synchronized void B_endTransaction() { + if(transactionStack.isEmpty()) + throw new IllegalStateException("No transaction in progress!"); + + Transaction thisTransaction = transactionStack.pop(); + if(!thisTransaction.successful) + transactionStackInvalidated = true; + + if(transactionStack.isEmpty()) { + try { + if (!transactionStackInvalidated) { + //If all transaction ended successfully, commit + thisTransaction.listener.onCommit(); + connection.commit(); + } else { + //If a transaction failed, rollback + thisTransaction.listener.onRollback(); + connection.rollback(); + transactionStackInvalidated = false; + } + connection.setAutoCommit(true); + } catch(java.sql.SQLException e) { + throw new SQLiteException("Failed to commit transaction!", e); + } + } + transactionLock.unlock(); + } + + //Do not synchronize this method as it conflicts with the transaction lock + private void B_beginTransaction(SQLiteTransactionListener transactionListener, + boolean exclusive) { //TODO Deal with "exclusive" arg + transactionLock.lock(); + + if(transactionListener == null) + transactionListener = NoOpSQLiteTransactionListener.INSTANCE; + + try { + Transaction transaction = new Transaction(); + transaction.listener = transactionListener; + + if(transactionStack.isEmpty()) { + connection.setAutoCommit(false); + transactionStackInvalidated = false; + } + + transactionStack.push(transaction); + } catch (java.sql.SQLException e) { + throw new SQLiteException("Failed to start transaction!", e); + } + + transactionListener.onBegin(); + } + + /** + * Used to allow returning sub-classes of {@link Cursor} when calling query. + */ + public interface CursorFactory { + /** + * See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}. + */ + Cursor newCursor(SQLiteDatabase db, + SQLiteCursorDriver masterQuery, String editTable, + SQLiteQuery query); + } + + Connection getConnection() { + return connection; + } + + private static class Transaction { + SQLiteTransactionListener listener = NoOpSQLiteTransactionListener.INSTANCE; + boolean successful = false; + } + + private static class NoOpSQLiteTransactionListener implements SQLiteTransactionListener { + private static NoOpSQLiteTransactionListener INSTANCE = new NoOpSQLiteTransactionListener(); + + @Override + public void onBegin() {} + + @Override + public void onCommit() {} + + @Override + public void onRollback() {} + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseConfiguration.java new file mode 100644 index 00000000..d1873bfc --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseConfiguration.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2011 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 android.database.sqlite; + +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Describes how to configure a database. + *

+ * The purpose of this object is to keep track of all of the little + * configuration settings that are applied to a database after it + * is opened so that they can be applied to all connections in the + * connection pool uniformly. + *

+ * Each connection maintains its own copy of this object so it can + * keep track of which settings have already been applied. + *

+ * + * @hide + */ +public final class SQLiteDatabaseConfiguration { + // The pattern we use to strip email addresses from database paths + // when constructing a label to use in log messages. + private static final Pattern EMAIL_IN_DB_PATTERN = + Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); + + /** + * Special path used by in-memory databases. + */ + public static final String MEMORY_DB_PATH = ":memory:"; + + /** + * The database path. + */ + public final String path; + + /** + * The label to use to describe the database when it appears in logs. + * This is derived from the path but is stripped to remove PII. + */ + public final String label; + + /** + * The flags used to open the database. + */ + public int openFlags; + + /** + * The maximum size of the prepared statement cache for each database connection. + * Must be non-negative. + * + * Default is 25. + */ + public int maxSqlCacheSize; + + /** + * The database locale. + * + * Default is the value returned by {@link Locale#getDefault()}. + */ + public Locale locale; + + /** + * True if foreign key constraints are enabled. + * + * Default is false. + */ + public boolean foreignKeyConstraintsEnabled; + + /** + * Creates a database configuration with the required parameters for opening a + * database and default values for all other parameters. + * + * @param path The database path. + * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}. + */ + public SQLiteDatabaseConfiguration(String path, int openFlags) { + if (path == null) { + throw new IllegalArgumentException("path must not be null."); + } + + this.path = path; + label = stripPathForLogs(path); + this.openFlags = openFlags; + + // Set default values for optional parameters. + maxSqlCacheSize = 25; + locale = Locale.getDefault(); + } + + /** + * Creates a database configuration as a copy of another configuration. + * + * @param other The other configuration. + */ + public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null."); + } + + this.path = other.path; + this.label = other.label; + updateParametersFrom(other); + } + + /** + * Updates the non-immutable parameters of this configuration object + * from the other configuration object. + * + * @param other The object from which to copy the parameters. + */ + public void updateParametersFrom(SQLiteDatabaseConfiguration other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null."); + } + if (!path.equals(other.path)) { + throw new IllegalArgumentException("other configuration must refer to " + + "the same database."); + } + + openFlags = other.openFlags; + maxSqlCacheSize = other.maxSqlCacheSize; + locale = other.locale; + foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled; + } + + /** + * Returns true if the database is in-memory. + * @return True if the database is in-memory. + */ + public boolean isInMemoryDb() { + return path.equalsIgnoreCase(MEMORY_DB_PATH); + } + + private static String stripPathForLogs(String path) { + if (path.indexOf('@') == -1) { + return path; + } + return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY"); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseCorruptException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseCorruptException.java new file mode 100644 index 00000000..73b6c0ca --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseCorruptException.java @@ -0,0 +1,28 @@ +/* + * 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 android.database.sqlite; + +/** + * 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/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseLockedException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseLockedException.java new file mode 100644 index 00000000..f0e2d811 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatabaseLockedException.java @@ -0,0 +1,33 @@ +/* + * 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 android.database.sqlite; + +/** + * Thrown if the database engine was unable to acquire the + * database locks it needs to do its job. If the statement is a [COMMIT] + * or occurs outside of an explicit transaction, then you can retry the + * statement. If the statement is not a [COMMIT] and occurs within a + * explicit transaction then you should rollback the transaction before + * continuing. + */ +public class SQLiteDatabaseLockedException extends SQLiteException { + public SQLiteDatabaseLockedException() {} + + public SQLiteDatabaseLockedException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatatypeMismatchException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatatypeMismatchException.java new file mode 100644 index 00000000..7f825353 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDatatypeMismatchException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteDatatypeMismatchException extends SQLiteException { + public SQLiteDatatypeMismatchException() {} + + public SQLiteDatatypeMismatchException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDebug.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDebug.java new file mode 100644 index 00000000..247bb920 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDebug.java @@ -0,0 +1,170 @@ +/* + * 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 android.database.sqlite; + +import android.os.Build; +import android.os.SystemProperties; +import android.util.Log; +import android.util.Printer; + +import java.util.ArrayList; + +/** + * Provides debugging info about all SQLite databases running in the current process. + * + * {@hide} + */ +public final class SQLiteDebug { + private static native void nativeGetPagerStats(PagerStats stats); + + /** + * Controls the printing of informational SQL log messages. + * + * Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE". + */ + public static final boolean DEBUG_SQL_LOG = + Log.isLoggable("SQLiteLog", Log.VERBOSE); + + /** + * Controls the printing of SQL statements as they are executed. + * + * Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE". + */ + public static final boolean DEBUG_SQL_STATEMENTS = + Log.isLoggable("SQLiteStatements", Log.VERBOSE); + + /** + * Controls the printing of wall-clock time taken to execute SQL statements + * as they are executed. + * + * Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE". + */ + public static final boolean DEBUG_SQL_TIME = + Log.isLoggable("SQLiteTime", Log.VERBOSE); + + /** + * True to enable database performance testing instrumentation. + * @hide + */ + public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE; + + private SQLiteDebug() { + } + + /** + * Determines whether a query should be logged. + * + * Reads the "db.log.slow_query_threshold" system property, which can be changed + * by the user at any time. If the value is zero, then all queries will + * be considered slow. If the value does not exist or is negative, then no queries will + * be considered slow. + * + * This value can be changed dynamically while the system is running. + * For example, "adb shell setprop db.log.slow_query_threshold 200" will + * log all queries that take 200ms or longer to run. + * @hide + */ + public static final boolean shouldLogSlowQuery(long elapsedTimeMillis) { + int slowQueryMillis = SystemProperties.getInt("db.log.slow_query_threshold", -1); + return slowQueryMillis >= 0 && elapsedTimeMillis >= slowQueryMillis; + } + + /** + * Contains statistics about the active pagers in the current process. + * + * @see #nativeGetPagerStats(PagerStats) + */ + public static class PagerStats { + /** the current amount of memory checked out by sqlite using sqlite3_malloc(). + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int memoryUsed; + + /** the number of bytes of page cache allocation which could not be sattisfied by the + * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc(). + * The returned value includes allocations that overflowed because they where too large + * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations + * that overflowed because no space was left in the page cache. + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int pageCacheOverflow; + + /** records the largest memory allocation request handed to sqlite3. + * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html + */ + public int largestMemAlloc; + + /** a list of {@link DbStats} - one for each main database opened by the applications + * running on the android device + */ + public ArrayList dbStats; + } + + /** + * contains statistics about a database + */ + public static class DbStats { + /** name of the database */ + public String dbName; + + /** the page size for the database */ + public long pageSize; + + /** the database size */ + public long dbSize; + + /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */ + public int lookaside; + + /** statement cache stats: hits/misses/cachesize */ + public String cache; + + public DbStats(String dbName, long pageCount, long pageSize, int lookaside, + int hits, int misses, int cachesize) { + this.dbName = dbName; + this.pageSize = pageSize / 1024; + dbSize = (pageCount * pageSize) / 1024; + this.lookaside = lookaside; + this.cache = hits + "/" + misses + "/" + cachesize; + } + } + + /** + * return all pager and database stats for the current process. + * @return {@link PagerStats} + */ + public static PagerStats getDatabaseInfo() { + PagerStats stats = new PagerStats(); + nativeGetPagerStats(stats); + stats.dbStats = SQLiteDatabase.getDbStats(); + return stats; + } + + /** + * Dumps detailed information about all databases used by the process. + * @param printer The printer for dumping database state. + * @param args Command-line arguments supplied to dumpsys dbinfo + */ + public static void dump(Printer printer, String[] args) { + boolean verbose = false; + for (String arg : args) { + if (arg.equals("-v")) { + verbose = true; + } + } + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDirectCursorDriver.java new file mode 100644 index 00000000..797430a4 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -0,0 +1,83 @@ +/* + * 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 android.database.sqlite; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.os.CancellationSignal; + +/** + * A cursor driver that uses the given query directly. + * + * @hide + */ +public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver { + private final SQLiteDatabase mDatabase; + private final String mEditTable; + private final String mSql; + private final CancellationSignal mCancellationSignal; + private SQLiteQuery mQuery; + + public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable, + CancellationSignal cancellationSignal) { + mDatabase = db; + mEditTable = editTable; + mSql = sql; + mCancellationSignal = cancellationSignal; + } + + public Cursor query(CursorFactory factory, String[] selectionArgs) { + final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal); + final Cursor cursor; + try { + query.bindAllArgsAsStrings(selectionArgs); + + if (factory == null) { + cursor = new SQLiteCursor(this, mEditTable, query); + } else { + cursor = factory.newCursor(mDatabase, this, mEditTable, query); + } + } catch (RuntimeException ex) { + query.close(); + throw ex; + } + + mQuery = query; + return cursor; + } + + public void cursorClosed() { + // Do nothing + } + + public void setBindArguments(String[] bindArgs) { + mQuery.bindAllArgsAsStrings(bindArgs); + } + + public void cursorDeactivated() { + // Do nothing + } + + public void cursorRequeried(Cursor cursor) { + // Do nothing + } + + @Override + public String toString() { + return "SQLiteDirectCursorDriver: " + mSql; + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDiskIOException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDiskIOException.java new file mode 100644 index 00000000..01b2069c --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDiskIOException.java @@ -0,0 +1,29 @@ +/* + * 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 android.database.sqlite; + +/** + * 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/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDoneException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDoneException.java new file mode 100644 index 00000000..d6d3f669 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteDoneException.java @@ -0,0 +1,31 @@ +/* + * 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 android.database.sqlite; + +/** + * 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/AndroidCompat/src/main/java/android/database/sqlite/SQLiteException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteException.java new file mode 100644 index 00000000..a1d9c9f9 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteException.java @@ -0,0 +1,35 @@ +/* + * 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 android.database.sqlite; + +import android.database.SQLException; + +/** + * A SQLite exception that indicates there was an error with SQL parsing or execution. + */ +public class SQLiteException extends SQLException { + public SQLiteException() { + } + + public SQLiteException(String error) { + super(error); + } + + public SQLiteException(String error, Throwable cause) { + super(error, cause); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteFullException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteFullException.java new file mode 100644 index 00000000..582d930d --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteFullException.java @@ -0,0 +1,28 @@ +/* + * 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 android.database.sqlite; + +/** + * An exception that indicates that the SQLite database is full. + */ +public class SQLiteFullException extends SQLiteException { + public SQLiteFullException() {} + + public SQLiteFullException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteGlobal.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteGlobal.java new file mode 100644 index 00000000..0d269eec --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteGlobal.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 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 android.database.sqlite; + +import android.os.StatFs; +import android.os.SystemProperties; + +/** + * Provides access to SQLite functions that affect all database connection, + * such as memory management. + * + * The native code associated with SQLiteGlobal is also sets global configuration options + * using sqlite3_config() then calls sqlite3_initialize() to ensure that the SQLite + * library is properly initialized exactly once before any other framework or application + * code has a chance to run. + * + * Verbose SQLite logging is enabled if the "log.tag.SQLiteLog" property is set to "V". + * (per {@link SQLiteDebug#DEBUG_SQL_LOG}). + * + * @hide + */ +public final class SQLiteGlobal { + private static final Object sLock = new Object(); + private static int sDefaultPageSize; + + private static native int nativeReleaseMemory(); + + private SQLiteGlobal() { + } + + /** + * Attempts to release memory by pruning the SQLite page cache and other + * internal data structures. + * + * @return The number of bytes that were freed. + */ + public static int releaseMemory() { + return nativeReleaseMemory(); + } + + /** + * Gets the default page size to use when creating a database. + */ + public static int getDefaultPageSize() { + synchronized (sLock) { + if (sDefaultPageSize == 0) { + sDefaultPageSize = new StatFs("/data").getBlockSize(); + } + return SystemProperties.getInt("debug.sqlite.pagesize", sDefaultPageSize); + } + } + + /** + * Gets the default journal mode when WAL is not in use. + */ + public static String getDefaultJournalMode() { + return SystemProperties.get("debug.sqlite.journalmode","TRUNCATE"); + } + + /** + * Gets the journal size limit in bytes. + */ + public static int getJournalSizeLimit() { + return SystemProperties.getInt("debug.sqlite.journalsizelimit", + 524288); + } + + /** + * Gets the default database synchronization mode when WAL is not in use. + */ + public static String getDefaultSyncMode() { + return SystemProperties.get("debug.sqlite.syncmode","FULL"); + } + + /** + * Gets the database synchronization mode when in WAL mode. + */ + public static String getWALSyncMode() { + return SystemProperties.get("debug.sqlite.wal.syncmode", "FULL"); + } + + /** + * Gets the WAL auto-checkpoint integer in database pages. + */ + public static int getWALAutoCheckpoint() { + int value = SystemProperties.getInt("debug.sqlite.wal.autocheckpoint", 100); + return Math.max(1, value); + } + + /** + * Gets the connection pool size when in WAL mode. + */ + public static int getWALConnectionPoolSize() { + int value = SystemProperties.getInt("debug.sqlite.wal.poolsize", 4); + return Math.max(2, value); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteMisuseException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteMisuseException.java new file mode 100644 index 00000000..546ec08c --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteMisuseException.java @@ -0,0 +1,37 @@ +/* + * 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 android.database.sqlite; + +/** + * This error can occur if the application creates a SQLiteStatement object and allows multiple + * threads in the application use it at the same time. + * Sqlite returns this error if bind and execute methods on this object occur at the same time + * from multiple threads, like so: + * thread # 1: in execute() method of the SQLiteStatement object + * while thread # 2: is in bind..() on the same object. + *

+ * FIX this by NEVER sharing the same SQLiteStatement object between threads. + * Create a local instance of the SQLiteStatement whenever it is needed, use it and close it ASAP. + * NEVER make it globally available. + */ +public class SQLiteMisuseException extends SQLiteException { + public SQLiteMisuseException() {} + + public SQLiteMisuseException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOpenHelper.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOpenHelper.java new file mode 100644 index 00000000..2dd48002 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOpenHelper.java @@ -0,0 +1,378 @@ +/* + * 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 android.database.sqlite; + +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.util.Log; + +/** + * A helper class to manage database creation and version management. + * + *

You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and + * optionally {@link #onOpen}, and this class takes care of opening the database + * if it exists, creating it if it does not, and upgrading it as necessary. + * Transactions are used to make sure the database is always in a sensible state. + * + *

This class makes it easy for {@link android.content.ContentProvider} + * implementations to defer opening and upgrading the database until first use, + * to avoid blocking application startup with long-running database upgrades. + * + *

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

+ * + *

Note: this class assumes + * monotonically increasing version numbers for upgrades.

+ */ +public abstract class SQLiteOpenHelper { + private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); + + // When true, getReadableDatabase returns a read-only database if it is just being opened. + // The database handle is reopened in read/write mode when getWritableDatabase is called. + // We leave this behavior disabled in production because it is inefficient and breaks + // many applications. For debugging purposes it can be useful to turn on strict + // read-only semantics to catch applications that call getReadableDatabase when they really + // wanted getWritableDatabase. + private static final boolean DEBUG_STRICT_READONLY = false; + + private final Context mContext; + private final String mName; + private final CursorFactory mFactory; + private final int mNewVersion; + + private SQLiteDatabase mDatabase; + private boolean mIsInitializing; + private boolean mEnableWriteAheadLogging; + private final DatabaseErrorHandler mErrorHandler; + + /** + * 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; if the database is + * newer, {@link #onDowngrade} will be used to downgrade the database + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { + this(context, name, factory, version, null); + } + + /** + * 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; if the database is + * newer, {@link #onDowngrade} will be used to downgrade the database + * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption, or null to use the default error handler. + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, + DatabaseErrorHandler errorHandler) { + if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); + + mContext = context; + mName = name; + mFactory = factory; + mNewVersion = version; + mErrorHandler = errorHandler; + } + + /** + * 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; + } + } + } + + /** + * Create and/or open a database that will be used for reading and writing. + * The first time this is called, the database will be opened and + * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be + * called. + * + *

Once opened successfully, the database is cached, so you can + * call this method every time you need to write to the database. + * (Make sure to call {@link #close} when you no longer need the database.) + * Errors such as bad permissions or a full disk may cause this method + * to fail, but future attempts may succeed if the problem is fixed.

+ * + *

Database upgrade may take a long time, you + * should not call this method from the application main thread, including + * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @throws SQLiteException if the database cannot be opened for writing + * @return a read/write database object valid until {@link #close} is called + */ + public SQLiteDatabase getWritableDatabase() { + synchronized (this) { + return getDatabaseLocked(true); + } + } + + /** + * Create and/or open a database. This will be the same object returned by + * {@link #getWritableDatabase} unless some problem, such as a full disk, + * requires the database to be opened read-only. In that case, a read-only + * database object will be returned. If the problem is fixed, a future call + * to {@link #getWritableDatabase} may succeed, in which case the read-only + * database object will be closed and the read/write object will be returned + * in the future. + * + *

Like {@link #getWritableDatabase}, this method may + * take a long time to return, so you should not call it from the + * application main thread, including from + * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * + * @throws SQLiteException if the database cannot be opened + * @return a database object valid until {@link #getWritableDatabase} + * or {@link #close} is called. + */ + public SQLiteDatabase getReadableDatabase() { + synchronized (this) { + return getDatabaseLocked(false); + } + } + + private SQLiteDatabase getDatabaseLocked(boolean writable) { + if (mDatabase != null) { + if (!mDatabase.isOpen()) { + // Darn! The user closed the database by calling mDatabase.close(). + mDatabase = null; + } else if (!writable || !mDatabase.isReadOnly()) { + // The database is already open for business. + return mDatabase; + } + } + + if (mIsInitializing) { + throw new IllegalStateException("getDatabase called recursively"); + } + + SQLiteDatabase db = mDatabase; + try { + mIsInitializing = true; + + if (db != null) { + if (writable && db.isReadOnly()) { + db.reopenReadWrite(); + } + } else if (mName == null) { + db = SQLiteDatabase.create(null); + } else { + try { + if (DEBUG_STRICT_READONLY && !writable) { + final String path = mContext.getDatabasePath(mName).getPath(); + db = SQLiteDatabase.openDatabase(path, mFactory, + SQLiteDatabase.OPEN_READONLY, mErrorHandler); + } else { + db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? + Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0, + mFactory, mErrorHandler); + } + } catch (SQLiteException ex) { + if (writable) { + throw ex; + } + Log.e(TAG, "Couldn't open " + mName + + " for writing (will try read-only):", ex); + final String path = mContext.getDatabasePath(mName).getPath(); + db = SQLiteDatabase.openDatabase(path, mFactory, + SQLiteDatabase.OPEN_READONLY, mErrorHandler); + } + } + + onConfigure(db); + + final int version = db.getVersion(); + if (version != mNewVersion) { + if (db.isReadOnly()) { + throw new SQLiteException("Can't upgrade read-only database from version " + + db.getVersion() + " to " + mNewVersion + ": " + mName); + } + + db.beginTransaction(); + try { + if (version == 0) { + onCreate(db); + } else { + if (version > mNewVersion) { + onDowngrade(db, version, mNewVersion); + } else { + onUpgrade(db, version, mNewVersion); + } + } + db.setVersion(mNewVersion); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + onOpen(db); + + if (db.isReadOnly()) { + Log.w(TAG, "Opened " + mName + " in read-only mode"); + } + + mDatabase = db; + return db; + } finally { + mIsInitializing = false; + if (db != null && db != mDatabase) { + db.close(); + } + } + } + + /** + * Close any open database object. + */ + public synchronized void close() { + if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); + + if (mDatabase != null && mDatabase.isOpen()) { + mDatabase.close(); + mDatabase = null; + } + } + + /** + * 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}, {@link SQLiteDatabase#setMaximumSize}, + * or executing PRAGMA statements. + *

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

+ * The SQLite ALTER TABLE documentation can be found + * here. If you add new columns + * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns + * you can use ALTER TABLE to rename the old table, then create the new table and then + * populate the new table with the contents of the old table. + *

+ * 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 abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); + + /** + * 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 has been opened. The implementation + * should check {@link SQLiteDatabase#isReadOnly} before updating the + * database. + *

+ * This method is called after the database connection has been configured + * and after the database schema has been created, upgraded or downgraded as necessary. + * If the database connection must be configured in some way before the schema + * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. + *

+ * + * @param db The database. + */ + public void onOpen(SQLiteDatabase db) {} +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOutOfMemoryException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOutOfMemoryException.java new file mode 100644 index 00000000..98ce8b5f --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteOutOfMemoryException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteOutOfMemoryException extends SQLiteException { + public SQLiteOutOfMemoryException() {} + + public SQLiteOutOfMemoryException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteProgram.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteProgram.java new file mode 100644 index 00000000..ae95491f --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteProgram.java @@ -0,0 +1,235 @@ +/* + * 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 android.database.sqlite; + +import android.database.DatabaseUtils; +import android.os.CancellationSignal; +import xyz.nulldev.androidcompat.db.ScrollableResultSet; + +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; + +/** + * A base class for compiled SQLite programs. + *

+ * This class is not thread-safe. + *

+ */ +public abstract class SQLiteProgram extends SQLiteClosable { + private final SQLiteDatabase mDatabase; + private final String mSql; + private final int mNumParameters; + private final Object[] mBindArgs; + private PreparedStatement preparedStatement = null; + + SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, + CancellationSignal cancellationSignalForPrepare) { + mDatabase = db; + mSql = sql.trim(); + + ParameterMetaData metaData; + try { + preparedStatement = mDatabase.getConnection().prepareStatement(mSql, Statement.RETURN_GENERATED_KEYS); + metaData = preparedStatement.getParameterMetaData(); + mNumParameters = metaData.getParameterCount(); + } catch (SQLException e) { + throw new SQLiteException("Could not compile SQL statement: " + mSql, e); + } + + int n = DatabaseUtils.getSqlStatementType(mSql); + switch (n) { + case DatabaseUtils.STATEMENT_BEGIN: + case DatabaseUtils.STATEMENT_COMMIT: + case DatabaseUtils.STATEMENT_ABORT: + break; + + default: + break; + } + + if (bindArgs != null && bindArgs.length > mNumParameters) { + throw new IllegalArgumentException("Too many bind arguments. " + + bindArgs.length + " arguments were provided but the statement needs " + + mNumParameters + " arguments."); + } + + if (mNumParameters != 0) { + mBindArgs = new Object[mNumParameters]; + if (bindArgs != null) { + System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length); + } + } else { + mBindArgs = null; + } + } + + PreparedStatement getPreparedStatement() { + return preparedStatement; + } + + final SQLiteDatabase getDatabase() { + return mDatabase; + } + + final String getSql() { + return mSql; + } + + final Object[] getBindArgs() { + return mBindArgs; + } + + /** + * Unimplemented. + * @deprecated This method is deprecated and must not be used. + */ + @Deprecated + public final int getUniqueId() { + return -1; + } + + /** + * Bind a NULL value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind null to + */ + public void bindNull(int index) { + bind(index, null); + } + + /** + * Bind a long value to this statement. The value remains bound until + * {@link #clearBindings} is called. + *addToBindArgs + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + public void bindLong(int index, long value) { + bind(index, value); + } + + /** + * Bind a double value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind + */ + public void bindDouble(int index, double value) { + bind(index, value); + } + + /** + * Bind a String value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + public void bindString(int index, String value) { + if (value == null) { + throw new IllegalArgumentException("the bind value at index " + index + " is null"); + } + bind(index, value); + } + + /** + * Bind a byte array value to this statement. The value remains bound until + * {@link #clearBindings} is called. + * + * @param index The 1-based index to the parameter to bind + * @param value The value to bind, must not be null + */ + public void bindBlob(int index, byte[] value) { + if (value == null) { + throw new IllegalArgumentException("the bind value at index " + index + " is null"); + } + bind(index, value); + } + + /** + * Clears all existing bindings. Unset bindings are treated as NULL. + */ + public void clearBindings() { + if (mBindArgs != null) { + Arrays.fill(mBindArgs, null); + } + } + + /** + * Given an array of String bindArgs, this method binds all of them in one single call. + * + * @param bindArgs the String array of bind args, none of which must be null. + */ + public void bindAllArgsAsStrings(String[] bindArgs) { + if (bindArgs != null) { + for (int i = bindArgs.length; i != 0; i--) { + bindString(i, bindArgs[i - 1]); + } + } + } + + @Override + protected void onAllReferencesReleased() { + clearBindings(); + + // Close prepared statement + try { + if (preparedStatement.isClosed()) + preparedStatement.close(); + } catch(SQLException ignored) {} + } + + private void bind(int index, Object value) { + if (index < 1 || index > mNumParameters) { + throw new IllegalArgumentException("Cannot bind argument at index " + + index + " because the index is out of range. " + + "The statement has " + mNumParameters + " parameters."); + } + mBindArgs[index - 1] = value; + } + + protected void B_setBindArgs() { + Object[] bindArgs = getBindArgs(); + if(bindArgs == null) bindArgs = new Object[0]; + for(int i = 0; i < bindArgs.length; i++) { + Object obj = bindArgs[i]; + try { + getPreparedStatement().setObject(i + 1, obj); + } catch (SQLException e) { + throw new SQLiteException("Failed to bind argument: (" + i + ", " + obj + ")"); + } + } + } + + private ScrollableResultSet resultSet = null; + ScrollableResultSet getResultSet() { + if(resultSet == null) { + try { + resultSet = new ScrollableResultSet(getPreparedStatement().getResultSet()); + } catch (SQLException e) { + throw new SQLiteException("Failed to get result set!", e); + } + } + + return resultSet; + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQuery.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQuery.java new file mode 100644 index 00000000..301fd93b --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQuery.java @@ -0,0 +1,78 @@ +/* + * 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 android.database.sqlite; + +import android.database.CursorWindow; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; +import android.util.Log; + +/** + * Represents a query that reads the resulting rows into a {@link SQLiteQuery}. + * This class is used by {@link SQLiteCursor} and isn't useful itself. + *

+ * This class is not thread-safe. + *

+ */ +public final class SQLiteQuery extends SQLiteProgram { + private static final String TAG = "SQLiteQuery"; + + private final CancellationSignal mCancellationSignal; + + SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) { + super(db, query, null, cancellationSignal); + + mCancellationSignal = cancellationSignal; + } + + /** + * Reads rows into a buffer. + * + * @param window The window to fill into + * @param startPos The start position for filling the window. + * @param requiredPos The position of a row that MUST be in the window. + * If it won't fit, then the query should discard part of what it filled. + * @param countAllRows True to count all rows that the query would + * return regardless of whether they fit in the window. + * @return Number of rows that were enumerated. Might not be all rows + * unless countAllRows is true. + * + * @throws SQLiteException if an error occurs. + * @throws OperationCanceledException if the operation was canceled. + */ + int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) { + acquireReference(); + try { + window.acquireReference(); + try { + throw new SQLiteException("Not implemented!"); + } catch (SQLiteException ex) { + Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql()); + throw ex; + } finally { + window.releaseReference(); + } + } finally { + releaseReference(); + } + } + + @Override + public String toString() { + return "SQLiteQuery: " + getSql(); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQueryBuilder.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQueryBuilder.java new file mode 100644 index 00000000..56cba795 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -0,0 +1,648 @@ +/* + * 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 android.database.sqlite; + +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; +import android.provider.BaseColumns; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * This is a convience class that helps build SQL queries to be sent to + * {@link SQLiteDatabase} objects. + */ +public class SQLiteQueryBuilder +{ + private static final String TAG = "SQLiteQueryBuilder"; + private static final Pattern sLimitPattern = + Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); + + private Map mProjectionMap = null; + private String mTables = ""; + private StringBuilder mWhereClause = null; // lazily created + private boolean mDistinct; + private SQLiteDatabase.CursorFactory mFactory; + private boolean mStrict; + + public SQLiteQueryBuilder() { + mDistinct = false; + mFactory = null; + } + + /** + * Mark the query as DISTINCT. + * + * @param distinct if true the query is DISTINCT, otherwise it isn't + */ + public void setDistinct(boolean distinct) { + mDistinct = distinct; + } + + /** + * Returns the list of tables being queried + * + * @return the list of tables being queried + */ + public String getTables() { + return mTables; + } + + /** + * Sets the list of tables to query. Multiple tables can be specified to perform a join. + * For example: + * setTables("foo, bar") + * setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)") + * + * @param inTables the list of tables to query on + */ + public void setTables(String inTables) { + mTables = inTables; + } + + /** + * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded + * by parenthesis and ANDed with the selection passed to {@link #query}. The final + * WHERE clause looks like: + * + * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) + * + * @param inWhere the chunk of text to append to the WHERE clause. + */ + public void appendWhere(CharSequence inWhere) { + if (mWhereClause == null) { + mWhereClause = new StringBuilder(inWhere.length() + 16); + } + if (mWhereClause.length() == 0) { + mWhereClause.append('('); + } + mWhereClause.append(inWhere); + } + + /** + * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded + * by parenthesis and ANDed with the selection passed to {@link #query}. The final + * WHERE clause looks like: + * + * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) + * + * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped + * to avoid SQL injection attacks + */ + public void appendWhereEscapeString(String inWhere) { + if (mWhereClause == null) { + mWhereClause = new StringBuilder(inWhere.length() + 16); + } + if (mWhereClause.length() == 0) { + mWhereClause.append('('); + } + DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); + } + + /** + * Sets the projection map for the query. The projection map maps + * from column names that the caller passes into query to database + * column names. This is useful for renaming columns as well as + * disambiguating column names when doing joins. For example you + * could map "name" to "people.name". If a projection map is set + * it must contain all column names the user may request, even if + * the key and value are the same. + * + * @param columnMap maps from the user column names to the database column names + */ + public void setProjectionMap(Map columnMap) { + mProjectionMap = columnMap; + } + + /** + * Sets the cursor factory to be used for the query. You can use + * one factory for all queries on a database but it is normally + * easier to specify the factory when doing this query. + * + * @param factory the factory to use. + */ + public void setCursorFactory(SQLiteDatabase.CursorFactory factory) { + mFactory = factory; + } + + /** + * When set, the selection is verified against malicious arguments. + * When using this class to create a statement using + * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, + * non-numeric limits will raise an exception. If a projection map is specified, fields + * not in that map will be ignored. + * If this class is used to execute the statement directly using + * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)} + * or + * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)}, + * additionally also parenthesis escaping selection are caught. + * + * To summarize: To get maximum protection against malicious third party apps (for example + * content provider consumers), make sure to do the following: + *
    + *
  • Set this value to true
  • + *
  • Use a projection map
  • + *
  • Use one of the query overloads instead of getting the statement as a sql string
  • + *
+ * By default, this value is false. + */ + public void setStrict(boolean flag) { + mStrict = flag; + } + + /** + * Build an SQL query string from the given clauses. + * + * @param distinct true if you want each row to be unique, false otherwise. + * @param tables The table names 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 where 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 URL. + * @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 the SQL query string + */ + public static String buildQueryString( + boolean distinct, String tables, String[] columns, String where, + String groupBy, String having, String orderBy, String limit) { + if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) { + throw new IllegalArgumentException( + "HAVING clauses are only permitted when using a groupBy clause"); + } + if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { + throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); + } + + StringBuilder query = new StringBuilder(120); + + query.append("SELECT "); + if (distinct) { + query.append("DISTINCT "); + } + if (columns != null && columns.length != 0) { + appendColumns(query, columns); + } else { + query.append("* "); + } + query.append("FROM "); + query.append(tables); + appendClause(query, " WHERE ", where); + appendClause(query, " GROUP BY ", groupBy); + appendClause(query, " HAVING ", having); + appendClause(query, " ORDER BY ", orderBy); + appendClause(query, " LIMIT ", limit); + + return query.toString(); + } + + private static void appendClause(StringBuilder s, String name, String clause) { + if (!TextUtils.isEmpty(clause)) { + s.append(name); + s.append(clause); + } + } + + /** + * Add the names that are non-null in columns to s, separating + * them with commas. + */ + public static void appendColumns(StringBuilder s, String[] columns) { + int n = columns.length; + + for (int i = 0; i < n; i++) { + String column = columns[i]; + + if (column != null) { + if (i > 0) { + s.append(", "); + } + s.append(column); + } + } + s.append(' '); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn 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 URL. + * @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 sortOrder 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 cursor over the result set + * @see android.content.ContentResolver#query(android.net.Uri, String[], + * String, String[], String) + */ + 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, + null /* limit */, null /* cancellationSignal */); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn 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 URL. + * @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 sortOrder 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 cursor over the result set + * @see android.content.ContentResolver#query(android.net.Uri, String[], + * String, String[], String) + */ + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit) { + return query(db, projectionIn, selection, selectionArgs, + groupBy, having, sortOrder, limit, null); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn 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 URL. + * @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 sortOrder 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. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return a cursor over the result set + * @see android.content.ContentResolver#query(android.net.Uri, String[], + * String, String[], String) + */ + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit, CancellationSignal cancellationSignal) { + if (mTables == null) { + return null; + } + + if (mStrict && selection != null && selection.length() > 0) { + // Validate the user-supplied selection to detect syntactic anomalies + // in the selection string that could indicate a SQL injection attempt. + // The idea is to ensure that the selection clause is a valid SQL expression + // by compiling it twice: once wrapped in parentheses and once as + // originally specified. An attacker cannot create an expression that + // would escape the SQL expression while maintaining balanced parentheses + // in both the wrapped and original forms. + String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, + having, sortOrder, limit); + db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid + } + + String sql = buildQuery( + projectionIn, selection, groupBy, having, + sortOrder, limit); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Performing query: " + sql); + } + return db.rawQueryWithFactory( + mFactory, sql, selectionArgs, + SQLiteDatabase.findEditTable(mTables), + cancellationSignal); // will throw if query is invalid + } + + /** + * Construct a SELECT statement suitable for use in a group of + * SELECT statements that will be joined through UNION operators + * in buildUnionQuery. + * + * @param projectionIn 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 + * URL. + * @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 sortOrder 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 the resulting SQL SELECT statement + */ + public String buildQuery( + String[] projectionIn, String selection, String groupBy, + String having, String sortOrder, String limit) { + String[] projection = computeProjection(projectionIn); + + StringBuilder where = new StringBuilder(); + boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; + + if (hasBaseWhereClause) { + where.append(mWhereClause.toString()); + where.append(')'); + } + + // Tack on the user's selection, if present. + if (selection != null && selection.length() > 0) { + if (hasBaseWhereClause) { + where.append(" AND "); + } + + where.append('('); + where.append(selection); + where.append(')'); + } + + return buildQueryString( + mDistinct, mTables, projection, where.toString(), + groupBy, having, sortOrder, limit); + } + + /** + * @deprecated This method's signature is misleading since no SQL parameter + * substitution is carried out. The selection arguments parameter does not get + * used at all. To avoid confusion, call + * {@link #buildQuery(String[], String, String, String, String, String)} instead. + */ + @Deprecated + public String buildQuery( + String[] projectionIn, String selection, String[] selectionArgs, + String groupBy, String having, String sortOrder, String limit) { + return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit); + } + + /** + * Construct a SELECT statement suitable for use in a group of + * SELECT statements that will be joined through UNION operators + * in buildUnionQuery. + * + * @param typeDiscriminatorColumn the name of the result column + * whose cells will contain the name of the table from which + * each row was drawn. + * @param unionColumns the names of the columns to appear in the + * result. This may include columns that do not appear in the + * table this SELECT is querying (i.e. mTables), but that do + * appear in one of the other tables in the UNION query that we + * are constructing. + * @param columnsPresentInTable a Set of the names of the columns + * that appear in this table (i.e. in the table whose name is + * mTables). Since columns in unionColumns include columns that + * appear only in other tables, we use this array to distinguish + * which ones actually are present. Other columns will have + * NULL values for results from this subquery. + * @param computedColumnsOffset all columns in unionColumns before + * this index are included under the assumption that they're + * computed and therefore won't appear in columnsPresentInTable, + * e.g. "date * 1000 as normalized_date" + * @param typeDiscriminatorValue the value used for the + * type-discriminator column in this subquery + * @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 + * URL. + * @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. + * @return the resulting SQL SELECT statement + */ + public String buildUnionSubQuery( + String typeDiscriminatorColumn, + String[] unionColumns, + Set columnsPresentInTable, + int computedColumnsOffset, + String typeDiscriminatorValue, + String selection, + String groupBy, + String having) { + int unionColumnsCount = unionColumns.length; + String[] projectionIn = new String[unionColumnsCount]; + + for (int i = 0; i < unionColumnsCount; i++) { + String unionColumn = unionColumns[i]; + + if (unionColumn.equals(typeDiscriminatorColumn)) { + projectionIn[i] = "'" + typeDiscriminatorValue + "' AS " + + typeDiscriminatorColumn; + } else if (i <= computedColumnsOffset + || columnsPresentInTable.contains(unionColumn)) { + projectionIn[i] = unionColumn; + } else { + projectionIn[i] = "NULL AS " + unionColumn; + } + } + return buildQuery( + projectionIn, selection, groupBy, having, + null /* sortOrder */, + null /* limit */); + } + + /** + * @deprecated This method's signature is misleading since no SQL parameter + * substitution is carried out. The selection arguments parameter does not get + * used at all. To avoid confusion, call + * {@link #buildUnionSubQuery} + * instead. + */ + @Deprecated + public String buildUnionSubQuery( + String typeDiscriminatorColumn, + String[] unionColumns, + Set columnsPresentInTable, + int computedColumnsOffset, + String typeDiscriminatorValue, + String selection, + String[] selectionArgs, + String groupBy, + String having) { + return buildUnionSubQuery( + typeDiscriminatorColumn, unionColumns, columnsPresentInTable, + computedColumnsOffset, typeDiscriminatorValue, selection, + groupBy, having); + } + + /** + * Given a set of subqueries, all of which are SELECT statements, + * construct a query that returns the union of what those + * subqueries return. + * @param subQueries an array of SQL SELECT statements, all of + * which must have the same columns as the same positions in + * their results + * @param sortOrder 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 The limit clause, which applies to the entire union result set + * + * @return the resulting SQL SELECT statement + */ + public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) { + StringBuilder query = new StringBuilder(128); + int subQueryCount = subQueries.length; + String unionOperator = mDistinct ? " UNION " : " UNION ALL "; + + for (int i = 0; i < subQueryCount; i++) { + if (i > 0) { + query.append(unionOperator); + } + query.append(subQueries[i]); + } + appendClause(query, " ORDER BY ", sortOrder); + appendClause(query, " LIMIT ", limit); + return query.toString(); + } + + private String[] computeProjection(String[] projectionIn) { + if (projectionIn != null && projectionIn.length > 0) { + if (mProjectionMap != null) { + String[] projection = new String[projectionIn.length]; + int length = projectionIn.length; + + for (int i = 0; i < length; i++) { + String userColumn = projectionIn[i]; + String column = mProjectionMap.get(userColumn); + + if (column != null) { + projection[i] = column; + continue; + } + + if (!mStrict && + ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { + /* A column alias already exist */ + projection[i] = userColumn; + continue; + } + + throw new IllegalArgumentException("Invalid column " + + projectionIn[i]); + } + return projection; + } else { + return projectionIn; + } + } else if (mProjectionMap != null) { + // Return all columns in projection map. + Set> entrySet = mProjectionMap.entrySet(); + String[] projection = new String[entrySet.size()]; + Iterator> entryIter = entrySet.iterator(); + int i = 0; + + while (entryIter.hasNext()) { + Entry entry = entryIter.next(); + + // Don't include the _count column when people ask for no projection. + if (entry.getKey().equals(BaseColumns._COUNT)) { + continue; + } + projection[i++] = entry.getValue(); + } + return projection; + } + return null; + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java new file mode 100644 index 00000000..5b633c62 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteReadOnlyDatabaseException extends SQLiteException { + public SQLiteReadOnlyDatabaseException() {} + + public SQLiteReadOnlyDatabaseException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatement.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatement.java new file mode 100644 index 00000000..e27883fe --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatement.java @@ -0,0 +1,167 @@ +/* + * 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 android.database.sqlite; + +import android.os.ParcelFileDescriptor; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Represents a statement that can be executed against a database. The statement + * cannot return multiple rows or columns, but single value (1 x 1) result sets + * are supported. + *

+ * This class is not thread-safe. + *

+ */ +public final class SQLiteStatement extends SQLiteProgram { + SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { + super(db, sql, bindArgs, null); + } + + /** + * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example + * CREATE / DROP table, view, trigger, index etc. + * + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + public void execute() { + acquireReference(); + try { + B_setBindArgs(); + getPreparedStatement().execute(); + } catch (SQLException e) { + throw new SQLiteException("Failed to execute SQL statement!", e); + } finally { + releaseReference(); + } + } + + /** + * Execute this SQL statement, if the the number of rows affected by execution of this SQL + * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. + * + * @return the number of rows affected by this SQL statement execution. + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + public int executeUpdateDelete() { + acquireReference(); + try { + B_setBindArgs(); + getPreparedStatement().execute(); + return getPreparedStatement().getUpdateCount(); + } catch (SQLException e) { + throw new SQLiteException("Failed to execute SQL statement!", e); + } finally { + releaseReference(); + } + } + + /** + * Execute this SQL statement and return the ID of the row inserted due to this call. + * The SQL statement should be an INSERT for this to be a useful call. + * + * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. + * + * @throws android.database.SQLException If the SQL string is invalid for + * some reason + */ + public long executeInsert() { + acquireReference(); + try { + B_setBindArgs(); + getPreparedStatement().executeUpdate(); + ResultSet generated = getPreparedStatement().getGeneratedKeys(); + if(generated.next()) + return generated.getLong(1); + else + return -1; + } catch (SQLException e) { + throw new SQLiteException("Failed to execute SQL statement!", e); + } finally { + releaseReference(); + } + } + + /** + * Execute a statement that returns a 1 by 1 table with a numeric value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows + */ + public long simpleQueryForLong() { + acquireReference(); + try { + B_setBindArgs(); + getPreparedStatement().execute(); + if(getResultSet().next()) + return getResultSet().getLong(1); + else + throw new SQLiteDoneException(); + } catch (SQLException e) { + throw new SQLiteException("Failed to execute SQL statement!", e); + } finally { + releaseReference(); + } + } + + /** + * Execute a statement that returns a 1 by 1 table with a text value. + * For example, SELECT COUNT(*) FROM table; + * + * @return The result of the query. + * + * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows + */ + public String simpleQueryForString() { + acquireReference(); + try { + B_setBindArgs(); + getPreparedStatement().execute(); + if(getResultSet().next()) + return getResultSet().getString(1); + else + throw new SQLiteDoneException(); + } catch (SQLException e) { + throw new SQLiteException("Failed to execute SQL statement!", e); + } finally { + releaseReference(); + } + } + + /** + * Executes a statement that returns a 1 by 1 table with a blob value. + * + * @return A read-only file descriptor for a copy of the blob value, or {@code null} + * if the value is null or could not be read for some reason. + * + * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows + */ + public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { + throw new UnsupportedOperationException("Not implemented!"); + } + + @Override + public String toString() { + return "SQLiteProgram: " + getSql(); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatementInfo.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatementInfo.java new file mode 100644 index 00000000..3edfdb05 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteStatementInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011 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 android.database.sqlite; + +/** + * Describes a SQLite statement. + * + * @hide + */ +public final class SQLiteStatementInfo { + /** + * The number of parameters that the statement has. + */ + public int numParameters; + + /** + * The names of all columns in the result set of the statement. + */ + public String[] columnNames; + + /** + * True if the statement is read-only. + */ + public boolean readOnly; +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTableLockedException.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTableLockedException.java new file mode 100644 index 00000000..8278df07 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTableLockedException.java @@ -0,0 +1,25 @@ +/* + * 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 android.database.sqlite; + +public class SQLiteTableLockedException extends SQLiteException { + public SQLiteTableLockedException() {} + + public SQLiteTableLockedException(String error) { + super(error); + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTransactionListener.java b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTransactionListener.java new file mode 100644 index 00000000..f03b5802 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SQLiteTransactionListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 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 android.database.sqlite; + +/** + * A listener for transaction events. + */ +public interface SQLiteTransactionListener { + /** + * Called immediately after the transaction begins. + */ + void onBegin(); + + /** + * Called immediately before commiting the transaction. + */ + void onCommit(); + + /** + * Called if the transaction is about to be rolled back. + */ + void onRollback(); +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/SqliteWrapper.java b/AndroidCompat/src/main/java/android/database/sqlite/SqliteWrapper.java new file mode 100644 index 00000000..ebe44617 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/SqliteWrapper.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * 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 android.database.sqlite; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; +import android.widget.Toast; + +/** + * @hide + */ + +public final class SqliteWrapper { + private static final String TAG = "SqliteWrapper"; + private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE + = "unable to open database file"; + + private SqliteWrapper() { + // Forbidden being instantiated. + } + + // FIXME: need to optimize this method. + private static boolean isLowMemory(SQLiteException e) { + return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE); + } + + public static void checkSQLiteException(Context context, SQLiteException e) { + if (isLowMemory(e)) { + //From: com.android.internal.R.string.low_memory, + Toast.makeText(context, "Device storage is full. Delete some files to free space.", + Toast.LENGTH_SHORT).show(); + } else { + throw e; + } + } + + public static Cursor query(Context context, ContentResolver resolver, Uri uri, + String[] projection, String selection, String[] selectionArgs, String sortOrder) { + try { + return resolver.query(uri, projection, selection, selectionArgs, sortOrder); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when query: ", e); + checkSQLiteException(context, e); + return null; + } + } + + public static boolean requery(Context context, Cursor cursor) { + try { + return cursor.requery(); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when requery: ", e); + checkSQLiteException(context, e); + return false; + } + } + public static int update(Context context, ContentResolver resolver, Uri uri, + ContentValues values, String where, String[] selectionArgs) { + try { + return resolver.update(uri, values, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when update: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + public static int delete(Context context, ContentResolver resolver, Uri uri, + String where, String[] selectionArgs) { + try { + return resolver.delete(uri, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when delete: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + public static Uri insert(Context context, ContentResolver resolver, + Uri uri, ContentValues values) { + try { + return resolver.insert(uri, values); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when insert: ", e); + checkSQLiteException(context, e); + return null; + } + } +} diff --git a/AndroidCompat/src/main/java/android/database/sqlite/package.html b/AndroidCompat/src/main/java/android/database/sqlite/package.html new file mode 100644 index 00000000..864a9bb3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/database/sqlite/package.html @@ -0,0 +1,49 @@ + + +Contains the SQLite database management +classes that an application would use to manage its own private database. +

+Applications use these classes to manage 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. If you are working +with data sent to you by a provider, you do not use these SQLite classes, but +instead use the generic {@link android.database} classes. + +

The Android SDK and Android emulators both include the +sqlite3 command-line +database tool. On your development machine, run the tool from the +platform-tools/ folder of your SDK. On the emulator, run the tool +with adb shell, for example, adb -e shell sqlite3. + +

The version of SQLite depends on the version of Android. See the following table: + + + + + + + + +
Android APISQLite Version
API 243.9
API 213.8
API 113.7
API 83.6
API 33.5
API 13.4
+ +

Some device manufacturers include different versions of SQLite on their devices. + There are two ways to programmatically determine the version number. + +

    +
  • If available, use the sqlite3 tool, for example: + adb -e shell sqlite3 --version.
  • +
  • Create and query an in-memory database as shown in the following code sample: +
    +    String query = "select sqlite_version() AS sqlite_version";
    +    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
    +    Cursor cursor = db.rawQuery(query, null);
    +    String sqliteVersion = "";
    +    if (cursor.moveToNext()) {
    +        sqliteVersion = cursor.getString(0);
    +    }
    +
  • +
+ + diff --git a/AndroidCompat/src/main/java/android/net/ConnectivityManager.java b/AndroidCompat/src/main/java/android/net/ConnectivityManager.java new file mode 100644 index 00000000..dbc69b8f --- /dev/null +++ b/AndroidCompat/src/main/java/android/net/ConnectivityManager.java @@ -0,0 +1,2924 @@ +/* + * 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 android.net; + +import android.annotation.*; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build.VERSION_CODES; +import android.os.*; +import android.util.SparseIntArray; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.util.HashMap; + +/** + * Class that answers queries about the state of network connectivity. It also + * notifies applications when network connectivity changes. + *

+ * The primary responsibilities of this class are to: + *

    + *
  1. Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
  2. + *
  3. Send broadcast intents when network connectivity changes
  4. + *
  5. Attempt to "fail over" to another network when connectivity to a network + * is lost
  6. + *
  7. Provide an API that allows applications to query the coarse-grained or fine-grained + * state of the available networks
  8. + *
  9. Provide an API that allows applications to request and select networks for their data + * traffic
  10. + *
+ */ +public class ConnectivityManager { + public static final ConnectivityManager INSTANCE = new ConnectivityManager(); + + private static final String TAG = "ConnectivityManager"; + + /** + * A change in network connectivity has occurred. A default connection has either + * been established or lost. The NetworkInfo for the affected network is + * sent as an extra; it should be consulted to see what kind of + * connectivity event occurred. + *

+ * Apps targeting Android 7.0 (API level 24) and higher do not receive this + * broadcast if they declare the broadcast receiver in their manifest. Apps + * will still receive broadcasts if they register their + * {@link android.content.BroadcastReceiver} with + * {@link android.content.Context#registerReceiver Context.registerReceiver()} + * and that context is still valid. + *

+ * If this is a connection that was the result of failing over from a + * disconnected network, then the FAILOVER_CONNECTION boolean extra is + * set to true. + *

+ * For a loss of connectivity, if the connectivity manager is attempting + * to connect (or has already connected) to another network, the + * NetworkInfo for the new network is also passed as an extra. This lets + * any receivers of the broadcast know that they should not necessarily + * tell the user that no data traffic will be possible. Instead, the + * receiver should expect another broadcast soon, indicating either that + * the failover attempt succeeded (and so there is still overall data + * connectivity), or that the failover attempt failed, meaning that all + * connectivity has been lost. + *

+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY + * is set to {@code true} if there are no connected networks at all. + * + * @deprecated apps should use the more versatile {@link #requestNetwork}, + * {@link #registerNetworkCallback} or {@link #registerDefaultNetworkCallback} + * functions instead for faster and more detailed updates about the network + * changes they care about. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + + /** + * A temporary hack until SUPL system can get off the legacy APIS. + * They do too many network requests and the long list of apps listening + * and waking due to the CONNECTIVITY_ACTION broadcast makes it expensive. + * Use this broadcast intent instead for SUPL requests. + * @hide + */ + public static final String CONNECTIVITY_ACTION_SUPL = "android.net.conn.CONNECTIVITY_CHANGE_SUPL"; + + /** + * The device has connected to a network that has presented a captive + * portal, which is blocking Internet connectivity. The user was presented + * with a notification that network sign in is required, + * and the user invoked the notification's action indicating they + * desire to sign in to the network. Apps handling this activity should + * facilitate signing in to the network. This action includes a + * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents + * the network presenting the captive portal; all communication with the + * captive portal must be done using this {@code Network} object. + *

+ * This activity includes a {@link CaptivePortal} extra named + * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different + * outcomes of the captive portal sign in to the system: + *

    + *
  • When the app handling this action believes the user has signed in to + * the network and the captive portal has been dismissed, the app should + * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can + * reevaluate the network. If reevaluation finds the network no longer + * subject to a captive portal, the network may become the default active + * data network.
  • + *
  • When the app handling this action believes the user explicitly wants + * to ignore the captive portal and the network, the app should call + * {@link CaptivePortal#ignoreNetwork}.
  • + *
+ */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + + /** + * The lookup key for a {@link NetworkInfo} object. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated Since {@link NetworkInfo} can vary based on UID, applications + * should always obtain network information through + * {@link #getActiveNetworkInfo()}. + * @see #EXTRA_NETWORK_TYPE + */ + @Deprecated + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + + /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * + * @see android.content.Intent#getIntExtra(String, int) + */ + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** + * The lookup key for a boolean that indicates whether a connect event + * is for a network to which the connectivity manager was failing over + * following a disconnect on another network. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_IS_FAILOVER = "isFailover"; + + /** + * The lookup key for a {@link NetworkInfo} object. This is supplied when + * there is another network that it may be possible to connect to. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + + /** + * The lookup key for a boolean that indicates whether there is a + * complete lack of connectivity, i.e., no network is available. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + + /** + * The lookup key for a string that indicates why an attempt to connect + * to a network failed. The string has no particular structure. It is + * intended to be used in notifications presented to users. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_REASON = "reason"; + + /** + * The lookup key for a string that provides optionally supplied + * extra information about the network state. The information + * may be passed up from the lower networking layers, and its + * meaning may be specific to a particular network type. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_EXTRA_INFO = "extraInfo"; + + /** + * The lookup key for an int that provides information about + * our connection to the internet at large. 0 indicates no connection, + * 100 indicates a great connection. Retrieve it with + * {@link android.content.Intent#getIntExtra(String, int)}. + * {@hide} + */ + public static final String EXTRA_INET_CONDITION = "inetCondition"; + + /** + * The lookup key for a {@link CaptivePortal} object included with the + * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal} + * object can be used to either indicate to the system that the captive + * portal has been dismissed or that the user does not want to pursue + * signing in to captive portal. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + + /** + * Key for passing a URL to the captive portal login activity. + */ + public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + + /** + * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive + * portal login activity. + * {@hide} + */ + public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + + /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** + * Broadcast action to indicate the change of data activity status + * (idle or active) on a network in a recent period. + * The network becomes active when data transmission is started, or + * idle if there is no data transmission for a period of time. + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE"; + + /** + * The lookup key for an enum that indicates the network device type on which this data activity + * change happens. + * {@hide} + */ + public static final String EXTRA_DEVICE_TYPE = "deviceType"; + + /** + * The lookup key for a boolean that indicates the device is active or not. {@code true} means + * it is actively sending or receiving data and {@code false} means it is idle. + * {@hide} + */ + public static final String EXTRA_IS_ACTIVE = "isActive"; + + /** + * The lookup key for a long that contains the timestamp (nanos) of the radio state change. + * {@hide} + */ + public static final String EXTRA_REALTIME_NS = "tsNanos"; + + /** + * Broadcast Action: The setting for background data usage has changed + * values. Use {@link #getBackgroundDataSetting()} to get the current value. + *

+ * If an application uses the network in the background, it should listen + * for this broadcast and stop using the background data if the value is + * {@code false}. + *

+ * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability + * of background data depends on several combined factors, and + * this broadcast is no longer sent. Instead, when background + * data is unavailable, {@link #getActiveNetworkInfo()} will now + * appear disconnected. During first boot after a platform + * upgrade, this broadcast will be sent once if + * {@link #getBackgroundDataSetting()} was {@code false} before + * the upgrade. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + + /** + * Broadcast Action: The network connection may not be good + * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and + * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify + * the network and it's condition. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String INET_CONDITION_ACTION = "android.net.conn.INET_CONDITION_ACTION"; + + /** + * Broadcast Action: A tetherable connection has come or gone. + * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER}, + * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY}, + * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and + * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate + * the current state of tethering. Each include a list of + * interface names in that state (may be empty). + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; + + /** + * @hide + * gives a String[] listing all the interfaces configured for + * tethering and currently available for tethering. + */ + public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + + /** + * @hide + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ + public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"; + + /** + * @hide + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ + public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + + /** + * @hide + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + + /** + * Broadcast Action: The captive portal tracker has finished its test. + * Sent only while running Setup Wizard, in lieu of showing a user + * notification. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED = "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"; + + /** + * The lookup key for a boolean that indicates whether a captive portal was detected. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * @hide + */ + public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal"; + + /** + * Action used to display a dialog that asks the user whether to connect to a network that is + * not validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + + /** + * Action used to display a dialog that asks the user whether to avoid a network that is no + * longer validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.conn.PROMPT_LOST_VALIDATION"; + + /** + * Invalid tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + public static final int TETHERING_INVALID = -1; + + /** + * Wifi tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_WIFI = 0; + + /** + * USB tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_USB = 1; + + /** + * Bluetooth tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_BLUETOOTH = 2; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + * @hide + */ + public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + * @hide + */ + public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + * @hide + */ + public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + + /** + * Tells the TetherService to run a provision check now. + * @hide + */ + public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + * @hide + */ + public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; + + /** + * The absence of a connection type. + * @hide + */ + public static final int TYPE_NONE = -1; + + /** + * A Mobile data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE = 0; + + /** + * A WIFI data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIFI = 1; + + /** + * An MMS-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Multimedia Messaging Service servers. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_MMS = 2; + + /** + * A SUPL-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Secure User Plane Location servers for help locating the device. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_SUPL = 3; + + /** + * A DUN-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is sometimes by the system when setting up an upstream connection + * for tethering so that the carrier is aware of DUN traffic. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_DUN = 4; + + /** + * A High Priority Mobile data connection. This network type uses the + * same network interface as {@link #TYPE_MOBILE} but the routing setup + * is different. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE_HIPRI = 5; + + /** + * A WiMAX data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIMAX = 6; + + /** + * A Bluetooth data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_BLUETOOTH = 7; + + /** + * Dummy data connection. This should not be used on shipping devices. + * @deprecated This is not used any more. + */ + @Deprecated + public static final int TYPE_DUMMY = 8; + + /** + * An Ethernet data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_ETHERNET = 9; + + /** + * Over the air Administration. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_MOBILE_FOTA = 10; + + /** + * IP Multimedia Subsystem. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_MOBILE_IMS = 11; + + /** + * Carrier Branded Services. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_MOBILE_CBS = 12; + + /** + * A Wi-Fi p2p connection. Only requesting processes will have access to + * the peers connected. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_WIFI_P2P = 13; + + /** + * The network to use for initially attaching to the network + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_MOBILE_IA = 14; + + /** + * Emergency PDN connection for emergency services. This + * may include IMS and MMS in emergency situations. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_MOBILE_EMERGENCY = 15; + + /** + * The network that uses proxy to achieve connectivity. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + public static final int TYPE_PROXY = 16; + + /** + * A virtual network using one or more native bearers. + * It may or may not be providing security services. + * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead. + */ + @Deprecated + public static final int TYPE_VPN = 17; + + /** {@hide} */ + public static final int MAX_RADIO_TYPE = 0; + + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = 0; + + private static final int MIN_NETWORK_TYPE = 0; + + /** + * If you want to set the default network preference,you can directly + * change the networkAttributes array in framework's config.xml. + * + * @deprecated Since we support so many more networks now, the single + * network default network preference can't really express + * the hierarchy. Instead, the default is defined by the + * networkAttributes in config.xml. You can determine + * the current value by calling {@link #getNetworkPreference()} + * from an App. + */ + @Deprecated + public static final int DEFAULT_NETWORK_PREFERENCE = 0; + + /** + * @hide + */ + public static final int REQUEST_ID_UNSET = 0; + + /** + * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered. + * This allows to distinguish when unregistering NetworkCallbacks those that were never + * registered from those that were already unregistered. + * @hide + */ + private static final NetworkRequest ALREADY_UNREGISTERED = null; + + /** + * A NetID indicating no Network is selected. + * Keep in sync with bionic/libc/dns/include/resolv_netid.h + * @hide + */ + public static final int NETID_UNSET = 0; + + /** + * Private DNS Mode values. + * + * The "private_dns_mode" global setting stores a String value which is + * expected to be one of the following. + */ + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OFF = "off"; + + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; + + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; + + /** + * The default Private DNS mode. + * + * This may change from release to release or may become dependent upon + * the capabilities of the underlying platform. + * + * @hide + */ + public static final String PRIVATE_DNS_DEFAULT_MODE = null; + + /** + * A kludge to facilitate static access where a Context pointer isn't available, like in the + * case of the static set/getProcessDefaultNetwork methods and from the Network class. + * TODO: Remove this after deprecating the static methods in favor of non-static methods or + * methods that take a Context argument. + */ + private static ConnectivityManager sInstance; + + /** + * Tests if a given integer represents a valid network type. + * @param networkType the type to be tested + * @return a boolean. {@code true} if the type is valid, else {@code false} + * @deprecated All APIs accepting a network type are deprecated. There should be no need to + * validate a network type. + */ + @Deprecated + public static boolean isNetworkTypeValid(int networkType) { + return true; + } + + /** + * Returns a non-localized string representing a given network type. + * ONLY used for debugging output. + * @param type the type needing naming + * @return a String for the given type, or a string version of the type ("87") + * if no name is known. + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + public static String getNetworkTypeName(int type) { + throw new RuntimeException("Stub!"); + } + + /** + * Checks if a given type uses the cellular data connection. + * This should be replaced in the future by a network property. + * @param networkType the type to check + * @return a boolean - {@code true} if uses cellular network, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + public static boolean isNetworkTypeMobile(int networkType) { + return false; + } + + /** + * Checks if the given network type is backed by a Wi-Fi radio. + * + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static boolean isNetworkTypeWifi(int networkType) { + return true; + } + + /** + * Specifies the preferred network type. When the device has more + * than one type available the preferred network type will be used. + * + * @param preference the network type to prefer over all others. It is + * unspecified what happens to the old preferred network in the + * overall ordering. + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + public void setNetworkPreference(int preference) { + } + + /** + * Retrieves the current preferred network type. + * + * @return an integer representing the preferred network type + * + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public int getNetworkPreference() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns details about the currently active default data network. When + * connected, this network is the default route for outgoing connections. + * You should always check {@link NetworkInfo#isConnected()} before initiating + * network traffic. This may return {@code null} when there is no default + * network. + * Note that if the default network is a VPN, this method will return the + * NetworkInfo for one of its underlying networks instead, or null if the + * VPN agent did not specify any. Apps interested in learning about VPNs + * should use {@link #getNetworkInfo(android.net.Network)} instead. + * + * @return a {@link NetworkInfo} object for the current default network + * or {@code null} if no default network is currently active + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public NetworkInfo getActiveNetworkInfo() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network. In the event that the current active default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network. + * + * @return a {@link Network} object for the current default network or + * {@code null} if no default network is currently active + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public Network getActiveNetwork() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network for a specific UID. In the event that the default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network for the UID. + * + * @return a {@link Network} object for the current default network for the + * given UID or {@code null} if no default network is currently active + * + * @hide + */ + public Network getActiveNetworkForUid(int uid) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) { + throw new RuntimeException("Stub!"); + } + + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

    + *
  • target {@link VERSION_CODES#N API 24} or above, and + *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + *
+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + throw new RuntimeException("Stub!"); + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + public String getAlwaysOnVpnPackageForUser(int userId) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns details about the currently active default data network + * for a given uid. This is for internal use only to avoid spying + * other apps. + * + * @return a {@link NetworkInfo} object for the current default network + * for the given uid or {@code null} if no default network is + * available for the specified uid. + * + * {@hide} + */ + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns connection status information about a particular + * network type. + * + * @param networkType integer specifying which networkType in + * which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network type or {@code null} if the type is not + * supported by the device. If {@code networkType} is + * TYPE_VPN and a VPN is active for the calling app, + * then this method will try to return one of the + * underlying networks for the VPN or null if the + * VPN agent didn't specify any. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public NetworkInfo getNetworkInfo(int networkType) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns connection status information about a particular + * Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public NetworkInfo getNetworkInfo(Network network) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns connection status information about all network + * types supported by the device. + * + * @return an array of {@link NetworkInfo} objects. Check each + * {@link NetworkInfo#getType} for which type each applies. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public NetworkInfo[] getAllNetworkInfo() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the {@link Network} object currently serving a given type, or + * null if the given type is not connected. + * + * @hide + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public Network getNetworkForType(int networkType) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns an array of all {@link Network} currently tracked by the + * framework. + * + * @return an array of {@link Network} objects. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public Network[] getAllNetworks() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns an array of {@link android.net.NetworkCapabilities} objects, representing + * the Networks that applications run by the given user will use by default. + * @hide + */ + public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the IP information for the current default network. + * + * @return a {@link LinkProperties} object describing the IP info + * for the current default network, or {@code null} if there + * is no current default network. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public LinkProperties getActiveLinkProperties() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the IP information for a given network type. + * + * @param networkType the network type of interest. + * @return a {@link LinkProperties} object describing the IP info + * for the given networkType, or {@code null} if there is + * no current default network. + * + * {@hide} + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks}, + * {@link #getNetworkInfo(android.net.Network)}, and + * {@link #getLinkProperties(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public LinkProperties getLinkProperties(int networkType) { + throw new RuntimeException("Stub!"); + } + + /** + * Get the {@link LinkProperties} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link LinkProperties} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public LinkProperties getLinkProperties(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public NetworkCapabilities getNetworkCapabilities(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Gets the URL that should be used for resolving whether a captive portal is present. + * 1. This URL should respond with a 204 response to a GET request to indicate no captive + * portal is present. + * 2. This URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. Captive portals cannot respond to HTTPS requests with redirects. + * + * @hide + */ + @SystemApi + public String getCaptivePortalServerUrl() { + throw new RuntimeException("Stub!"); + } + + /** + * Tells the underlying networking system that the caller wants to + * begin using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature to be used + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int startUsingNetworkFeature(int networkType, String feature) { + throw new RuntimeException("Stub!"); + } + + /** + * Tells the underlying networking system that the caller is finished + * using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature that is no longer needed + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #unregisterNetworkCallback(NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int stopUsingNetworkFeature(int networkType, String feature) { + throw new RuntimeException("Stub!"); + } + + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + throw new RuntimeException("Stub!"); + } + + /** + * Guess what the network request was trying to say so that the resulting + * network is accessible via the legacy (deprecated) API such as + * requestRouteToHost. + * + * This means we should try to be fairly precise about transport and + * capability but ignore things such as networkSpecifier. + * If the request has more than one transport or capability it doesn't + * match the old legacy requests (they selected only single transport/capability) + * so this function cannot map the request to a single legacy type and + * the resulting network will not be available to the legacy APIs. + * + * This code is only called from the requestNetwork API (L and above). + * + * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive + * because they wake up lots of apps - see http://b/23350688 . So we currently only + * do this for SUPL requests, which are the only ones that we know need it. If + * omitting these broadcasts causes unacceptable app breakage, then for backwards + * compatibility we can send them: + * + * if (targetSdkVersion < Build.VERSION_CODES.M) && // legacy API unsupported >= M + * targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP)) // requestNetwork not present < L + * + * TODO - This should be removed when the legacy APIs are removed. + */ + private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + throw new RuntimeException("Stub!"); + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + throw new RuntimeException("Stub!"); + } + + private static class LegacyRequest { + + NetworkCapabilities networkCapabilities; + + NetworkRequest networkRequest; + + int expireSequenceNumber; + + Network currentNetwork; + + int delay = -1; + + private void clearDnsBinding() { + throw new RuntimeException("Stub!"); + } + + NetworkCallback networkCallback = null; + } + + private static final HashMap sLegacyRequests = null; + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + throw new RuntimeException("Stub!"); + } + + private void renewRequestLocked(LegacyRequest l) { + throw new RuntimeException("Stub!"); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + throw new RuntimeException("Stub!"); + } + + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + throw new RuntimeException("Stub!"); + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + throw new RuntimeException("Stub!"); + } + + private boolean removeRequestForFeature(NetworkCapabilities netCap) { + throw new RuntimeException("Stub!"); + } + + private static final SparseIntArray sLegacyTypeToTransport = null; + + static { + } + + private static final SparseIntArray sLegacyTypeToCapability = null; + + static { + } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities + * instance suitable for registering a request or callback. Throws an + * IllegalArgumentException if no mapping from the legacy type to + * NetworkCapabilities is known. + * + * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest} + * to find the network instead. + * @hide + */ + public static NetworkCapabilities networkCapabilitiesForType(int type) { + throw new RuntimeException("Stub!"); + } + + /** @hide */ + public static class PacketKeepaliveCallback { + + /** The requested keepalive was successfully started. */ + public void onStarted() { + throw new RuntimeException("Stub!"); + } + + /** The keepalive was successfully stopped. */ + public void onStopped() { + throw new RuntimeException("Stub!"); + } + + /** An error occurred. */ + public void onError(int error) { + throw new RuntimeException("Stub!"); + } + } + + /** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive}, + * passing in a non-null callback. If the callback is successfully started, the callback's + * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called, + * specifying one of the {@code ERROR_*} constants in this class. + * + * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call + * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or + * {@link PacketKeepaliveCallback#onError} if an error occurred. + * + * @hide + */ + public class PacketKeepalive { + + private static final String TAG = "PacketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + /** The minimum interval in seconds between keepalive packet transmissions */ + public static final int MIN_INTERVAL = 10; + + private final Network mNetwork; + + private final PacketKeepaliveCallback mCallback; + + private final Looper mLooper; + + private final Messenger mMessenger; + + private volatile Integer mSlot; + + void stopLooper() { + throw new RuntimeException("Stub!"); + } + + public void stop() { + throw new RuntimeException("Stub!"); + } + + private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { + throw new RuntimeException("Stub!"); + } + } + + /** + * Starts an IPsec NAT-T keepalive packet with the specified parameters. + * + * @hide + */ + public PacketKeepalive startNattKeepalive(Network network, int intervalSeconds, PacketKeepaliveCallback callback, InetAddress srcAddr, int srcPort, InetAddress dstAddr) { + throw new RuntimeException("Stub!"); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public boolean requestRouteToHost(int networkType, int hostAddress) { + throw new RuntimeException("Stub!"); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * @hide + * @deprecated Deprecated in favor of the {@link #requestNetwork} and + * {@link #bindProcessToNetwork} API. + */ + @Deprecated + public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the value of the setting for background data usage. If false, + * applications should not use the network if the application is not in the + * foreground. Developers should respect this setting, and check the value + * of this before performing any background data operations. + *

+ * All applications that have background services that use the network + * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}. + *

+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of + * background data depends on several combined factors, and this method will + * always return {@code true}. Instead, when background data is unavailable, + * {@link #getActiveNetworkInfo()} will now appear disconnected. + * + * @return Whether background data usage is allowed. + */ + @Deprecated + public boolean getBackgroundDataSetting() { + throw new RuntimeException("Stub!"); + } + + /** + * Sets the value of the setting for background data usage. + * + * @param allowBackgroundData Whether an application should use data while + * it is in the background. + * + * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING + * @see #getBackgroundDataSetting() + * @hide + */ + @Deprecated + public void setBackgroundDataSetting(boolean allowBackgroundData) { + throw new RuntimeException("Stub!"); + } + + /** + * @hide + * @deprecated Talk to TelephonyManager directly + */ + @Deprecated + public boolean getMobileDataEnabled() { + throw new RuntimeException("Stub!"); + } + + /** + * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener} + * to find out when the system default network has gone in to a high power state. + */ + public interface OnNetworkActiveListener { + + /** + * Called on the main thread of the process to report that the current data network + * has become active, and it is now a good time to perform any pending network + * operations. Note that this listener only tells you when the network becomes + * active; if at any other time you want to know whether it is active (and thus okay + * to initiate network traffic), you can retrieve its instantaneous state with + * {@link ConnectivityManager#isDefaultNetworkActive}. + */ + void onNetworkActive(); + } + + /** + * Start listening to reports when the system's default data network is active, meaning it is + * a good time to perform network traffic. Use {@link #isDefaultNetworkActive()} + * to determine the current state of the system's default network after registering the + * listener. + *

+ * If the process default network has been set with + * {@link ConnectivityManager#bindProcessToNetwork} this function will not + * reflect the process's default, but the system default. + * + * @param l The listener to be told when the network is active. + */ + public void addDefaultNetworkActiveListener(final OnNetworkActiveListener l) { + throw new RuntimeException("Stub!"); + } + + /** + * Remove network active listener previously registered with + * {@link #addDefaultNetworkActiveListener}. + * + * @param l Previously registered listener. + */ + public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) { + throw new RuntimeException("Stub!"); + } + + /** + * Return whether the data network is currently active. An active network means that + * it is currently in a high power state for performing data transmission. On some + * types of networks, it may be expensive to move and stay in such a state, so it is + * more power efficient to batch network traffic together when the radio is already in + * this state. This method tells you whether right now is currently a good time to + * initiate network traffic, as the network is already active. + */ + public boolean isDefaultNetworkActive() { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public static ConnectivityManager from(Context context) { + throw new RuntimeException("Stub!"); + } + + /* TODO: These permissions checks don't belong in client-side code. Move them to + * services.jar, possibly in com.android.server.net. */ + /** {@hide} */ + public static final void enforceChangePermission(Context context) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public static final void enforceTetherChangePermission(Context context, String callingPkg) { + throw new RuntimeException("Stub!"); + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + static ConnectivityManager getInstanceOrNull() { + throw new RuntimeException("Stub!"); + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + private static ConnectivityManager getInstance() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the set of tetherable, available interfaces. This list is limited by + * device configuration and current interface existence. + * + * @return an array of 0 or more Strings of tetherable interface names. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetherableIfaces() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the set of tethered interfaces. + * + * @return an array of 0 or more String of currently tethered interface names. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetheredIfaces() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the set of interface names which attempted to tether but + * failed. Re-attempting to tether may cause them to reset to the Tethered + * state. Alternatively, causing the interface to be destroyed and recreated + * may cause them to reset to the available state. + * {@link ConnectivityManager#getLastTetherError} can be used to get more + * information on the cause of the errors. + * + * @return an array of 0 or more String indicating the interface names + * which failed to tether. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetheringErroredIfaces() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the set of tethered dhcp ranges. + * + * @return an array of 0 or more {@code String} of tethered dhcp ranges. + * {@hide} + */ + public String[] getTetheredDhcpRanges() { + throw new RuntimeException("Stub!"); + } + + /** + * Attempt to tether the named interface. This will setup a dhcp server + * on the interface, forward and NAT IP packets and forward DNS requests + * to the best active upstream network interface. Note that if no upstream + * IP network interface is available, dhcp will still run and traffic will be + * allowed between the tethered devices and this device, though upstream net + * access will of course fail until an upstream network interface becomes + * active. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to tether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + public int tether(String iface) { + throw new RuntimeException("Stub!"); + } + + /** + * Stop tethering the named interface. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to untether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + public int untether(String iface) { + throw new RuntimeException("Stub!"); + } + + /** + * Check if the device allows for tethering. It may be disabled via + * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or + * due to device configuration. + * + *

If this app does not have permission to use this API, it will always + * return false rather than throw an exception.

+ * + *

If the device has a hotspot provisioning app, the caller is required to hold the + * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.

+ * + *

Otherwise, this method requires the caller to hold the ability to modify system + * settings as determined by {@link android.provider.Settings.System#canWrite}.

+ * + * @return a boolean - {@code true} indicating Tethering is supported. + * + * {@hide} + */ + @SystemApi + public boolean isTetheringSupported() { + throw new RuntimeException("Stub!"); + } + + /** + * Callback for use with {@link #startTethering} to find out whether tethering succeeded. + * @hide + */ + @SystemApi + public abstract static class OnStartTetheringCallback { + + /** + * Called when tethering has been successfully started. + */ + public void onTetheringStarted() { + throw new RuntimeException("Stub!"); + } + + /** + * Called when starting tethering failed. + */ + public void onTetheringFailed() { + throw new RuntimeException("Stub!"); + } + } + + /** + * Convenient overload for + * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null + * handler to run on the current thread's {@link Looper}. + * @hide + */ + @SystemApi + public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback) { + throw new RuntimeException("Stub!"); + } + + /** + * Runs tether provisioning for the given type if needed and then starts tethering if + * the check succeeds. If no carrier provisioning is required for tethering, tethering is + * enabled immediately. If provisioning fails, tethering will not be enabled. It also + * schedules tether provisioning re-checks if appropriate. + * + * @param type The type of tethering to start. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there + * is one. This should be true the first time this function is called and also any time + * the user can see this UI. It gives users information from their carrier about the + * check failing and how they can sign up for tethering if possible. + * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller + * of the result of trying to tether. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @hide + */ + @SystemApi + public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if + * applicable. + * + * @param type The type of tethering to stop. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * @hide + */ + @SystemApi + public void stopTethering(int type) { + throw new RuntimeException("Stub!"); + } + + /** + * Get the list of regular expressions that define any tetherable + * USB network interfaces. If USB tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable usb interfaces. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetherableUsbRegexs() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the list of regular expressions that define any tetherable + * Wifi network interfaces. If Wifi tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable wifi interfaces. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetherableWifiRegexs() { + throw new RuntimeException("Stub!"); + } + + /** + * Get the list of regular expressions that define any tetherable + * Bluetooth network interfaces. If Bluetooth tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable bluetooth interfaces. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public String[] getTetherableBluetoothRegexs() { + throw new RuntimeException("Stub!"); + } + + /** + * Attempt to both alter the mode of USB and Tethering of USB. A + * utility method to deal with some of the complexity of USB - will + * attempt to switch to Rndis and subsequently tether the resulting + * interface on {@code true} or turn off tethering and switch off + * Rndis on {@code false}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param enable a boolean - {@code true} to enable tethering + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + public int setUsbTethering(boolean enable) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public static final int TETHER_ERROR_NO_ERROR = 0; + + /** {@hide} */ + public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; + + /** {@hide} */ + public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; + + /** {@hide} */ + public static final int TETHER_ERROR_UNSUPPORTED = 3; + + /** {@hide} */ + public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; + + /** {@hide} */ + public static final int TETHER_ERROR_MASTER_ERROR = 5; + + /** {@hide} */ + public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; + + /** {@hide} */ + public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; + + /** {@hide} */ + public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; + + /** {@hide} */ + public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; + + /** {@hide} */ + public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; + + /** {@hide} */ + public static final int TETHER_ERROR_PROVISION_FAILED = 11; + + /** + * Get a more detailed error code after a Tethering or Untethering + * request asynchronously failed. + * + * @param iface The name of the interface of interest + * @return error The error code of the last error tethering or untethering the named + * interface + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public int getLastTetherError(String iface) { + throw new RuntimeException("Stub!"); + } + + /** + * Report network connectivity status. This is currently used only + * to alter status bar UI. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#STATUS_BAR}. + * + * @param networkType The type of network you want to report on + * @param percentage The quality of the connection 0 is bad, 100 is good + * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead. + * {@hide} + */ + public void reportInetCondition(int networkType, int percentage) { + throw new RuntimeException("Stub!"); + } + + /** + * Report a problem network to the framework. This provides a hint to the system + * that there might be connectivity problems on this network and may cause + * the framework to re-evaluate network connectivity and/or switch to another + * network. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both + * working and non-working connectivity. + */ + @Deprecated + public void reportBadNetwork(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Report to the framework whether a network has working connectivity. + * This provides a hint to the system that a particular network is providing + * working connectivity or not. In response the framework may re-evaluate + * the network's connectivity and might take further action thereafter. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @param hasConnectivity {@code true} if the application was able to successfully access the + * Internet using {@code network} or {@code false} if not. + */ + public void reportNetworkConnectivity(Network network, boolean hasConnectivity) { + throw new RuntimeException("Stub!"); + } + + /** + * Set a network-independent global http proxy. This is not normally what you want + * for typical HTTP proxies - they are general network dependent. However if you're + * doing something unusual like general internal filtering this may be useful. On + * a private network where the proxy is not accessible, you may break HTTP using this. + * + * @param p A {@link ProxyInfo} object defining the new global + * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + * @hide + */ + public void setGlobalProxy(ProxyInfo p) { + throw new RuntimeException("Stub!"); + } + + /** + * Retrieve any network-independent global HTTP proxy. + * + * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} + * if no global HTTP proxy is set. + * @hide + */ + public ProxyInfo getGlobalProxy() { + throw new RuntimeException("Stub!"); + } + + /** + * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a + * network-specific HTTP proxy. If {@code network} is null, the + * network-specific proxy returned is the proxy of the default active + * network. + * + * @return {@link ProxyInfo} for the current global HTTP proxy, or if no + * global HTTP proxy is set, {@code ProxyInfo} for {@code network}, + * or when {@code network} is {@code null}, + * the {@code ProxyInfo} for the default active network. Returns + * {@code null} when no proxy applies or the caller doesn't have + * permission to use {@code network}. + * @hide + */ + public ProxyInfo getProxyForNetwork(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Get the current default HTTP proxy settings. If a global proxy is set it will be returned, + * otherwise if this process is bound to a {@link Network} using + * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise + * the default network's proxy is returned. + * + * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no + * HTTP proxy is active. + */ + public ProxyInfo getDefaultProxy() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns true if the hardware supports the given network type + * else it returns false. This doesn't indicate we have coverage + * or are authorized onto a network, just whether or not the + * hardware supports it. For example a GSM phone without a SIM + * should still return {@code true} for mobile data, but a wifi only + * tablet would return {@code false}. + * + * @param networkType The network type we'd like to check + * @return {@code true} if supported, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public boolean isNetworkSupported(int networkType) { + return true; + } + + /** + * Returns if the currently active data network is metered. A network is + * classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public boolean isActiveNetworkMetered() { + return false; + } + + /** + * If the LockdownVpn mechanism is enabled, updates the vpn + * with a reload of its profile. + * + * @return a boolean with {@code} indicating success + * + *

This method can only be called by the system UID + * {@hide} + */ + public boolean updateLockdownVpn() { + throw new RuntimeException("Stub!"); + } + + /** + * Check mobile provisioning. + * + * @param suggestedTimeOutMs, timeout in milliseconds + * + * @return time out that will be used, maybe less that suggestedTimeOutMs + * -1 if an error. + * + * {@hide} + */ + public int checkMobileProvisioning(int suggestedTimeOutMs) { + throw new RuntimeException("Stub!"); + } + + /** + * Get the mobile provisioning url. + * {@hide} + */ + public String getMobileProvisioningUrl() { + throw new RuntimeException("Stub!"); + } + + /** + * Set sign in error notification to visible or in visible + * + * {@hide} + * @deprecated Doesn't properly deal with multiple connected networks of the same type. + */ + @Deprecated + public void setProvisioningNotificationVisible(boolean visible, int networkType, String action) { + throw new RuntimeException("Stub!"); + } + + /** + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + * @hide + */ + public void setAirplaneMode(boolean enable) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public void registerNetworkFactory(Messenger messenger, String name) { + throw new RuntimeException("Stub!"); + } + + /** {@hide} */ + public void unregisterNetworkFactory(Messenger messenger) { + throw new RuntimeException("Stub!"); + } + + /** + * Base class for {@code NetworkRequest} callbacks. Used for notifications about network + * changes. Should be extended by applications wanting notifications. + * + * A {@code NetworkCallback} is registered by calling + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)}, + * or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is + * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}. + * A {@code NetworkCallback} should be registered at most once at any time. + * A {@code NetworkCallback} that has been unregistered can be registered again. + */ + public static class NetworkCallback { + + /** + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + *

+ * Most applications should not act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. + * + * @hide + */ + public void onPreCheck(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the framework connects and has declared a new network ready for use. + * This callback may be called more than once if the {@link Network} that is + * satisfying the request changes. + * + * @param network The {@link Network} of the satisfying network. + * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. + * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @hide + */ + public void onAvailable(Network network, NetworkCapabilities networkCapabilities, LinkProperties linkProperties) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the framework connects and has declared a new network ready for use. + * This callback may be called more than once if the {@link Network} that is + * satisfying the request changes. This will always immediately be followed by a + * call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a + * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}. + * + * @param network The {@link Network} of the satisfying network. + */ + public void onAvailable(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the network is about to be disconnected. Often paired with an + * {@link NetworkCallback#onAvailable} call with the new replacement network + * for graceful handover. This may not be called if we have a hard loss + * (loss without warning). This may be followed by either a + * {@link NetworkCallback#onLost} call or a + * {@link NetworkCallback#onAvailable} call for this network depending + * on whether we lose or regain it. + * + * @param network The {@link Network} that is about to be disconnected. + * @param maxMsToLive The time in ms the framework will attempt to keep the + * network connected. Note that the network may suffer a + * hard loss at any time. + */ + public void onLosing(Network network, int maxMsToLive) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the framework has a hard loss of the network or when the + * graceful failure ends. + * + * @param network The {@link Network} lost. + */ + public void onLost(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Called if no network is found in the timeout time specified in + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not + * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)} + * without timeout. When this callback is invoked the associated + * {@link NetworkRequest} will have already been removed and released, as if + * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. + */ + public void onUnavailable() { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the network the framework connected to for this request + * changes capabilities but still satisfies the stated need. + * + * @param network The {@link Network} whose capabilities have changed. + * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this + * network. + */ + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the network the framework connected to for this request + * changes {@link LinkProperties}. + * + * @param network The {@link Network} whose link properties have changed. + * @param linkProperties The new {@link LinkProperties} for this network. + */ + public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the network the framework connected to for this request + * goes into {@link NetworkInfo.State#SUSPENDED}. + * This generally means that while the TCP connections are still live, + * temporarily network data fails to transfer. Specifically this is used + * on cellular networks to mask temporary outages when driving through + * a tunnel, etc. + * @hide + */ + public void onNetworkSuspended(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Called when the network the framework connected to for this request + * returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be + * preceded by a matching {@link NetworkCallback#onNetworkSuspended} call. + * @hide + */ + public void onNetworkResumed(Network network) { + throw new RuntimeException("Stub!"); + } + + private NetworkRequest networkRequest; + } + + /** + * Constant error codes used by ConnectivityService to communicate about failures and errors + * across a Binder boundary. + * @hide + */ + public interface Errors { + + int TOO_MANY_REQUESTS = 1; + } + + /** @hide */ + public static class TooManyRequestsException extends RuntimeException { + } + + private static final int BASE = 0; + + /** @hide */ + public static final int CALLBACK_PRECHECK = 0; + + /** @hide */ + public static final int CALLBACK_AVAILABLE = 0; + + /** @hide arg1 = TTL */ + public static final int CALLBACK_LOSING = 0; + + /** @hide */ + public static final int CALLBACK_LOST = 0; + + /** @hide */ + public static final int CALLBACK_UNAVAIL = 0; + + /** @hide */ + public static final int CALLBACK_CAP_CHANGED = 0; + + /** @hide */ + public static final int CALLBACK_IP_CHANGED = 0; + + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = 0; + + /** @hide */ + public static final int CALLBACK_SUSPENDED = 0; + + /** @hide */ + public static final int CALLBACK_RESUMED = 0; + + /** @hide */ + public static String getCallbackName(int whichCallback) { + throw new RuntimeException("Stub!"); + } + + private class CallbackHandler extends Handler { + + private static final String TAG = "ConnectivityManager.CallbackHandler"; + + private static final boolean DBG = false; + + CallbackHandler(Looper looper) { + throw new RuntimeException("Stub!"); + } + + CallbackHandler(Handler handler) { + throw new RuntimeException("Stub!"); + } + + @Override + public void handleMessage(Message message) { + throw new RuntimeException("Stub!"); + } + + private T getObject(Message msg, Class c) { + throw new RuntimeException("Stub!"); + } + } + + private CallbackHandler getDefaultHandler() { + throw new RuntimeException("Stub!"); + } + + private static final HashMap sCallbacks = null; + + private static CallbackHandler sCallbackHandler; + + private static final int LISTEN = 1; + + private static final int REQUEST = 2; + + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, int timeoutMs, int action, int legacyType, CallbackHandler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Helper function to request a network with a particular legacy type. + * + * This is temporarily public @hide so it can be called by system code that uses the + * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for + * instead network notifications. + * + * TODO: update said system code to rely on NetworkCallbacks and make this method private. + * + * @hide + */ + public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, int timeoutMs, int legacyType, Handler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This {@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A + * version of the method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}. + * Status of the request can be followed by listening to the various + * callbacks described in {@link NetworkCallback}. The {@link Network} + * can be used to direct traffic to the network. + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfing a request with these capabilities. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * The callback is invoked on the default internal Handler. + * @throws IllegalArgumentException if {@code request} specifies any mutable + * {@code NetworkCapabilities}. + */ + public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) { + throw new RuntimeException("Stub!"); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This {@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A + * version of the method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}. + * Status of the request can be followed by listening to the various + * callbacks described in {@link NetworkCallback}. The {@link Network} + * can be used to direct traffic to the network. + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws IllegalArgumentException if {@code request} specifies any mutable + * {@code NetworkCapabilities}. + */ + public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, Handler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + *

Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + */ + public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, int timeoutMs) { + throw new RuntimeException("Stub!"); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the version without timeout, but if a suitable + * network is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + *

Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable} is called. + */ + public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, Handler handler, int timeoutMs) { + throw new RuntimeException("Stub!"); + } + + /** + * The lookup key for a {@link Network} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + *

+ * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)} + * then you must get a ConnectivityManager instance before doing so. + */ + public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + + /** + * The lookup key for a {@link NetworkRequest} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. It is important to create a new, + * {@link NetworkCallback} based request before completing the processing of the + * Intent to reserve the network or it will be released shortly after the Intent + * is processed. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #releaseNetworkRequest(android.app.PendingIntent)}. + *

It is presently unsupported to request a network with either + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws IllegalArgumentException if {@code request} contains either + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}. + */ + public void requestNetwork(NetworkRequest request, PendingIntent operation) { + throw new RuntimeException("Stub!"); + } + + /** + * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} + *

+ * This method has the same behavior as + * {@link #unregisterNetworkCallback(android.app.PendingIntent)} with respect to + * releasing network resources and disconnecting. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the + * corresponding NetworkRequest you'd like to remove. Cannot be null. + */ + public void releaseNetworkRequest(PendingIntent operation) { + throw new RuntimeException("Stub!"); + } + + private static void checkPendingIntentNotNull(PendingIntent intent) { + throw new RuntimeException("Stub!"); + } + + private static void checkCallbackNotNull(NetworkCallback callback) { + throw new RuntimeException("Stub!"); + } + + private static void checkTimeout(int timeoutMs) { + throw new RuntimeException("Stub!"); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * The callback is invoked on the default internal Handler. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) { + throw new RuntimeException("Stub!"); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback, Handler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Registers a PendingIntent to be sent when a network is available which satisfies the given + * {@link NetworkRequest}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #unregisterNetworkCallback(android.app.PendingIntent)}. + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) { + throw new RuntimeException("Stub!"); + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * The callback is invoked on the default internal Handler. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(NetworkCallback networkCallback) { + throw new RuntimeException("Stub!"); + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) { + throw new RuntimeException("Stub!"); + } + + /** + * Requests bandwidth update for a given {@link Network} and returns whether the update request + * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying + * network connection for updated bandwidth information. The caller will be notified via + * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this + * method assumes that the caller has previously called + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} to listen for network + * changes. + * + * @param network {@link Network} specifying which network you're interested. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean requestBandwidthUpdate(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Unregisters a {@code NetworkCallback} and possibly releases networks originating from + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls. + * If the given {@code NetworkCallback} had previously been used with + * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request + * will be disconnected. + * + * Notifications that would have triggered that {@code NetworkCallback} will immediately stop + * triggering it as soon as this call returns. + * + * @param networkCallback The {@link NetworkCallback} used when making the request. + */ + public void unregisterNetworkCallback(NetworkCallback networkCallback) { + throw new RuntimeException("Stub!"); + } + + /** + * Unregisters a callback previously registered via + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * Cannot be null. + */ + public void unregisterNetworkCallback(PendingIntent operation) { + throw new RuntimeException("Stub!"); + } + + /** + * Informs the system whether it should switch to {@code network} regardless of whether it is + * validated or not. If {@code accept} is true, and the network was explicitly selected by the + * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become + * the system default network regardless of any other network that's currently connected. If + * {@code always} is true, then the choice is remembered, so that the next time the user + * connects to this network, the system will switch to it. + * + * @param network The network to accept. + * @param accept Whether to accept the network even if unvalidated. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + throw new RuntimeException("Stub!"); + } + + /** + * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is + * only meaningful if the system is configured not to penalize such networks, e.g., if the + * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code + * NETWORK_AVOID_BAD_WIFI setting is unset}. + * + * @param network The network to accept. + * + * @hide + */ + public void setAvoidUnvalidated(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Requests that the system open the captive portal app on the specified network. + * + * @param network The network to log into. + * + * @hide + */ + public void startCaptivePortalApp(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * It is acceptable to briefly use multipath data to provide seamless connectivity for + * time-sensitive user-facing operations when the system default network is temporarily + * unresponsive. The amount of data should be limited (less than one megabyte for every call to + * this method), and the operation should be infrequent to ensure that data usage is limited. + * + * An example of such an operation might be a time-sensitive foreground activity, such as a + * voice command, that the user is performing while walking out of range of a Wi-Fi network. + */ + public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0; + + /** + * It is acceptable to use small amounts of multipath data on an ongoing basis to provide + * a backup channel for traffic that is primarily going over another network. + * + * An example might be maintaining backup connections to peers or servers for the purpose of + * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic + * on backup paths should be negligible compared to the traffic on the main path. + */ + public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1; + + /** + * It is acceptable to use metered data to improve network latency and performance. + */ + public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2; + + /** + * Return value to use for unmetered networks. On such networks we currently set all the flags + * to true. + * @hide + */ + public static final int MULTIPATH_PREFERENCE_UNMETERED = 0; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { MULTIPATH_PREFERENCE_HANDOVER, MULTIPATH_PREFERENCE_RELIABILITY, MULTIPATH_PREFERENCE_PERFORMANCE }) + public @interface MultipathPreference { + } + + /** + * Provides a hint to the calling application on whether it is desirable to use the + * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.) + * for multipath data transfer on this network when it is not the system default network. + * Applications desiring to use multipath network protocols should call this method before + * each such operation. + * + * @param network The network on which the application desires to use multipath data. + * If {@code null}, this method will return the a preference that will generally + * apply to metered networks. + * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @MultipathPreference + public int getMultipathPreference(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Resets all connectivity manager settings back to factory defaults. + * @hide + */ + public void factoryReset() { + throw new RuntimeException("Stub!"); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code bindProcessToNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean bindProcessToNetwork(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code setProcessDefaultNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @deprecated This function can throw {@link IllegalStateException}. Use + * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork} + * is a direct replacement. + */ + @Deprecated + public static boolean setProcessDefaultNetwork(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + */ + public Network getBoundNetworkForProcess() { + throw new RuntimeException("Stub!"); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + * @deprecated Using this function can lead to other functions throwing + * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead. + * {@code getBoundNetworkForProcess} is a direct replacement. + */ + @Deprecated + public static Network getProcessDefaultNetwork() { + throw new RuntimeException("Stub!"); + } + + private void unsupportedStartingFrom(int version) { + throw new RuntimeException("Stub!"); + } + + // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature, + // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException. + // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and + // remove these exemptions. Note that this check is not secure, and apps can still access these + // functions by accessing ConnectivityService directly. However, it should be clear that doing + // so is unsupported and may break in the future. http://b/22728205 + private void checkLegacyRoutingApiAccess() { + throw new RuntimeException("Stub!"); + } + + /** + * Binds host resolutions performed by this process to {@code network}. + * {@link #bindProcessToNetwork} takes precedence over this setting. + * + * @param network The {@link Network} to bind host resolutions from the current process to, or + * {@code null} to clear the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @hide + * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}. + */ + @Deprecated + public static boolean setProcessDefaultNetworkForHostResolution(Network network) { + throw new RuntimeException("Stub!"); + } + + /** + * Device is not restricting metered network activity while application is running on + * background. + */ + public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; + + /** + * Device is restricting metered network activity while application is running on background, + * but application is allowed to bypass it. + *

+ * In this state, application should take action to mitigate metered network access. + * For example, a music streaming application should switch to a low-bandwidth bitrate. + */ + public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; + + /** + * Device is restricting metered network activity while application is running on background. + *

+ * In this state, application should not try to use the network while running on background, + * because it would be denied. + */ + public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; + + /** + * A change in the background metered network activity restriction has occurred. + *

+ * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction + * applies to them. + *

+ * This is only sent to registered receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, value = { RESTRICT_BACKGROUND_STATUS_DISABLED, RESTRICT_BACKGROUND_STATUS_WHITELISTED, RESTRICT_BACKGROUND_STATUS_ENABLED }) + public @interface RestrictBackgroundStatus { + } + + /** + * Determines if the calling application is subject to metered network restrictions while + * running on background. + * + * @return {@link #RESTRICT_BACKGROUND_STATUS_DISABLED}, + * {@link #RESTRICT_BACKGROUND_STATUS_ENABLED}, + * or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED} + */ + @RestrictBackgroundStatus + public int getRestrictBackgroundStatus() { + throw new RuntimeException("Stub!"); + } +} diff --git a/AndroidCompat/src/main/java/android/net/NetworkInfo.java b/AndroidCompat/src/main/java/android/net/NetworkInfo.java new file mode 100644 index 00000000..91bac1fd --- /dev/null +++ b/AndroidCompat/src/main/java/android/net/NetworkInfo.java @@ -0,0 +1,496 @@ +/* + * 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 android.net; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.VisibleForTesting; + +import java.util.EnumMap; +/** + * Describes the status of a network interface. + *

Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents + * the current network connection. + */ +public class NetworkInfo implements Parcelable { + /** + * Coarse-grained network state. This is probably what most applications should + * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}. + * The mapping between the two is as follows: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Detailed stateCoarse-grained state
IDLEDISCONNECTED
SCANNINGDISCONNECTED
CONNECTINGCONNECTING
AUTHENTICATINGCONNECTING
OBTAINING_IPADDRCONNECTING
VERIFYING_POOR_LINKCONNECTING
CAPTIVE_PORTAL_CHECKCONNECTING
CONNECTEDCONNECTED
SUSPENDEDSUSPENDED
DISCONNECTINGDISCONNECTING
DISCONNECTEDDISCONNECTED
FAILEDDISCONNECTED
BLOCKEDDISCONNECTED
+ */ + public enum State { + CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN + } + /** + * The fine-grained state of a network connection. This level of detail + * is probably of interest to few applications. Most should use + * {@link android.net.NetworkInfo.State State} instead. + */ + public enum DetailedState { + /** Ready to start data connection setup. */ + IDLE, + /** Searching for an available access point. */ + SCANNING, + /** Currently setting up data connection. */ + CONNECTING, + /** Network link established, performing authentication. */ + AUTHENTICATING, + /** Awaiting response from DHCP server in order to assign IP address information. */ + OBTAINING_IPADDR, + /** IP traffic should be available. */ + CONNECTED, + /** IP traffic is suspended */ + SUSPENDED, + /** Currently tearing down data connection. */ + DISCONNECTING, + /** IP traffic not available. */ + DISCONNECTED, + /** Attempt to connect failed. */ + FAILED, + /** Access to this network is blocked. */ + BLOCKED, + /** Link has poor connectivity. */ + VERIFYING_POOR_LINK, + /** Checking if network is a captive portal */ + CAPTIVE_PORTAL_CHECK + } + /** + * This is the map described in the Javadoc comment above. The positions + * of the elements of the array must correspond to the ordinal values + * of DetailedState. + */ + private static final EnumMap stateMap = + new EnumMap(DetailedState.class); + static { + stateMap.put(DetailedState.IDLE, State.DISCONNECTED); + stateMap.put(DetailedState.SCANNING, State.DISCONNECTED); + stateMap.put(DetailedState.CONNECTING, State.CONNECTING); + stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); + stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); + stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING); + stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING); + stateMap.put(DetailedState.CONNECTED, State.CONNECTED); + stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); + stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); + stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED); + stateMap.put(DetailedState.FAILED, State.DISCONNECTED); + stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED); + } + private int mNetworkType; + private int mSubtype; + private String mTypeName; + private String mSubtypeName; + private State mState; + private DetailedState mDetailedState; + private String mReason; + private String mExtraInfo; + private boolean mIsFailover; + private boolean mIsAvailable; + private boolean mIsRoaming; + /** + * @hide + */ + public NetworkInfo(int type, int subtype, String typeName, String subtypeName) { + if (!ConnectivityManager.isNetworkTypeValid(type) + && type != ConnectivityManager.TYPE_NONE) { + throw new IllegalArgumentException("Invalid network type: " + type); + } + mNetworkType = type; + mSubtype = subtype; + mTypeName = typeName; + mSubtypeName = subtypeName; + setDetailedState(DetailedState.IDLE, null, null); + mState = State.UNKNOWN; + } + /** {@hide} */ + public NetworkInfo(NetworkInfo source) { + if (source != null) { + synchronized (source) { + mNetworkType = source.mNetworkType; + mSubtype = source.mSubtype; + mTypeName = source.mTypeName; + mSubtypeName = source.mSubtypeName; + mState = source.mState; + mDetailedState = source.mDetailedState; + mReason = source.mReason; + mExtraInfo = source.mExtraInfo; + mIsFailover = source.mIsFailover; + mIsAvailable = source.mIsAvailable; + mIsRoaming = source.mIsRoaming; + } + } + } + /** + * Reports the type of network to which the + * info in this {@code NetworkInfo} pertains. + * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link + * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link + * ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other + * types defined by {@link ConnectivityManager}. + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public int getType() { + synchronized (this) { + return mNetworkType; + } + } + /** + * @deprecated Use {@link NetworkCapabilities} instead + * @hide + */ + @Deprecated + public void setType(int type) { + synchronized (this) { + mNetworkType = type; + } + } + /** + * Return a network-type-specific integer describing the subtype + * of the network. + * @return the network subtype + */ + public int getSubtype() { + synchronized (this) { + return mSubtype; + } + } + /** + * @hide + */ + public void setSubtype(int subtype, String subtypeName) { + synchronized (this) { + mSubtype = subtype; + mSubtypeName = subtypeName; + } + } + /** + * Return a human-readable name describe the type of the network, + * for example "WIFI" or "MOBILE". + * @return the name of the network type + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public String getTypeName() { + synchronized (this) { + return mTypeName; + } + } + /** + * Return a human-readable name describing the subtype of the network. + * @return the name of the network subtype + */ + public String getSubtypeName() { + synchronized (this) { + return mSubtypeName; + } + } + /** + * Indicates whether network connectivity exists or is in the process + * of being established. This is good for applications that need to + * do anything related to the network other than read or write data. + * For the latter, call {@link #isConnected()} instead, which guarantees + * that the network is fully usable. + * @return {@code true} if network connectivity exists or is in the process + * of being established, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isConnectedOrConnecting() { + synchronized (this) { + return mState == State.CONNECTED || mState == State.CONNECTING; + } + } + /** + * Indicates whether network connectivity exists and it is possible to establish + * connections and pass data. + *

Always call this before attempting to perform data transactions. + * @return {@code true} if network connectivity exists, {@code false} otherwise. + */ + public boolean isConnected() { + synchronized (this) { + return mState == State.CONNECTED; + } + } + /** + * Indicates whether network connectivity is possible. A network is unavailable + * when a persistent or semi-persistent condition prevents the possibility + * of connecting to that network. Examples include + *

    + *
  • The device is out of the coverage area for any network of this type.
  • + *
  • The device is on a network other than the home network (i.e., roaming), and + * data roaming has been disabled.
  • + *
  • The device's radio is turned off, e.g., because airplane mode is enabled.
  • + *
+ * Since Android L, this always returns {@code true}, because the system only + * returns info for available networks. + * @return {@code true} if the network is available, {@code false} otherwise + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isAvailable() { + synchronized (this) { + return mIsAvailable; + } + } + /** + * Sets if the network is available, ie, if the connectivity is possible. + * @param isAvailable the new availability value. + * @deprecated Use {@link NetworkCapabilities} instead + * + * @hide + */ + @Deprecated + public void setIsAvailable(boolean isAvailable) { + synchronized (this) { + mIsAvailable = isAvailable; + } + } + /** + * Indicates whether the current attempt to connect to the network + * resulted from the ConnectivityManager trying to fail over to this + * network following a disconnect from another network. + * @return {@code true} if this is a failover attempt, {@code false} + * otherwise. + * @deprecated This field is not populated in recent Android releases, + * and does not make a lot of sense in a multi-network world. + */ + @Deprecated + public boolean isFailover() { + synchronized (this) { + return mIsFailover; + } + } + /** + * Set the failover boolean. + * @param isFailover {@code true} to mark the current connection attempt + * as a failover. + * @deprecated This hasn't been set in any recent Android release. + * @hide + */ + @Deprecated + public void setFailover(boolean isFailover) { + synchronized (this) { + mIsFailover = isFailover; + } + } + /** + * Indicates whether the device is currently roaming on this network. When + * {@code true}, it suggests that use of data on this network may incur + * extra costs. + * + * @return {@code true} if roaming is in effect, {@code false} otherwise. + * @deprecated Callers should switch to checking + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} + * instead, since that handles more complex situations, such as + * VPNs. + */ + @Deprecated + public boolean isRoaming() { + synchronized (this) { + return mIsRoaming; + } + } + /** + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} instead. + * {@hide} + */ + @VisibleForTesting + @Deprecated + public void setRoaming(boolean isRoaming) { + synchronized (this) { + mIsRoaming = isRoaming; + } + } + /** + * Reports the current coarse-grained state of the network. + * @return the coarse-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public State getState() { + synchronized (this) { + return mState; + } + } + /** + * Reports the current fine-grained state of the network. + * @return the fine-grained state + */ + public DetailedState getDetailedState() { + synchronized (this) { + return mDetailedState; + } + } + /** + * Sets the fine-grained state of the network. + * @param detailedState the {@link DetailedState}. + * @param reason a {@code String} indicating the reason for the state change, + * if one was supplied. May be {@code null}. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) { + synchronized (this) { + this.mDetailedState = detailedState; + this.mState = stateMap.get(detailedState); + this.mReason = reason; + this.mExtraInfo = extraInfo; + } + } + /** + * Set the extraInfo field. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @hide + */ + public void setExtraInfo(String extraInfo) { + synchronized (this) { + this.mExtraInfo = extraInfo; + } + } + /** + * Report the reason an attempt to establish connectivity failed, + * if one is available. + * @return the reason for failure, or null if not available + * @deprecated This method does not have a consistent contract that could make it useful + * to callers. + */ + public String getReason() { + synchronized (this) { + return mReason; + } + } + /** + * Report the extra information about the network state, if any was + * provided by the lower networking layers. + * @return the extra information, or null if not available + */ + public String getExtraInfo() { + synchronized (this) { + return mExtraInfo; + } + } + @Override + public String toString() { + synchronized (this) { + StringBuilder builder = new StringBuilder("["); + builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()). + append("], state: ").append(mState).append("/").append(mDetailedState). + append(", reason: ").append(mReason == null ? "(unspecified)" : mReason). + append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo). + append(", failover: ").append(mIsFailover). + append(", available: ").append(mIsAvailable). + append(", roaming: ").append(mIsRoaming). + append("]"); + return builder.toString(); + } + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel dest, int flags) { + synchronized (this) { + dest.writeInt(mNetworkType); + dest.writeInt(mSubtype); + dest.writeString(mTypeName); + dest.writeString(mSubtypeName); + dest.writeString(mState.name()); + dest.writeString(mDetailedState.name()); + dest.writeInt(mIsFailover ? 1 : 0); + dest.writeInt(mIsAvailable ? 1 : 0); + dest.writeInt(mIsRoaming ? 1 : 0); + dest.writeString(mReason); + dest.writeString(mExtraInfo); + } + } + public static final Creator CREATOR = new Creator() { + @Override + public NetworkInfo createFromParcel(Parcel in) { + int netType = in.readInt(); + int subtype = in.readInt(); + String typeName = in.readString(); + String subtypeName = in.readString(); + NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName); + netInfo.mState = State.valueOf(in.readString()); + netInfo.mDetailedState = DetailedState.valueOf(in.readString()); + netInfo.mIsFailover = in.readInt() != 0; + netInfo.mIsAvailable = in.readInt() != 0; + netInfo.mIsRoaming = in.readInt() != 0; + netInfo.mReason = in.readString(); + netInfo.mExtraInfo = in.readString(); + return netInfo; + } + @Override + public NetworkInfo[] newArray(int size) { + return new NetworkInfo[size]; + } + }; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/net/ParseException.java b/AndroidCompat/src/main/java/android/net/ParseException.java new file mode 100644 index 00000000..1ba3b080 --- /dev/null +++ b/AndroidCompat/src/main/java/android/net/ParseException.java @@ -0,0 +1,26 @@ +/* + * 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 android.net; +/** + * Thrown when parsing a URL fails. + */ +// See non-public class {@link WebAddress}. +public class ParseException extends RuntimeException { + public String response; + ParseException(String response) { + this.response = response; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/net/Uri.java b/AndroidCompat/src/main/java/android/net/Uri.java new file mode 100644 index 00000000..cc6081b1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/net/Uri.java @@ -0,0 +1,1992 @@ +/* + * 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. + * + * [Modified for TachiWeb] + */ +package android.net; + +import android.os.Environment; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.StrictMode; +import android.util.Log; +import libcore.net.UriCodec; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * Immutable URI reference. A URI reference includes a URI and a fragment, the + * component of the URI following a '#'. Builds and parses URI references + * which conform to + * RFC 2396. + * + *

In the interest of performance, this class performs little to no + * validation. Behavior is undefined for invalid input. This class is very + * forgiving--in the face of invalid input, it will return garbage + * rather than throw an exception unless otherwise specified. + */ +public abstract class Uri implements Parcelable, Comparable { + /* + This class aims to do as little up front work as possible. To accomplish + that, we vary the implementation depending on what the user passes in. + For example, we have one implementation if the user passes in a + URI string (StringUri) and another if the user passes in the + individual components (OpaqueUri). + *Concurrency notes*: Like any truly immutable object, this class is safe + for concurrent use. This class uses a caching pattern in some places where + it doesn't use volatile or synchronized. This is safe to do with ints + because getting or setting an int is atomic. It's safe to do with a String + because the internal fields are final and the memory model guarantees other + threads won't see a partially initialized instance. We are not guaranteed + that some threads will immediately see changes from other threads on + certain platforms, but we don't mind if those threads reconstruct the + cached result. As a result, we get thread safe caching with no concurrency + overhead, which means the most common case, access from a single thread, + is as fast as possible. + From the Java Language spec.: + "17.5 Final Field Semantics + ... when the object is seen by another thread, that thread will always + see the correctly constructed version of that object's final fields. + It will also see versions of any object or array referenced by + those final fields that are at least as up-to-date as the final fields + are." + In that same vein, all non-transient fields within Uri + implementations should be final and immutable so as to ensure true + immutability for clients even when they don't use proper concurrency + control. + For reference, from RFC 2396: + "4.3. Parsing a URI Reference + A URI reference is typically parsed according to the four main + components and fragment identifier in order to determine what + components are present and whether the reference is relative or + absolute. The individual components are then parsed for their + subparts and, if not opaque, to verify their validity. + Although the BNF defines what is allowed in each component, it is + ambiguous in terms of differentiating between an authority component + and a path component that begins with two slash characters. The + greedy algorithm is used for disambiguation: the left-most matching + rule soaks up as much of the URI reference string as it is capable of + matching. In other words, the authority component wins." + The "four main components" of a hierarchical URI consist of + ://? + */ + /** Log tag. */ + private static final String LOG = Uri.class.getSimpleName(); + /** + * NOTE: EMPTY accesses this field during its own initialization, so this + * field *must* be initialized first, or else EMPTY will see a null value! + * + * Placeholder for strings which haven't been cached. This enables us + * to cache null. We intentionally create a new String instance so we can + * compare its identity and there is no chance we will confuse it with + * user data. + */ + @SuppressWarnings("RedundantStringConstructorCall") + private static final String NOT_CACHED = new String("NOT CACHED"); + /** + * The empty URI, equivalent to "". + */ + public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL, + PathPart.EMPTY, Part.NULL, Part.NULL); + /** + * Prevents external subclassing. + */ + private Uri() {} + /** + * Returns true if this URI is hierarchical like "http://google.com". + * Absolute URIs are hierarchical if the scheme-specific part starts with + * a '/'. Relative URIs are always hierarchical. + */ + public abstract boolean isHierarchical(); + /** + * Returns true if this URI is opaque like "mailto:nobody@google.com". The + * scheme-specific part of an opaque URI cannot start with a '/'. + */ + public boolean isOpaque() { + return !isHierarchical(); + } + /** + * Returns true if this URI is relative, i.e. if it doesn't contain an + * explicit scheme. + * + * @return true if this URI is relative, false if it's absolute + */ + public abstract boolean isRelative(); + /** + * Returns true if this URI is absolute, i.e. if it contains an + * explicit scheme. + * + * @return true if this URI is absolute, false if it's relative + */ + public boolean isAbsolute() { + return !isRelative(); + } + /** + * Gets the scheme of this URI. Example: "http" + * + * @return the scheme or null if this is a relative URI + */ + public abstract String getScheme(); + /** + * Gets the scheme-specific part of this URI, i.e. everything between + * the scheme separator ':' and the fragment separator '#'. If this is a + * relative URI, this method returns the entire URI. Decodes escaped octets. + * + *

Example: "//www.google.com/search?q=android" + * + * @return the decoded scheme-specific-part + */ + public abstract String getSchemeSpecificPart(); + /** + * Gets the scheme-specific part of this URI, i.e. everything between + * the scheme separator ':' and the fragment separator '#'. If this is a + * relative URI, this method returns the entire URI. Leaves escaped octets + * intact. + * + *

Example: "//www.google.com/search?q=android" + * + * @return the decoded scheme-specific-part + */ + public abstract String getEncodedSchemeSpecificPart(); + /** + * Gets the decoded authority part of this URI. For + * server addresses, the authority is structured as follows: + * {@code [ userinfo '@' ] host [ ':' port ]} + * + *

Examples: "google.com", "bob@google.com:80" + * + * @return the authority for this URI or null if not present + */ + public abstract String getAuthority(); + /** + * Gets the encoded authority part of this URI. For + * server addresses, the authority is structured as follows: + * {@code [ userinfo '@' ] host [ ':' port ]} + * + *

Examples: "google.com", "bob@google.com:80" + * + * @return the authority for this URI or null if not present + */ + public abstract String getEncodedAuthority(); + /** + * Gets the decoded user information from the authority. + * For example, if the authority is "nobody@google.com", this method will + * return "nobody". + * + * @return the user info for this URI or null if not present + */ + public abstract String getUserInfo(); + /** + * Gets the encoded user information from the authority. + * For example, if the authority is "nobody@google.com", this method will + * return "nobody". + * + * @return the user info for this URI or null if not present + */ + public abstract String getEncodedUserInfo(); + /** + * Gets the encoded host from the authority for this URI. For example, + * if the authority is "bob@google.com", this method will return + * "google.com". + * + * @return the host for this URI or null if not present + */ + public abstract String getHost(); + /** + * Gets the port from the authority for this URI. For example, + * if the authority is "google.com:80", this method will return 80. + * + * @return the port for this URI or -1 if invalid or not present + */ + public abstract int getPort(); + /** + * Gets the decoded path. + * + * @return the decoded path, or null if this is not a hierarchical URI + * (like "mailto:nobody@google.com") or the URI is invalid + */ + public abstract String getPath(); + /** + * Gets the encoded path. + * + * @return the encoded path, or null if this is not a hierarchical URI + * (like "mailto:nobody@google.com") or the URI is invalid + */ + public abstract String getEncodedPath(); + /** + * Gets the decoded query component from this URI. The query comes after + * the query separator ('?') and before the fragment separator ('#'). This + * method would return "q=android" for + * "http://www.google.com/search?q=android". + * + * @return the decoded query or null if there isn't one + */ + public abstract String getQuery(); + /** + * Gets the encoded query component from this URI. The query comes after + * the query separator ('?') and before the fragment separator ('#'). This + * method would return "q=android" for + * "http://www.google.com/search?q=android". + * + * @return the encoded query or null if there isn't one + */ + public abstract String getEncodedQuery(); + /** + * Gets the decoded fragment part of this URI, everything after the '#'. + * + * @return the decoded fragment or null if there isn't one + */ + public abstract String getFragment(); + /** + * Gets the encoded fragment part of this URI, everything after the '#'. + * + * @return the encoded fragment or null if there isn't one + */ + public abstract String getEncodedFragment(); + /** + * Gets the decoded path segments. + * + * @return decoded path segments, each without a leading or trailing '/' + */ + public abstract List getPathSegments(); + /** + * Gets the decoded last segment in the path. + * + * @return the decoded last segment or null if the path is empty + */ + public abstract String getLastPathSegment(); + /** + * Compares this Uri to another object for equality. Returns true if the + * encoded string representations of this Uri and the given Uri are + * equal. Case counts. Paths are not normalized. If one Uri specifies a + * default port explicitly and the other leaves it implicit, they will not + * be considered equal. + */ + public boolean equals(Object o) { + if (!(o instanceof Uri)) { + return false; + } + Uri other = (Uri) o; + return toString().equals(other.toString()); + } + /** + * Hashes the encoded string represention of this Uri consistently with + * {@link #equals(Object)}. + */ + public int hashCode() { + return toString().hashCode(); + } + /** + * Compares the string representation of this Uri with that of + * another. + */ + public int compareTo(Uri other) { + return toString().compareTo(other.toString()); + } + /** + * Returns the encoded string representation of this URI. + * Example: "http://google.com/" + */ + public abstract String toString(); + /** + * Return a string representation of the URI that is safe to print + * to logs and other places where PII should be avoided. + * @hide + */ + public String toSafeString() { + String scheme = getScheme(); + String ssp = getSchemeSpecificPart(); + if (scheme != null) { + if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") + || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") + || scheme.equalsIgnoreCase("mailto")) { + StringBuilder builder = new StringBuilder(64); + builder.append(scheme); + builder.append(':'); + if (ssp != null) { + for (int i=0; i". Encodes path characters with the exception of + * '/'. + * + *

Example: "file:///tmp/android.txt" + * + * @throws NullPointerException if file is null + * @return a Uri for the given file + */ + public static Uri fromFile(File file) { + if (file == null) { + throw new NullPointerException("file"); + } + PathPart path = PathPart.fromDecoded(file.getAbsolutePath()); + return new HierarchicalUri( + "file", Part.EMPTY, path, Part.NULL, Part.NULL); + } + /** + * An implementation which wraps a String URI. This URI can be opaque or + * hierarchical, but we extend AbstractHierarchicalUri in case we need + * the hierarchical functionality. + */ + private static class StringUri extends AbstractHierarchicalUri { + /** Used in parcelling. */ + static final int TYPE_ID = 1; + /** URI string representation. */ + private final String uriString; + private StringUri(String uriString) { + if (uriString == null) { + throw new NullPointerException("uriString"); + } + this.uriString = uriString; + } + static Uri readFrom(Parcel parcel) { + return new StringUri(parcel.readString()); + } + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(TYPE_ID); + parcel.writeString(uriString); + } + /** Cached scheme separator index. */ + private volatile int cachedSsi = NOT_CALCULATED; + /** Finds the first ':'. Returns -1 if none found. */ + private int findSchemeSeparator() { + return cachedSsi == NOT_CALCULATED + ? cachedSsi = uriString.indexOf(':') + : cachedSsi; + } + /** Cached fragment separator index. */ + private volatile int cachedFsi = NOT_CALCULATED; + /** Finds the first '#'. Returns -1 if none found. */ + private int findFragmentSeparator() { + return cachedFsi == NOT_CALCULATED + ? cachedFsi = uriString.indexOf('#', findSchemeSeparator()) + : cachedFsi; + } + public boolean isHierarchical() { + int ssi = findSchemeSeparator(); + if (ssi == NOT_FOUND) { + // All relative URIs are hierarchical. + return true; + } + if (uriString.length() == ssi + 1) { + // No ssp. + return false; + } + // If the ssp starts with a '/', this is hierarchical. + return uriString.charAt(ssi + 1) == '/'; + } + public boolean isRelative() { + // Note: We return true if the index is 0 + return findSchemeSeparator() == NOT_FOUND; + } + private volatile String scheme = NOT_CACHED; + public String getScheme() { + @SuppressWarnings("StringEquality") + boolean cached = (scheme != NOT_CACHED); + return cached ? scheme : (scheme = parseScheme()); + } + private String parseScheme() { + int ssi = findSchemeSeparator(); + return ssi == NOT_FOUND ? null : uriString.substring(0, ssi); + } + private Part ssp; + private Part getSsp() { + return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp; + } + public String getEncodedSchemeSpecificPart() { + return getSsp().getEncoded(); + } + public String getSchemeSpecificPart() { + return getSsp().getDecoded(); + } + private String parseSsp() { + int ssi = findSchemeSeparator(); + int fsi = findFragmentSeparator(); + // Return everything between ssi and fsi. + return fsi == NOT_FOUND + ? uriString.substring(ssi + 1) + : uriString.substring(ssi + 1, fsi); + } + private Part authority; + private Part getAuthorityPart() { + if (authority == null) { + String encodedAuthority + = parseAuthority(this.uriString, findSchemeSeparator()); + return authority = Part.fromEncoded(encodedAuthority); + } + return authority; + } + public String getEncodedAuthority() { + return getAuthorityPart().getEncoded(); + } + public String getAuthority() { + return getAuthorityPart().getDecoded(); + } + private PathPart path; + private PathPart getPathPart() { + return path == null + ? path = PathPart.fromEncoded(parsePath()) + : path; + } + public String getPath() { + return getPathPart().getDecoded(); + } + public String getEncodedPath() { + return getPathPart().getEncoded(); + } + public List getPathSegments() { + return getPathPart().getPathSegments(); + } + private String parsePath() { + String uriString = this.uriString; + int ssi = findSchemeSeparator(); + // If the URI is absolute. + if (ssi > -1) { + // Is there anything after the ':'? + boolean schemeOnly = ssi + 1 == uriString.length(); + if (schemeOnly) { + // Opaque URI. + return null; + } + // A '/' after the ':' means this is hierarchical. + if (uriString.charAt(ssi + 1) != '/') { + // Opaque URI. + return null; + } + } else { + // All relative URIs are hierarchical. + } + return parsePath(uriString, ssi); + } + private Part query; + private Part getQueryPart() { + return query == null + ? query = Part.fromEncoded(parseQuery()) : query; + } + public String getEncodedQuery() { + return getQueryPart().getEncoded(); + } + private String parseQuery() { + // It doesn't make sense to cache this index. We only ever + // calculate it once. + int qsi = uriString.indexOf('?', findSchemeSeparator()); + if (qsi == NOT_FOUND) { + return null; + } + int fsi = findFragmentSeparator(); + if (fsi == NOT_FOUND) { + return uriString.substring(qsi + 1); + } + if (fsi < qsi) { + // Invalid. + return null; + } + return uriString.substring(qsi + 1, fsi); + } + public String getQuery() { + return getQueryPart().getDecoded(); + } + private Part fragment; + private Part getFragmentPart() { + return fragment == null + ? fragment = Part.fromEncoded(parseFragment()) : fragment; + } + public String getEncodedFragment() { + return getFragmentPart().getEncoded(); + } + private String parseFragment() { + int fsi = findFragmentSeparator(); + return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1); + } + public String getFragment() { + return getFragmentPart().getDecoded(); + } + public String toString() { + return uriString; + } + /** + * Parses an authority out of the given URI string. + * + * @param uriString URI string + * @param ssi scheme separator index, -1 for a relative URI + * + * @return the authority or null if none is found + */ + static String parseAuthority(String uriString, int ssi) { + int length = uriString.length(); + // If "//" follows the scheme separator, we have an authority. + if (length > ssi + 2 + && uriString.charAt(ssi + 1) == '/' + && uriString.charAt(ssi + 2) == '/') { + // We have an authority. + // Look for the start of the path, query, or fragment, or the + // end of the string. + int end = ssi + 3; + LOOP: while (end < length) { + switch (uriString.charAt(end)) { + case '/': // Start of path + case '?': // Start of query + case '#': // Start of fragment + break LOOP; + } + end++; + } + return uriString.substring(ssi + 3, end); + } else { + return null; + } + } + /** + * Parses a path out of this given URI string. + * + * @param uriString URI string + * @param ssi scheme separator index, -1 for a relative URI + * + * @return the path + */ + static String parsePath(String uriString, int ssi) { + int length = uriString.length(); + // Find start of path. + int pathStart; + if (length > ssi + 2 + && uriString.charAt(ssi + 1) == '/' + && uriString.charAt(ssi + 2) == '/') { + // Skip over authority to path. + pathStart = ssi + 3; + LOOP: while (pathStart < length) { + switch (uriString.charAt(pathStart)) { + case '?': // Start of query + case '#': // Start of fragment + return ""; // Empty path. + case '/': // Start of path! + break LOOP; + } + pathStart++; + } + } else { + // Path starts immediately after scheme separator. + pathStart = ssi + 1; + } + // Find end of path. + int pathEnd = pathStart; + LOOP: while (pathEnd < length) { + switch (uriString.charAt(pathEnd)) { + case '?': // Start of query + case '#': // Start of fragment + break LOOP; + } + pathEnd++; + } + return uriString.substring(pathStart, pathEnd); + } + public Builder buildUpon() { + if (isHierarchical()) { + return new Builder() + .scheme(getScheme()) + .authority(getAuthorityPart()) + .path(getPathPart()) + .query(getQueryPart()) + .fragment(getFragmentPart()); + } else { + return new Builder() + .scheme(getScheme()) + .opaquePart(getSsp()) + .fragment(getFragmentPart()); + } + } + } + /** + * Creates an opaque Uri from the given components. Encodes the ssp + * which means this method cannot be used to create hierarchical URIs. + * + * @param scheme of the URI + * @param ssp scheme-specific-part, everything between the + * scheme separator (':') and the fragment separator ('#'), which will + * get encoded + * @param fragment fragment, everything after the '#', null if undefined, + * will get encoded + * + * @throws NullPointerException if scheme or ssp is null + * @return Uri composed of the given scheme, ssp, and fragment + * + * @see Builder if you don't want the ssp and fragment to be encoded + */ + public static Uri fromParts(String scheme, String ssp, + String fragment) { + if (scheme == null) { + throw new NullPointerException("scheme"); + } + if (ssp == null) { + throw new NullPointerException("ssp"); + } + return new OpaqueUri(scheme, Part.fromDecoded(ssp), + Part.fromDecoded(fragment)); + } + /** + * Opaque URI. + */ + private static class OpaqueUri extends Uri { + /** Used in parcelling. */ + static final int TYPE_ID = 2; + private final String scheme; + private final Part ssp; + private final Part fragment; + private OpaqueUri(String scheme, Part ssp, Part fragment) { + this.scheme = scheme; + this.ssp = ssp; + this.fragment = fragment == null ? Part.NULL : fragment; + } + static Uri readFrom(Parcel parcel) { + return new OpaqueUri( + parcel.readString(), + Part.readFrom(parcel), + Part.readFrom(parcel) + ); + } + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(TYPE_ID); + parcel.writeString(scheme); + ssp.writeTo(parcel); + fragment.writeTo(parcel); + } + public boolean isHierarchical() { + return false; + } + public boolean isRelative() { + return scheme == null; + } + public String getScheme() { + return this.scheme; + } + public String getEncodedSchemeSpecificPart() { + return ssp.getEncoded(); + } + public String getSchemeSpecificPart() { + return ssp.getDecoded(); + } + public String getAuthority() { + return null; + } + public String getEncodedAuthority() { + return null; + } + public String getPath() { + return null; + } + public String getEncodedPath() { + return null; + } + public String getQuery() { + return null; + } + public String getEncodedQuery() { + return null; + } + public String getFragment() { + return fragment.getDecoded(); + } + public String getEncodedFragment() { + return fragment.getEncoded(); + } + public List getPathSegments() { + return Collections.emptyList(); + } + public String getLastPathSegment() { + return null; + } + public String getUserInfo() { + return null; + } + public String getEncodedUserInfo() { + return null; + } + public String getHost() { + return null; + } + public int getPort() { + return -1; + } + private volatile String cachedString = NOT_CACHED; + public String toString() { + @SuppressWarnings("StringEquality") + boolean cached = cachedString != NOT_CACHED; + if (cached) { + return cachedString; + } + StringBuilder sb = new StringBuilder(); + sb.append(scheme).append(':'); + sb.append(getEncodedSchemeSpecificPart()); + if (!fragment.isEmpty()) { + sb.append('#').append(fragment.getEncoded()); + } + return cachedString = sb.toString(); + } + public Builder buildUpon() { + return new Builder() + .scheme(this.scheme) + .opaquePart(this.ssp) + .fragment(this.fragment); + } + } + /** + * Wrapper for path segment array. + */ + static class PathSegments extends AbstractList + implements RandomAccess { + static final PathSegments EMPTY = new PathSegments(null, 0); + final String[] segments; + final int size; + PathSegments(String[] segments, int size) { + this.segments = segments; + this.size = size; + } + public String get(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException(); + } + return segments[index]; + } + public int size() { + return this.size; + } + } + /** + * Builds PathSegments. + */ + static class PathSegmentsBuilder { + String[] segments; + int size = 0; + void add(String segment) { + if (segments == null) { + segments = new String[4]; + } else if (size + 1 == segments.length) { + String[] expanded = new String[segments.length * 2]; + System.arraycopy(segments, 0, expanded, 0, segments.length); + segments = expanded; + } + segments[size++] = segment; + } + PathSegments build() { + if (segments == null) { + return PathSegments.EMPTY; + } + try { + return new PathSegments(segments, size); + } finally { + // Makes sure this doesn't get reused. + segments = null; + } + } + } + /** + * Support for hierarchical URIs. + */ + private abstract static class AbstractHierarchicalUri extends Uri { + public String getLastPathSegment() { + // TODO: If we haven't parsed all of the segments already, just + // grab the last one directly so we only allocate one string. + List segments = getPathSegments(); + int size = segments.size(); + if (size == 0) { + return null; + } + return segments.get(size - 1); + } + private Part userInfo; + private Part getUserInfoPart() { + return userInfo == null + ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo; + } + public final String getEncodedUserInfo() { + return getUserInfoPart().getEncoded(); + } + private String parseUserInfo() { + String authority = getEncodedAuthority(); + if (authority == null) { + return null; + } + int end = authority.indexOf('@'); + return end == NOT_FOUND ? null : authority.substring(0, end); + } + public String getUserInfo() { + return getUserInfoPart().getDecoded(); + } + private volatile String host = NOT_CACHED; + public String getHost() { + @SuppressWarnings("StringEquality") + boolean cached = (host != NOT_CACHED); + return cached ? host + : (host = parseHost()); + } + private String parseHost() { + String authority = getEncodedAuthority(); + if (authority == null) { + return null; + } + // Parse out user info and then port. + int userInfoSeparator = authority.indexOf('@'); + int portSeparator = authority.indexOf(':', userInfoSeparator); + String encodedHost = portSeparator == NOT_FOUND + ? authority.substring(userInfoSeparator + 1) + : authority.substring(userInfoSeparator + 1, portSeparator); + return decode(encodedHost); + } + private volatile int port = NOT_CALCULATED; + public int getPort() { + return port == NOT_CALCULATED + ? port = parsePort() + : port; + } + private int parsePort() { + String authority = getEncodedAuthority(); + if (authority == null) { + return -1; + } + // Make sure we look for the port separtor *after* the user info + // separator. We have URLs with a ':' in the user info. + int userInfoSeparator = authority.indexOf('@'); + int portSeparator = authority.indexOf(':', userInfoSeparator); + if (portSeparator == NOT_FOUND) { + return -1; + } + String portString = decode(authority.substring(portSeparator + 1)); + try { + return Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.w(LOG, "Error parsing port string.", e); + return -1; + } + } + } + /** + * Hierarchical Uri. + */ + private static class HierarchicalUri extends AbstractHierarchicalUri { + /** Used in parcelling. */ + static final int TYPE_ID = 3; + private final String scheme; // can be null + private final Part authority; + private final PathPart path; + private final Part query; + private final Part fragment; + private HierarchicalUri(String scheme, Part authority, PathPart path, + Part query, Part fragment) { + this.scheme = scheme; + this.authority = Part.nonNull(authority); + this.path = path == null ? PathPart.NULL : path; + this.query = Part.nonNull(query); + this.fragment = Part.nonNull(fragment); + } + static Uri readFrom(Parcel parcel) { + return new HierarchicalUri( + parcel.readString(), + Part.readFrom(parcel), + PathPart.readFrom(parcel), + Part.readFrom(parcel), + Part.readFrom(parcel) + ); + } + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(TYPE_ID); + parcel.writeString(scheme); + authority.writeTo(parcel); + path.writeTo(parcel); + query.writeTo(parcel); + fragment.writeTo(parcel); + } + public boolean isHierarchical() { + return true; + } + public boolean isRelative() { + return scheme == null; + } + public String getScheme() { + return scheme; + } + private Part ssp; + private Part getSsp() { + return ssp == null + ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp; + } + public String getEncodedSchemeSpecificPart() { + return getSsp().getEncoded(); + } + public String getSchemeSpecificPart() { + return getSsp().getDecoded(); + } + /** + * Creates the encoded scheme-specific part from its sub parts. + */ + private String makeSchemeSpecificPart() { + StringBuilder builder = new StringBuilder(); + appendSspTo(builder); + return builder.toString(); + } + private void appendSspTo(StringBuilder builder) { + String encodedAuthority = authority.getEncoded(); + if (encodedAuthority != null) { + // Even if the authority is "", we still want to append "//". + builder.append("//").append(encodedAuthority); + } + String encodedPath = path.getEncoded(); + if (encodedPath != null) { + builder.append(encodedPath); + } + if (!query.isEmpty()) { + builder.append('?').append(query.getEncoded()); + } + } + public String getAuthority() { + return this.authority.getDecoded(); + } + public String getEncodedAuthority() { + return this.authority.getEncoded(); + } + public String getEncodedPath() { + return this.path.getEncoded(); + } + public String getPath() { + return this.path.getDecoded(); + } + public String getQuery() { + return this.query.getDecoded(); + } + public String getEncodedQuery() { + return this.query.getEncoded(); + } + public String getFragment() { + return this.fragment.getDecoded(); + } + public String getEncodedFragment() { + return this.fragment.getEncoded(); + } + public List getPathSegments() { + return this.path.getPathSegments(); + } + private volatile String uriString = NOT_CACHED; + @Override + public String toString() { + @SuppressWarnings("StringEquality") + boolean cached = (uriString != NOT_CACHED); + return cached ? uriString + : (uriString = makeUriString()); + } + private String makeUriString() { + StringBuilder builder = new StringBuilder(); + if (scheme != null) { + builder.append(scheme).append(':'); + } + appendSspTo(builder); + if (!fragment.isEmpty()) { + builder.append('#').append(fragment.getEncoded()); + } + return builder.toString(); + } + public Builder buildUpon() { + return new Builder() + .scheme(scheme) + .authority(authority) + .path(path) + .query(query) + .fragment(fragment); + } + } + /** + * Helper class for building or manipulating URI references. Not safe for + * concurrent use. + * + *

An absolute hierarchical URI reference follows the pattern: + * {@code ://?#} + * + *

Relative URI references (which are always hierarchical) follow one + * of two patterns: {@code ?#} + * or {@code //?#} + * + *

An opaque URI follows this pattern: + * {@code :#} + * + *

Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI. + */ + public static final class Builder { + private String scheme; + private Part opaquePart; + private Part authority; + private PathPart path; + private Part query; + private Part fragment; + /** + * Constructs a new Builder. + */ + public Builder() {} + /** + * Sets the scheme. + * + * @param scheme name or {@code null} if this is a relative Uri + */ + public Builder scheme(String scheme) { + this.scheme = scheme; + return this; + } + Builder opaquePart(Part opaquePart) { + this.opaquePart = opaquePart; + return this; + } + /** + * Encodes and sets the given opaque scheme-specific-part. + * + * @param opaquePart decoded opaque part + */ + public Builder opaquePart(String opaquePart) { + return opaquePart(Part.fromDecoded(opaquePart)); + } + /** + * Sets the previously encoded opaque scheme-specific-part. + * + * @param opaquePart encoded opaque part + */ + public Builder encodedOpaquePart(String opaquePart) { + return opaquePart(Part.fromEncoded(opaquePart)); + } + Builder authority(Part authority) { + // This URI will be hierarchical. + this.opaquePart = null; + this.authority = authority; + return this; + } + /** + * Encodes and sets the authority. + */ + public Builder authority(String authority) { + return authority(Part.fromDecoded(authority)); + } + /** + * Sets the previously encoded authority. + */ + public Builder encodedAuthority(String authority) { + return authority(Part.fromEncoded(authority)); + } + Builder path(PathPart path) { + // This URI will be hierarchical. + this.opaquePart = null; + this.path = path; + return this; + } + /** + * Sets the path. Leaves '/' characters intact but encodes others as + * necessary. + * + *

If the path is not null and doesn't start with a '/', and if + * you specify a scheme and/or authority, the builder will prepend the + * given path with a '/'. + */ + public Builder path(String path) { + return path(PathPart.fromDecoded(path)); + } + /** + * Sets the previously encoded path. + * + *

If the path is not null and doesn't start with a '/', and if + * you specify a scheme and/or authority, the builder will prepend the + * given path with a '/'. + */ + public Builder encodedPath(String path) { + return path(PathPart.fromEncoded(path)); + } + /** + * Encodes the given segment and appends it to the path. + */ + public Builder appendPath(String newSegment) { + return path(PathPart.appendDecodedSegment(path, newSegment)); + } + /** + * Appends the given segment to the path. + */ + public Builder appendEncodedPath(String newSegment) { + return path(PathPart.appendEncodedSegment(path, newSegment)); + } + Builder query(Part query) { + // This URI will be hierarchical. + this.opaquePart = null; + this.query = query; + return this; + } + /** + * Encodes and sets the query. + */ + public Builder query(String query) { + return query(Part.fromDecoded(query)); + } + /** + * Sets the previously encoded query. + */ + public Builder encodedQuery(String query) { + return query(Part.fromEncoded(query)); + } + Builder fragment(Part fragment) { + this.fragment = fragment; + return this; + } + /** + * Encodes and sets the fragment. + */ + public Builder fragment(String fragment) { + return fragment(Part.fromDecoded(fragment)); + } + /** + * Sets the previously encoded fragment. + */ + public Builder encodedFragment(String fragment) { + return fragment(Part.fromEncoded(fragment)); + } + /** + * Encodes the key and value and then appends the parameter to the + * query string. + * + * @param key which will be encoded + * @param value which will be encoded + */ + public Builder appendQueryParameter(String key, String value) { + // This URI will be hierarchical. + this.opaquePart = null; + String encodedParameter = encode(key, null) + "=" + + encode(value, null); + if (query == null) { + query = Part.fromEncoded(encodedParameter); + return this; + } + String oldQuery = query.getEncoded(); + if (oldQuery == null || oldQuery.length() == 0) { + query = Part.fromEncoded(encodedParameter); + } else { + query = Part.fromEncoded(oldQuery + "&" + encodedParameter); + } + return this; + } + /** + * Clears the the previously set query. + */ + public Builder clearQuery() { + return query((Part) null); + } + /** + * Constructs a Uri with the current attributes. + * + * @throws UnsupportedOperationException if the URI is opaque and the + * scheme is null + */ + public Uri build() { + if (opaquePart != null) { + if (this.scheme == null) { + throw new UnsupportedOperationException( + "An opaque URI must have a scheme."); + } + return new OpaqueUri(scheme, opaquePart, fragment); + } else { + // Hierarchical URIs should not return null for getPath(). + PathPart path = this.path; + if (path == null || path == PathPart.NULL) { + path = PathPart.EMPTY; + } else { + // If we have a scheme and/or authority, the path must + // be absolute. Prepend it with a '/' if necessary. + if (hasSchemeOrAuthority()) { + path = PathPart.makeAbsolute(path); + } + } + return new HierarchicalUri( + scheme, authority, path, query, fragment); + } + } + private boolean hasSchemeOrAuthority() { + return scheme != null + || (authority != null && authority != Part.NULL); + } + @Override + public String toString() { + return build().toString(); + } + } + /** + * Returns a set of the unique names of all query parameters. Iterating + * over the set will return the names in order of their first occurrence. + * + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * + * @return a set of decoded names + */ + public Set getQueryParameterNames() { + if (isOpaque()) { + throw new UnsupportedOperationException(NOT_HIERARCHICAL); + } + String query = getEncodedQuery(); + if (query == null) { + return Collections.emptySet(); + } + Set names = new LinkedHashSet(); + int start = 0; + do { + int next = query.indexOf('&', start); + int end = (next == -1) ? query.length() : next; + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + String name = query.substring(start, separator); + names.add(decode(name)); + // Move start to end of name. + start = end + 1; + } while (start < query.length()); + return Collections.unmodifiableSet(names); + } + /** + * Searches the query string for parameter values with the given key. + * + * @param key which will be encoded + * + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * @throws NullPointerException if key is null + * @return a list of decoded values + */ + public List getQueryParameters(String key) { + if (isOpaque()) { + throw new UnsupportedOperationException(NOT_HIERARCHICAL); + } + if (key == null) { + throw new NullPointerException("key"); + } + String query = getEncodedQuery(); + if (query == null) { + return Collections.emptyList(); + } + String encodedKey; + try { + encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + ArrayList values = new ArrayList(); + int start = 0; + do { + int nextAmpersand = query.indexOf('&', start); + int end = nextAmpersand != -1 ? nextAmpersand : query.length(); + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + if (separator - start == encodedKey.length() + && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { + if (separator == end) { + values.add(""); + } else { + values.add(decode(query.substring(separator + 1, end))); + } + } + // Move start to end of name. + if (nextAmpersand != -1) { + start = nextAmpersand + 1; + } else { + break; + } + } while (true); + return Collections.unmodifiableList(values); + } + /** + * Searches the query string for the first value with the given key. + * + *

Warning: Prior to Jelly Bean, this decoded + * the '+' character as '+' rather than ' '. + * + * @param key which will be encoded + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * @throws NullPointerException if key is null + * @return the decoded value or null if no parameter is found + */ + public String getQueryParameter(String key) { + if (isOpaque()) { + throw new UnsupportedOperationException(NOT_HIERARCHICAL); + } + if (key == null) { + throw new NullPointerException("key"); + } + final String query = getEncodedQuery(); + if (query == null) { + return null; + } + final String encodedKey = encode(key, null); + final int length = query.length(); + int start = 0; + do { + int nextAmpersand = query.indexOf('&', start); + int end = nextAmpersand != -1 ? nextAmpersand : length; + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + if (separator - start == encodedKey.length() + && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { + if (separator == end) { + return ""; + } else { + String encodedValue = query.substring(separator + 1, end); + return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false); + } + } + // Move start to end of name. + if (nextAmpersand != -1) { + start = nextAmpersand + 1; + } else { + break; + } + } while (true); + return null; + } + /** + * Searches the query string for the first value with the given key and interprets it + * as a boolean value. "false" and "0" are interpreted as false, everything + * else is interpreted as true. + * + * @param key which will be decoded + * @param defaultValue the default value to return if there is no query parameter for key + * @return the boolean interpretation of the query parameter key + */ + public boolean getBooleanQueryParameter(String key, boolean defaultValue) { + String flag = getQueryParameter(key); + if (flag == null) { + return defaultValue; + } + flag = flag.toLowerCase(Locale.ROOT); + return (!"false".equals(flag) && !"0".equals(flag)); + } + /** + * Return an equivalent URI with a lowercase scheme component. + * This aligns the Uri with Android best practices for + * intent filtering. + * + *

For example, "HTTP://www.android.com" becomes + * "http://www.android.com" + * + *

All URIs received from outside Android (such as user input, + * or external sources like Bluetooth, NFC, or the Internet) should + * be normalized before they are used to create an Intent. + * + *

This method does not validate bad URI's, + * or 'fix' poorly formatted URI's - so do not use it for input validation. + * A Uri will always be returned, even if the Uri is badly formatted to + * begin with and a scheme component cannot be found. + * + * @return normalized Uri (never null) + * @see {@link android.content.Intent#setData} + * @see {@link android.content.Intent#setDataAndNormalize} + */ + public Uri normalizeScheme() { + String scheme = getScheme(); + if (scheme == null) return this; // give up + String lowerScheme = scheme.toLowerCase(Locale.ROOT); + if (scheme.equals(lowerScheme)) return this; // no change + return buildUpon().scheme(lowerScheme).build(); + } + /** Identifies a null parcelled Uri. */ + private static final int NULL_TYPE_ID = 0; + /** + * Reads Uris from Parcels. + */ + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public Uri createFromParcel(Parcel in) { + int type = in.readInt(); + switch (type) { + case NULL_TYPE_ID: return null; + case StringUri.TYPE_ID: return StringUri.readFrom(in); + case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in); + case HierarchicalUri.TYPE_ID: + return HierarchicalUri.readFrom(in); + } + throw new IllegalArgumentException("Unknown URI type: " + type); + } + public Uri[] newArray(int size) { + return new Uri[size]; + } + }; + /** + * Writes a Uri to a Parcel. + * + * @param out parcel to write to + * @param uri to write, can be null + */ + public static void writeToParcel(Parcel out, Uri uri) { + if (uri == null) { + out.writeInt(NULL_TYPE_ID); + } else { + uri.writeToParcel(out, 0); + } + } + private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + /** + * Encodes characters in the given string as '%'-escaped octets + * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers + * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes + * all other characters. + * + * @param s string to encode + * @return an encoded version of s suitable for use as a URI component, + * or null if s is null + */ + public static String encode(String s) { + return encode(s, null); + } + /** + * Encodes characters in the given string as '%'-escaped octets + * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers + * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes + * all other characters with the exception of those specified in the + * allow argument. + * + * @param s string to encode + * @param allow set of additional characters to allow in the encoded form, + * null if no characters should be skipped + * @return an encoded version of s suitable for use as a URI component, + * or null if s is null + */ + public static String encode(String s, String allow) { + if (s == null) { + return null; + } + // Lazily-initialized buffers. + StringBuilder encoded = null; + int oldLength = s.length(); + // This loop alternates between copying over allowed characters and + // encoding in chunks. This results in fewer method calls and + // allocations than encoding one character at a time. + int current = 0; + while (current < oldLength) { + // Start in "copying" mode where we copy over allowed chars. + // Find the next character which needs to be encoded. + int nextToEncode = current; + while (nextToEncode < oldLength + && isAllowed(s.charAt(nextToEncode), allow)) { + nextToEncode++; + } + // If there's nothing more to encode... + if (nextToEncode == oldLength) { + if (current == 0) { + // We didn't need to encode anything! + return s; + } else { + // Presumably, we've already done some encoding. + encoded.append(s, current, oldLength); + return encoded.toString(); + } + } + if (encoded == null) { + encoded = new StringBuilder(); + } + if (nextToEncode > current) { + // Append allowed characters leading up to this point. + encoded.append(s, current, nextToEncode); + } else { + // assert nextToEncode == current + } + // Switch to "encoding" mode. + // Find the next allowed character. + current = nextToEncode; + int nextAllowed = current + 1; + while (nextAllowed < oldLength + && !isAllowed(s.charAt(nextAllowed), allow)) { + nextAllowed++; + } + // Convert the substring to bytes and encode the bytes as + // '%'-escaped octets. + String toEncode = s.substring(current, nextAllowed); + try { + byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING); + int bytesLength = bytes.length; + for (int i = 0; i < bytesLength; i++) { + encoded.append('%'); + encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]); + encoded.append(HEX_DIGITS[bytes[i] & 0xf]); + } + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + current = nextAllowed; + } + // Encoded could still be null at this point if s is empty. + return encoded == null ? s : encoded.toString(); + } + /** + * Returns true if the given character is allowed. + * + * @param c character to check + * @param allow characters to allow + * @return true if the character is allowed or false if it should be + * encoded + */ + private static boolean isAllowed(char c, String allow) { + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || "_-!.~'()*".indexOf(c) != NOT_FOUND + || (allow != null && allow.indexOf(c) != NOT_FOUND); + } + /** + * Decodes '%'-escaped octets in the given string using the UTF-8 scheme. + * Replaces invalid octets with the unicode replacement character + * ("\\uFFFD"). + * + * @param s encoded string to decode + * @return the given string with escaped octets decoded, or null if + * s is null + */ + public static String decode(String s) { + if (s == null) { + return null; + } + return UriCodec.decode(s, false, StandardCharsets.UTF_8, false); + } + /** + * Support for part implementations. + */ + static abstract class AbstractPart { + /** + * Enum which indicates which representation of a given part we have. + */ + static class Representation { + static final int BOTH = 0; + static final int ENCODED = 1; + static final int DECODED = 2; + } + volatile String encoded; + volatile String decoded; + AbstractPart(String encoded, String decoded) { + this.encoded = encoded; + this.decoded = decoded; + } + abstract String getEncoded(); + final String getDecoded() { + @SuppressWarnings("StringEquality") + boolean hasDecoded = decoded != NOT_CACHED; + return hasDecoded ? decoded : (decoded = decode(encoded)); + } + final void writeTo(Parcel parcel) { + @SuppressWarnings("StringEquality") + boolean hasEncoded = encoded != NOT_CACHED; + @SuppressWarnings("StringEquality") + boolean hasDecoded = decoded != NOT_CACHED; + if (hasEncoded && hasDecoded) { + parcel.writeInt(Representation.BOTH); + parcel.writeString(encoded); + parcel.writeString(decoded); + } else if (hasEncoded) { + parcel.writeInt(Representation.ENCODED); + parcel.writeString(encoded); + } else if (hasDecoded) { + parcel.writeInt(Representation.DECODED); + parcel.writeString(decoded); + } else { + throw new IllegalArgumentException("Neither encoded nor decoded"); + } + } + } + /** + * Immutable wrapper of encoded and decoded versions of a URI part. Lazily + * creates the encoded or decoded version from the other. + */ + static class Part extends AbstractPart { + /** A part with null values. */ + static final Part NULL = new EmptyPart(null); + /** A part with empty strings for values. */ + static final Part EMPTY = new EmptyPart(""); + private Part(String encoded, String decoded) { + super(encoded, decoded); + } + boolean isEmpty() { + return false; + } + String getEncoded() { + @SuppressWarnings("StringEquality") + boolean hasEncoded = encoded != NOT_CACHED; + return hasEncoded ? encoded : (encoded = encode(decoded)); + } + static Part readFrom(Parcel parcel) { + int representation = parcel.readInt(); + switch (representation) { + case Representation.BOTH: + return from(parcel.readString(), parcel.readString()); + case Representation.ENCODED: + return fromEncoded(parcel.readString()); + case Representation.DECODED: + return fromDecoded(parcel.readString()); + default: + throw new IllegalArgumentException("Unknown representation: " + + representation); + } + } + /** + * Returns given part or {@link #NULL} if the given part is null. + */ + static Part nonNull(Part part) { + return part == null ? NULL : part; + } + /** + * Creates a part from the encoded string. + * + * @param encoded part string + */ + static Part fromEncoded(String encoded) { + return from(encoded, NOT_CACHED); + } + /** + * Creates a part from the decoded string. + * + * @param decoded part string + */ + static Part fromDecoded(String decoded) { + return from(NOT_CACHED, decoded); + } + /** + * Creates a part from the encoded and decoded strings. + * + * @param encoded part string + * @param decoded part string + */ + static Part from(String encoded, String decoded) { + // We have to check both encoded and decoded in case one is + // NOT_CACHED. + if (encoded == null) { + return NULL; + } + if (encoded.length() == 0) { + return EMPTY; + } + if (decoded == null) { + return NULL; + } + if (decoded .length() == 0) { + return EMPTY; + } + return new Part(encoded, decoded); + } + private static class EmptyPart extends Part { + public EmptyPart(String value) { + super(value, value); + } + @Override + boolean isEmpty() { + return true; + } + } + } + /** + * Immutable wrapper of encoded and decoded versions of a path part. Lazily + * creates the encoded or decoded version from the other. + */ + static class PathPart extends AbstractPart { + /** A part with null values. */ + static final PathPart NULL = new PathPart(null, null); + /** A part with empty strings for values. */ + static final PathPart EMPTY = new PathPart("", ""); + private PathPart(String encoded, String decoded) { + super(encoded, decoded); + } + String getEncoded() { + @SuppressWarnings("StringEquality") + boolean hasEncoded = encoded != NOT_CACHED; + // Don't encode '/'. + return hasEncoded ? encoded : (encoded = encode(decoded, "/")); + } + /** + * Cached path segments. This doesn't need to be volatile--we don't + * care if other threads see the result. + */ + private PathSegments pathSegments; + /** + * Gets the individual path segments. Parses them if necessary. + * + * @return parsed path segments or null if this isn't a hierarchical + * URI + */ + PathSegments getPathSegments() { + if (pathSegments != null) { + return pathSegments; + } + String path = getEncoded(); + if (path == null) { + return pathSegments = PathSegments.EMPTY; + } + PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder(); + int previous = 0; + int current; + while ((current = path.indexOf('/', previous)) > -1) { + // This check keeps us from adding a segment if the path starts + // '/' and an empty segment for "//". + if (previous < current) { + String decodedSegment + = decode(path.substring(previous, current)); + segmentBuilder.add(decodedSegment); + } + previous = current + 1; + } + // Add in the final path segment. + if (previous < path.length()) { + segmentBuilder.add(decode(path.substring(previous))); + } + return pathSegments = segmentBuilder.build(); + } + static PathPart appendEncodedSegment(PathPart oldPart, + String newSegment) { + // If there is no old path, should we make the new path relative + // or absolute? I pick absolute. + if (oldPart == null) { + // No old path. + return fromEncoded("/" + newSegment); + } + String oldPath = oldPart.getEncoded(); + if (oldPath == null) { + oldPath = ""; + } + int oldPathLength = oldPath.length(); + String newPath; + if (oldPathLength == 0) { + // No old path. + newPath = "/" + newSegment; + } else if (oldPath.charAt(oldPathLength - 1) == '/') { + newPath = oldPath + newSegment; + } else { + newPath = oldPath + "/" + newSegment; + } + return fromEncoded(newPath); + } + static PathPart appendDecodedSegment(PathPart oldPart, String decoded) { + String encoded = encode(decoded); + // TODO: Should we reuse old PathSegments? Probably not. + return appendEncodedSegment(oldPart, encoded); + } + static PathPart readFrom(Parcel parcel) { + int representation = parcel.readInt(); + switch (representation) { + case Representation.BOTH: + return from(parcel.readString(), parcel.readString()); + case Representation.ENCODED: + return fromEncoded(parcel.readString()); + case Representation.DECODED: + return fromDecoded(parcel.readString()); + default: + throw new IllegalArgumentException("Bad representation: " + representation); + } + } + /** + * Creates a path from the encoded string. + * + * @param encoded part string + */ + static PathPart fromEncoded(String encoded) { + return from(encoded, NOT_CACHED); + } + /** + * Creates a path from the decoded string. + * + * @param decoded part string + */ + static PathPart fromDecoded(String decoded) { + return from(NOT_CACHED, decoded); + } + /** + * Creates a path from the encoded and decoded strings. + * + * @param encoded part string + * @param decoded part string + */ + static PathPart from(String encoded, String decoded) { + if (encoded == null) { + return NULL; + } + if (encoded.length() == 0) { + return EMPTY; + } + return new PathPart(encoded, decoded); + } + /** + * Prepends path values with "/" if they're present, not empty, and + * they don't already start with "/". + */ + static PathPart makeAbsolute(PathPart oldPart) { + @SuppressWarnings("StringEquality") + boolean encodedCached = oldPart.encoded != NOT_CACHED; + // We don't care which version we use, and we don't want to force + // unneccessary encoding/decoding. + String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded; + if (oldPath == null || oldPath.length() == 0 + || oldPath.startsWith("/")) { + return oldPart; + } + // Prepend encoded string if present. + String newEncoded = encodedCached + ? "/" + oldPart.encoded : NOT_CACHED; + // Prepend decoded string if present. + @SuppressWarnings("StringEquality") + boolean decodedCached = oldPart.decoded != NOT_CACHED; + String newDecoded = decodedCached + ? "/" + oldPart.decoded + : NOT_CACHED; + return new PathPart(newEncoded, newDecoded); + } + } + /** + * Creates a new Uri by appending an already-encoded path segment to a + * base Uri. + * + * @param baseUri Uri to append path segment to + * @param pathSegment encoded path segment to append + * @return a new Uri based on baseUri with the given segment appended to + * the path + * @throws NullPointerException if baseUri is null + */ + public static Uri withAppendedPath(Uri baseUri, String pathSegment) { + Builder builder = baseUri.buildUpon(); + builder = builder.appendEncodedPath(pathSegment); + return builder.build(); + } + /** + * If this {@link Uri} is {@code file://}, then resolve and return its + * canonical path. Also fixes legacy emulated storage paths so they are + * usable across user boundaries. Should always be called from the app + * process before sending elsewhere. + * + * @hide + */ + public Uri getCanonicalUri() { + if ("file".equals(getScheme())) { + final String canonicalPath; + try { + canonicalPath = new File(getPath()).getCanonicalPath(); + } catch (IOException e) { + return this; + } + if (Environment.isExternalStorageEmulated()) { + final String legacyPath = Environment.getLegacyExternalStorageDirectory() + .toString(); + // Splice in user-specific path when legacy path is found + if (canonicalPath.startsWith(legacyPath)) { + return Uri.fromFile(new File( + Environment.getExternalStorageDirectory().toString(), + canonicalPath.substring(legacyPath.length() + 1))); + } + } + return Uri.fromFile(new File(canonicalPath)); + } else { + return this; + } + } + /** + * If this is a {@code file://} Uri, it will be reported to + * {@link StrictMode}. + * + * @hide + */ + public void checkFileUriExposed(String location) { + if ("file".equals(getScheme())) { + //StrictMode.onFileUriExposed(location); + } + } + /** + * Test if this is a path prefix match against the given Uri. Verifies that + * scheme, authority, and atomic path segments match. + * + * @hide + */ + public boolean isPathPrefixMatch(Uri prefix) { + if (!Objects.equals(getScheme(), prefix.getScheme())) return false; + if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false; + List seg = getPathSegments(); + List prefixSeg = prefix.getPathSegments(); + final int prefixSize = prefixSeg.size(); + if (seg.size() < prefixSize) return false; + for (int i = 0; i < prefixSize; i++) { + if (!Objects.equals(seg.get(i), prefixSeg.get(i))) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/net/WebAddress.java b/AndroidCompat/src/main/java/android/net/WebAddress.java new file mode 100644 index 00000000..56f2c821 --- /dev/null +++ b/AndroidCompat/src/main/java/android/net/WebAddress.java @@ -0,0 +1,169 @@ +/* + * 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 android.net; +import android.annotation.SystemApi; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static android.util.Patterns.GOOD_IRI_CHAR; +/** + * {@hide} + * + * Web Address Parser + * + * This is called WebAddress, rather than URL or URI, because it + * attempts to parse the stuff that a user will actually type into a + * browser address widget. + * + * Unlike java.net.uri, this parser will not choke on URIs missing + * schemes. It will only throw a ParseException if the input is + * really hosed. + * + * If given an https scheme but no port, fills in port + * + */ +// TODO(igsolla): remove WebAddress from the system SDK once the WebView apk does not +// longer need to be binary compatible with the API 21 version of the framework. +@SystemApi +public class WebAddress { + private String mScheme; + private String mHost; + private int mPort; + private String mPath; + private String mAuthInfo; + static final int MATCH_GROUP_SCHEME = 1; + static final int MATCH_GROUP_AUTHORITY = 2; + static final int MATCH_GROUP_HOST = 3; + static final int MATCH_GROUP_PORT = 4; + static final int MATCH_GROUP_PATH = 5; + static Pattern sAddressPattern = Pattern.compile( + /* scheme */ "(?:(http|https|file)\\:\\/\\/)?" + + /* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" + + /* host */ "([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?" + + /* port */ "(?:\\:([0-9]*))?" + + /* path */ "(\\/?[^#]*)?" + + /* anchor */ ".*", Pattern.CASE_INSENSITIVE); + /** parses given uriString. */ + public WebAddress(String address) throws ParseException { + if (address == null) { + throw new NullPointerException(); + } + // android.util.Log.d(LOGTAG, "WebAddress: " + address); + mScheme = ""; + mHost = ""; + mPort = -1; + mPath = "/"; + mAuthInfo = ""; + Matcher m = sAddressPattern.matcher(address); + String t; + if (m.matches()) { + t = m.group(MATCH_GROUP_SCHEME); + if (t != null) mScheme = t.toLowerCase(Locale.ROOT); + t = m.group(MATCH_GROUP_AUTHORITY); + if (t != null) mAuthInfo = t; + t = m.group(MATCH_GROUP_HOST); + if (t != null) mHost = t; + t = m.group(MATCH_GROUP_PORT); + if (t != null && t.length() > 0) { + // The ':' character is not returned by the regex. + try { + mPort = Integer.parseInt(t); + } catch (NumberFormatException ex) { + throw new ParseException("Bad port"); + } + } + t = m.group(MATCH_GROUP_PATH); + if (t != null && t.length() > 0) { + /* handle busted myspace frontpage redirect with + missing initial "/" */ + if (t.charAt(0) == '/') { + mPath = t; + } else { + mPath = "/" + t; + } + } + } else { + // nothing found... outa here + throw new ParseException("Bad address"); + } + /* Get port from scheme or scheme from port, if necessary and + possible */ + if (mPort == 443 && mScheme.equals("")) { + mScheme = "https"; + } else if (mPort == -1) { + if (mScheme.equals("https")) + mPort = 443; + else + mPort = 80; // default + } + if (mScheme.equals("")) mScheme = "http"; + } + @Override + public String toString() { + String port = ""; + if ((mPort != 443 && mScheme.equals("https")) || + (mPort != 80 && mScheme.equals("http"))) { + port = ":" + Integer.toString(mPort); + } + String authInfo = ""; + if (mAuthInfo.length() > 0) { + authInfo = mAuthInfo + "@"; + } + return mScheme + "://" + authInfo + mHost + port + mPath; + } + /** {@hide} */ + public void setScheme(String scheme) { + mScheme = scheme; + } + /** {@hide} */ + public String getScheme() { + return mScheme; + } + /** {@hide} */ + public void setHost(String host) { + mHost = host; + } + /** {@hide} */ + public String getHost() { + return mHost; + } + /** {@hide} */ + public void setPort(int port) { + mPort = port; + } + /** {@hide} */ + public int getPort() { + return mPort; + } + /** {@hide} */ + public void setPath(String path) { + mPath = path; + } + /** {@hide} */ + public String getPath() { + return mPath; + } + /** {@hide} */ + public void setAuthInfo(String authInfo) { + mAuthInfo = authInfo; + } + /** {@hide} */ + public String getAuthInfo() { + return mAuthInfo; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/BaseBundle.java b/AndroidCompat/src/main/java/android/os/BaseBundle.java new file mode 100644 index 00000000..f407c91e --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/BaseBundle.java @@ -0,0 +1,1482 @@ +/* + * Copyright (C) 2014 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 android.os; + +import android.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import android.util.Log; +import kotlin.NotImplementedError; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Set; +/** + * A mapping from String keys to values of various types. In most cases, you + * should work directly with either the {@link Bundle} or + * {@link PersistableBundle} subclass. + */ +public class BaseBundle { + private static final String TAG = "Bundle"; + static final boolean DEBUG = false; + // Keep in sync with frameworks/native/libs/binder/PersistableBundle.cpp. + static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' + /** + * Flag indicating that this Bundle is okay to "defuse." That is, it's okay + * for system processes to ignore any {@link BadParcelableException} + * encountered when unparceling it, leaving an empty bundle in its place. + *

+ * This should only be set when the Bundle reaches its final + * destination, otherwise a system process may clobber contents that were + * destined for an app that could have unparceled them. + */ + static final int FLAG_DEFUSABLE = 1 << 0; + private static final boolean LOG_DEFUSABLE = false; + private static volatile boolean sShouldDefuse = false; + /** + * Set global variable indicating that any Bundles parsed in this process + * should be "defused." That is, any {@link BadParcelableException} + * encountered will be suppressed and logged, leaving an empty Bundle + * instead of crashing. + * + * @hide + */ + public static void setShouldDefuse(boolean shouldDefuse) { + sShouldDefuse = shouldDefuse; + } + // A parcel cannot be obtained during compile-time initialization. Put the + // empty parcel into an inner class that can be initialized separately. This + // allows to initialize BaseBundle, and classes depending on it. + /** {@hide} */ + static final class NoImagePreloadHolder { + public static final Parcel EMPTY_PARCEL = Parcel.obtain(); + } + // Invariant - exactly one of mMap / mParcelledData will be null + // (except inside a call to unparcel) + ArrayMap mMap = null; + /* + * If mParcelledData is non-null, then mMap will be null and the + * data are stored as a Parcel containing a Bundle. When the data + * are unparcelled, mParcelledData willbe set to null. + */ + Parcel mParcelledData = null; + /** + * The ClassLoader used when unparcelling data from mParcelledData. + */ + private ClassLoader mClassLoader; + /** {@hide} */ + @VisibleForTesting + public int mFlags; + /** + * Constructs a new, empty Bundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + * @param capacity Initial size of the ArrayMap. + */ + BaseBundle(@Nullable ClassLoader loader, int capacity) { + mMap = capacity > 0 ? + new ArrayMap(capacity) : new ArrayMap(); + mClassLoader = loader == null ? getClass().getClassLoader() : loader; + } + /** + * Constructs a new, empty Bundle. + */ + BaseBundle() { + this((ClassLoader) null, 0); + } + /** + * Constructs a Bundle whose data is stored as a Parcel. The data + * will be unparcelled on first contact, using the assigned ClassLoader. + * + * @param parcelledData a Parcel containing a Bundle + */ + BaseBundle(Parcel parcelledData) { + throw new NotImplementedError(); +// readFromParcelInner(parcelledData); + } + BaseBundle(Parcel parcelledData, int length) { + throw new NotImplementedError(); +// readFromParcelInner(parcelledData, length); + } + /** + * Constructs a new, empty Bundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + BaseBundle(ClassLoader loader) { + this(loader, 0); + } + /** + * Constructs a new, empty Bundle sized to hold the given number of + * elements. The Bundle will grow as needed. + * + * @param capacity the initial capacity of the Bundle + */ + BaseBundle(int capacity) { + this((ClassLoader) null, capacity); + } + /** + * Constructs a Bundle containing a copy of the mappings from the given + * Bundle. + * + * @param b a Bundle to be copied. + */ + BaseBundle(BaseBundle b) { + copyInternal(b, false); + } + /** + * Special constructor that does not initialize the bundle. + */ + BaseBundle(boolean doInit) { + } + /** + * TODO: optimize this later (getting just the value part of a Bundle + * with a single pair) once Bundle.forPair() above is implemented + * with a special single-value Map implementation/serialization. + * + * Note: value in single-pair Bundle may be null. + * + * @hide + */ + public String getPairValue() { + unparcel(); + int size = mMap.size(); + if (size > 1) { + Log.w(TAG, "getPairValue() used on Bundle with multiple pairs."); + } + if (size == 0) { + return null; + } + Object o = mMap.valueAt(0); + try { + return (String) o; + } catch (ClassCastException e) { + typeWarning("getPairValue()", o, "String", e); + return null; + } + } + /** + * Changes the ClassLoader this Bundle uses when instantiating objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + void setClassLoader(ClassLoader loader) { + mClassLoader = loader; + } + /** + * Return the ClassLoader currently associated with this Bundle. + */ + ClassLoader getClassLoader() { + return mClassLoader; + } + /** + * If the underlying data are stored as a Parcel, unparcel them + * using the currently assigned class loader. + */ + /* package */ void unparcel() { + synchronized (this) { + final Parcel source = mParcelledData; + if (source != null) { + throw new NotImplementedError(); +// initializeFromParcelLocked(source, /*recycleParcel=*/ true); + } else { + if (DEBUG) { + Log.d(TAG, "unparcel " + + Integer.toHexString(System.identityHashCode(this)) + + ": no parcelled data"); + } + } + } + } + /*private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) { + if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { +// Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may " +// + "clobber all data inside!", new Throwable()); + } + if (isEmptyParcel(parcelledData)) { + if (DEBUG) { + Log.d(TAG, "unparcel " + + Integer.toHexString(System.identityHashCode(this)) + ": empty"); + } + if (mMap == null) { + mMap = new ArrayMap<>(1); + } else { + mMap.erase(); + } + mParcelledData = null; + return; + } + final int count = parcelledData.readInt(); + if (DEBUG) { + Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + ": reading " + count + " maps"); + } + if (count < 0) { + return; + } + ArrayMap map = mMap; + if (map == null) { + map = new ArrayMap<>(count); + } else { + map.erase(); + map.ensureCapacity(count); + } + try { + parcelledData.readArrayMapInternal(map, count, mClassLoader); + } catch (BadParcelableException e) { + if (sShouldDefuse) { + Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); + map.erase(); + } else { + throw e; + } + } finally { + mMap = map; + if (recycleParcel) { + recycleParcel(parcelledData); + } + mParcelledData = null; + } + if (DEBUG) { + Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + + " final map: " + mMap); + } + }*/ + /** + * @hide + */ + public boolean isParcelled() { + return false; +// return mParcelledData != null; + } + /** + * @hide + */ + public boolean isEmptyParcel() { + return isEmptyParcel(mParcelledData); + } + /** + * @hide + */ + private static boolean isEmptyParcel(Parcel p) { + return p == NoImagePreloadHolder.EMPTY_PARCEL; + } + private static void recycleParcel(Parcel p) { + if (p != null && !isEmptyParcel(p)) { + p.recycle(); + } + } + /** @hide */ + ArrayMap getMap() { + unparcel(); + return mMap; + } + /** + * Returns the number of mappings contained in this Bundle. + * + * @return the number of mappings as an int. + */ + public int size() { + unparcel(); + return mMap.size(); + } + /** + * Returns true if the mapping of this Bundle is empty, false otherwise. + */ + public boolean isEmpty() { + unparcel(); + return mMap.isEmpty(); + } + /** + * @hide this should probably be the implementation of isEmpty(). To do that we + * need to ensure we always use the special empty parcel form when the bundle is + * empty. (This may already be the case, but to be safe we'll do this later when + * we aren't trying to stabilize.) + */ + public boolean maybeIsEmpty() { + if (isParcelled()) { + return isEmptyParcel(); + } else { + return isEmpty(); + } + } + /** + * @hide This kind-of does an equality comparison. Kind-of. + */ + public boolean kindofEquals(BaseBundle other) { + if (other == null) { + return false; + } + if (isParcelled() != other.isParcelled()) { + // Big kind-of here! + return false; + } else if (isParcelled()) { + throw new NotImplementedError(); +// return mParcelledData.compareData(other.mParcelledData) == 0; + } else { + return mMap.equals(other.mMap); + } + } + /** + * Removes all elements from the mapping of this Bundle. + */ + public void clear() { + unparcel(); + mMap.clear(); + } + void copyInternal(BaseBundle from, boolean deep) { + synchronized (from) { + if (from.mParcelledData != null) { + if (from.isEmptyParcel()) { + mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL; + } else { + mParcelledData = Parcel.obtain(); + mParcelledData.appendFrom(from.mParcelledData, 0, + from.mParcelledData.dataSize()); + mParcelledData.setDataPosition(0); + } + } else { + mParcelledData = null; + } + if (from.mMap != null) { + if (!deep) { + mMap = new ArrayMap<>(from.mMap); + } else { + final ArrayMap fromMap = from.mMap; + final int N = fromMap.size(); + mMap = new ArrayMap<>(N); + for (int i = 0; i < N; i++) { + mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i))); + } + } + } else { + mMap = null; + } + mClassLoader = from.mClassLoader; + } + } + Object deepCopyValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Bundle) { + return ((Bundle)value).deepCopy(); + } else if (value instanceof PersistableBundle) { + return ((PersistableBundle)value).deepCopy(); + } else if (value instanceof ArrayList) { + return deepcopyArrayList((ArrayList) value); + } else if (value.getClass().isArray()) { + if (value instanceof int[]) { + return ((int[])value).clone(); + } else if (value instanceof long[]) { + return ((long[])value).clone(); + } else if (value instanceof float[]) { + return ((float[])value).clone(); + } else if (value instanceof double[]) { + return ((double[])value).clone(); + } else if (value instanceof Object[]) { + return ((Object[])value).clone(); + } else if (value instanceof byte[]) { + return ((byte[])value).clone(); + } else if (value instanceof short[]) { + return ((short[])value).clone(); + } else if (value instanceof char[]) { + return ((char[]) value).clone(); + } + } + return value; + } + ArrayList deepcopyArrayList(ArrayList from) { + final int N = from.size(); + ArrayList out = new ArrayList(N); + for (int i=0; i keySet() { + unparcel(); + return mMap.keySet(); + } + /** + * Inserts a Boolean value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a boolean + */ + public void putBoolean(@Nullable String key, boolean value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a byte value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a byte + */ + void putByte(@Nullable String key, byte value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a char value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a char + */ + void putChar(@Nullable String key, char value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a short value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a short + */ + void putShort(@Nullable String key, short value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an int value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value an int + */ + public void putInt(@Nullable String key, int value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a long value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a long + */ + public void putLong(@Nullable String key, long value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a float value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a float + */ + void putFloat(@Nullable String key, float value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a double value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a double + */ + public void putDouble(@Nullable String key, double value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a String value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String, or null + */ + public void putString(@Nullable String key, @Nullable String value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a CharSequence value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence, or null + */ + void putCharSequence(@Nullable String key, @Nullable CharSequence value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + void putIntegerArrayList(@Nullable String key, @Nullable ArrayList value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + void putStringArrayList(@Nullable String key, @Nullable ArrayList value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + void putCharSequenceArrayList(@Nullable String key, @Nullable ArrayList value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a Serializable value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Serializable object, or null + */ + void putSerializable(@Nullable String key, @Nullable Serializable value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a boolean array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a boolean array object, or null + */ + public void putBooleanArray(@Nullable String key, @Nullable boolean[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a byte array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a byte array object, or null + */ + void putByteArray(@Nullable String key, @Nullable byte[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a short array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a short array object, or null + */ + void putShortArray(@Nullable String key, @Nullable short[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a char array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a char array object, or null + */ + void putCharArray(@Nullable String key, @Nullable char[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an int array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an int array object, or null + */ + public void putIntArray(@Nullable String key, @Nullable int[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a long array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a long array object, or null + */ + public void putLongArray(@Nullable String key, @Nullable long[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a float array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a float array object, or null + */ + void putFloatArray(@Nullable String key, @Nullable float[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a double array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a double array object, or null + */ + public void putDoubleArray(@Nullable String key, @Nullable double[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a String array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a String array object, or null + */ + public void putStringArray(@Nullable String key, @Nullable String[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a CharSequence array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence array object, or null + */ + void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) { + unparcel(); + mMap.put(key, value); + } + /** + * Returns the value associated with the given key, or false if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a boolean value + */ + public boolean getBoolean(String key) { + unparcel(); + if (DEBUG) Log.d(TAG, "Getting boolean in " + + Integer.toHexString(System.identityHashCode(this))); + return getBoolean(key, false); + } + // Log a message if the value was non-null but not of the expected type + void typeWarning(String key, Object value, String className, + Object defaultValue, ClassCastException e) { + StringBuilder sb = new StringBuilder(); + sb.append("Key "); + sb.append(key); + sb.append(" expected "); + sb.append(className); + sb.append(" but value was a "); + sb.append(value.getClass().getName()); + sb.append(". The default value "); + sb.append(defaultValue); + sb.append(" was returned."); + Log.w(TAG, sb.toString()); + Log.w(TAG, "Attempt to cast generated internal exception:", e); + } + void typeWarning(String key, Object value, String className, + ClassCastException e) { + typeWarning(key, value, className, "", e); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a boolean value + */ + public boolean getBoolean(String key, boolean defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Boolean) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Boolean", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or (byte) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a byte value + */ + byte getByte(String key) { + unparcel(); + return getByte(key, (byte) 0); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a byte value + */ + Byte getByte(String key, byte defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Byte) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Byte", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or (char) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a char value + */ + char getChar(String key) { + unparcel(); + return getChar(key, (char) 0); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a char value + */ + char getChar(String key, char defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Character) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Character", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or (short) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a short value + */ + short getShort(String key) { + unparcel(); + return getShort(key, (short) 0); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a short value + */ + short getShort(String key, short defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Short) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Short", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return an int value + */ + public int getInt(String key) { + unparcel(); + return getInt(key, 0); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return an int value + */ + public int getInt(String key, int defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Integer) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Integer", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or 0L if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a long value + */ + public long getLong(String key) { + unparcel(); + return getLong(key, 0L); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a long value + */ + public long getLong(String key, long defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Long) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Long", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or 0.0f if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a float value + */ + float getFloat(String key) { + unparcel(); + return getFloat(key, 0.0f); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a float value + */ + float getFloat(String key, float defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Float) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Float", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or 0.0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a double value + */ + public double getDouble(String key) { + unparcel(); + return getDouble(key, 0.0); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a double value + */ + public double getDouble(String key, double defaultValue) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return defaultValue; + } + try { + return (Double) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Double", defaultValue, e); + return defaultValue; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String value, or null + */ + @Nullable + public String getString(@Nullable String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (String) o; + } catch (ClassCastException e) { + typeWarning(key, o, "String", e); + return null; + } + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associated with the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. + * @return the String value associated with the given key, or defaultValue + * if no valid String object is currently mapped to that key. + */ + public String getString(@Nullable String key, String defaultValue) { + final String s = getString(key); + return (s == null) ? defaultValue : s; + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence value, or null + */ + @Nullable + CharSequence getCharSequence(@Nullable String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (CharSequence) o; + } catch (ClassCastException e) { + typeWarning(key, o, "CharSequence", e); + return null; + } + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associated with the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. + * @return the CharSequence value associated with the given key, or defaultValue + * if no valid CharSequence object is currently mapped to that key. + */ + CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) { + final CharSequence cs = getCharSequence(key); + return (cs == null) ? defaultValue : cs; + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Serializable value, or null + */ + @Nullable + Serializable getSerializable(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (Serializable) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Serializable", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Nullable + ArrayList getIntegerArrayList(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Nullable + ArrayList getStringArrayList(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Nullable + ArrayList getCharSequenceArrayList(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a boolean[] value, or null + */ + @Nullable + public boolean[] getBooleanArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (boolean[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "byte[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a byte[] value, or null + */ + @Nullable + byte[] getByteArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (byte[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "byte[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a short[] value, or null + */ + @Nullable + short[] getShortArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (short[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "short[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a char[] value, or null + */ + @Nullable + char[] getCharArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (char[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "char[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an int[] value, or null + */ + @Nullable + public int[] getIntArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (int[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "int[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a long[] value, or null + */ + @Nullable + public long[] getLongArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (long[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "long[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a float[] value, or null + */ + @Nullable + float[] getFloatArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (float[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "float[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a double[] value, or null + */ + @Nullable + public double[] getDoubleArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (double[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "double[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a String[] value, or null + */ + @Nullable + public String[] getStringArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (String[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "String[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence[] value, or null + */ + @Nullable + CharSequence[] getCharSequenceArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (CharSequence[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "CharSequence[]", e); + return null; + } + } + /** + * Writes the Bundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + void writeToParcelInner(Parcel parcel, int flags) { + /*// If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first. + if (parcel.hasReadWriteHelper()) { + unparcel(); + } + // Keep implementation in sync with writeToParcel() in + // frameworks/native/libs/binder/PersistableBundle.cpp. + final ArrayMap map; + synchronized (this) { + // unparcel() can race with this method and cause the parcel to recycle + // at the wrong time. So synchronize access the mParcelledData's content. + if (mParcelledData != null) { + if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) { + parcel.writeInt(0); + } else { + int length = mParcelledData.dataSize(); + parcel.writeInt(length); + parcel.writeInt(BUNDLE_MAGIC); + parcel.appendFrom(mParcelledData, 0, length); + } + return; + } + map = mMap; + } + // Special case for empty bundles. + if (map == null || map.size() <= 0) { + parcel.writeInt(0); + return; + } + int lengthPos = parcel.dataPosition(); + parcel.writeInt(-1); // dummy, will hold length + parcel.writeInt(BUNDLE_MAGIC); + int startPos = parcel.dataPosition(); + parcel.writeArrayMapInternal(map); + int endPos = parcel.dataPosition(); + // Backpatch length + parcel.setDataPosition(lengthPos); + int length = endPos - startPos; + parcel.writeInt(length); + parcel.setDataPosition(endPos);*/ + } + /** + * Reads the Parcel contents into this Bundle, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to overwrite this bundle from. + */ + void readFromParcelInner(Parcel parcel) { + // Keep implementation in sync with readFromParcel() in + // frameworks/native/libs/binder/PersistableBundle.cpp. +// int length = parcel.readInt(); +// readFromParcelInner(parcel, length); + } + /*private void readFromParcelInner(Parcel parcel, int length) { + if (length < 0) { + throw new RuntimeException("Bad length in parcel: " + length); + } else if (length == 0) { + // Empty Bundle or end of data. + mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL; + return; + } + final int magic = parcel.readInt(); + if (magic != BUNDLE_MAGIC) { + throw new IllegalStateException("Bad magic number for Bundle: 0x" + + Integer.toHexString(magic)); + } + if (parcel.hasReadWriteHelper()) { + // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just + // unparcel right away. + synchronized (this) { + initializeFromParcelLocked(parcel, *//*recycleParcel=*//* false); + } + return; + } + // Advance within this Parcel + int offset = parcel.dataPosition(); + parcel.setDataPosition(MathUtils.addOrThrow(offset, length)); + Parcel p = Parcel.obtain(); + p.setDataPosition(0); + p.appendFrom(parcel, offset, length); + p.adoptClassCookies(parcel); + if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this)) + + ": " + length + " bundle bytes starting at " + offset); + p.setDataPosition(0); + mParcelledData = p; + }*/ +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/Build.java b/AndroidCompat/src/main/java/android/os/Build.java new file mode 100644 index 00000000..d8fecc23 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/Build.java @@ -0,0 +1,102 @@ +package android.os; + +import xyz.nulldev.androidcompat.config.SystemConfigModule; +import xyz.nulldev.ts.config.ConfigManager; +import xyz.nulldev.ts.config.GlobalConfigManager; + +/** + * Android compat class + */ +public class Build { + private static ConfigManager configManager = GlobalConfigManager.INSTANCE; + private static SystemConfigModule configModule = configManager.module(SystemConfigModule.class); + + public static boolean IS_DEBUGGABLE = configModule.isDebuggable(); + + //TODO Make all of this stuff configurable! + + public static final String BOARD = null; + public static final String BOOTLOADER = null; + public static final String BRAND = null; + /** @deprecated */ + @Deprecated + public static final String CPU_ABI = null; + /** @deprecated */ + @Deprecated + public static final String CPU_ABI2 = null; + public static final String DEVICE = null; + public static final String DISPLAY = null; + public static final String FINGERPRINT = null; + public static final String HARDWARE = null; + public static final String HOST = null; + public static final String ID = null; + public static final String MANUFACTURER = null; + public static final String MODEL = null; + public static final String PRODUCT = null; + /** @deprecated */ + @Deprecated + public static final String RADIO = null; + public static final String SERIAL = null; + public static final String[] SUPPORTED_32_BIT_ABIS = null; + public static final String[] SUPPORTED_64_BIT_ABIS = null; + public static final String[] SUPPORTED_ABIS = null; + public static final String TAGS = null; + public static final long TIME = 0L; + public static final String TYPE = null; + public static final String UNKNOWN = "unknown"; + public static final String USER = null; + + public Build() { + throw new RuntimeException("This class cannot be instantiated!"); + } + + public static String getRadioVersion() { + throw new RuntimeException("Stub!"); + } + + public static class VERSION_CODES { + public static final int BASE = 1; + public static final int BASE_1_1 = 2; + public static final int CUPCAKE = 3; + public static final int CUR_DEVELOPMENT = 10000; + public static final int DONUT = 4; + public static final int ECLAIR = 5; + public static final int ECLAIR_0_1 = 6; + public static final int ECLAIR_MR1 = 7; + public static final int FROYO = 8; + public static final int GINGERBREAD = 9; + public static final int GINGERBREAD_MR1 = 10; + public static final int HONEYCOMB = 11; + public static final int HONEYCOMB_MR1 = 12; + public static final int HONEYCOMB_MR2 = 13; + public static final int ICE_CREAM_SANDWICH = 14; + public static final int ICE_CREAM_SANDWICH_MR1 = 15; + public static final int JELLY_BEAN = 16; + public static final int JELLY_BEAN_MR1 = 17; + public static final int JELLY_BEAN_MR2 = 18; + public static final int KITKAT = 19; + public static final int KITKAT_WATCH = 20; + public static final int LOLLIPOP = 21; + public static final int LOLLIPOP_MR1 = 22; + public static final int M = 23; + public static final int N = 24; + public static final int O = 25; + } + + public static class VERSION { + public static final String BASE_OS = null; + public static final String CODENAME = null; + public static final String INCREMENTAL = null; + public static final int PREVIEW_SDK_INT = 0; + public static final String RELEASE = null; + /** @deprecated */ + @Deprecated + public static final String SDK = null; + public static final int SDK_INT = 0; + public static final String SECURITY_PATCH = null; + + public VERSION() { + throw new RuntimeException("Stub!"); + } + } +} diff --git a/AndroidCompat/src/main/java/android/os/Bundle.java b/AndroidCompat/src/main/java/android/os/Bundle.java new file mode 100644 index 00000000..3f2cd06d --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/Bundle.java @@ -0,0 +1,1184 @@ +/* + * 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 android.os; + +import android.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import android.util.Size; +import android.util.SizeF; +import android.util.SparseArray; +import kotlin.NotImplementedError; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +/** + * A mapping from String keys to various {@link Parcelable} values. + * + * @see PersistableBundle + */ +public final class Bundle extends BaseBundle implements Cloneable, Parcelable { + @VisibleForTesting + static final int FLAG_HAS_FDS = 1 << 8; + @VisibleForTesting + static final int FLAG_HAS_FDS_KNOWN = 1 << 9; + @VisibleForTesting + static final int FLAG_ALLOW_FDS = 1 << 10; + public static final Bundle EMPTY; + /** + * Special extras used to denote extras have been stripped off. + * @hide + */ + public static final Bundle STRIPPED; + static { + EMPTY = new Bundle(); + EMPTY.mMap = ArrayMap.EMPTY; + STRIPPED = new Bundle(); + STRIPPED.putInt("STRIPPED", 1); + } + /** + * Constructs a new, empty Bundle. + */ + public Bundle() { + super(); + mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS; + } + /** + * Constructs a Bundle whose data is stored as a Parcel. The data + * will be unparcelled on first contact, using the assigned ClassLoader. + * + * @param parcelledData a Parcel containing a Bundle + * + * @hide + */ + @VisibleForTesting + public Bundle(Parcel parcelledData) { + super(parcelledData); + mFlags = FLAG_ALLOW_FDS; + maybePrefillHasFds(); + } + /** + * Constructor from a parcel for when the length is known *and is not stored in the parcel.* + * The other constructor that takes a parcel assumes the length is in the parcel. + * + * @hide + */ + @VisibleForTesting + public Bundle(Parcel parcelledData, int length) { + super(parcelledData, length); + mFlags = FLAG_ALLOW_FDS; + maybePrefillHasFds(); + } + /** + * If {@link #mParcelledData} is not null, copy the HAS FDS bit from it because it's fast. + * Otherwise (if {@link #mParcelledData} is already null), leave {@link #FLAG_HAS_FDS_KNOWN} + * unset, because scanning a map is slower. We'll do it lazily in + * {@link #hasFileDescriptors()}. + */ + private void maybePrefillHasFds() { + if (mParcelledData != null) { + if (mParcelledData.hasFileDescriptors()) { + mFlags |= FLAG_HAS_FDS | FLAG_HAS_FDS_KNOWN; + } else { + mFlags |= FLAG_HAS_FDS_KNOWN; + } + } + } + /** + * Constructs a new, empty Bundle that uses a specific ClassLoader for + * instantiating Parcelable and Serializable objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + public Bundle(ClassLoader loader) { + super(loader); + mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS; + } + /** + * Constructs a new, empty Bundle sized to hold the given number of + * elements. The Bundle will grow as needed. + * + * @param capacity the initial capacity of the Bundle + */ + public Bundle(int capacity) { + super(capacity); + mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS; + } + /** + * Constructs a Bundle containing a copy of the mappings from the given + * Bundle. Does only a shallow copy of the original Bundle -- see + * {@link #deepCopy()} if that is not what you want. + * + * @param b a Bundle to be copied. + * + * @see #deepCopy() + */ + public Bundle(Bundle b) { + super(b); + mFlags = b.mFlags; + } + /** + * Constructs a Bundle containing a copy of the mappings from the given + * PersistableBundle. Does only a shallow copy of the PersistableBundle -- see + * {@link PersistableBundle#deepCopy()} if you don't want that. + * + * @param b a PersistableBundle to be copied. + */ + public Bundle(PersistableBundle b) { + super(b); + mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS; + } + /** + * Constructs a Bundle without initializing it. + */ + Bundle(boolean doInit) { + super(doInit); + } + /** + * Make a Bundle for a single key/value pair. + * + * @hide + */ + public static Bundle forPair(String key, String value) { + Bundle b = new Bundle(1); + b.putString(key, value); + return b; + } + /** + * Changes the ClassLoader this Bundle uses when instantiating objects. + * + * @param loader An explicit ClassLoader to use when instantiating objects + * inside of the Bundle. + */ + @Override + public void setClassLoader(ClassLoader loader) { + super.setClassLoader(loader); + } + /** + * Return the ClassLoader currently associated with this Bundle. + */ + @Override + public ClassLoader getClassLoader() { + return super.getClassLoader(); + } + /** {@hide} */ + public boolean setAllowFds(boolean allowFds) { + final boolean orig = (mFlags & FLAG_ALLOW_FDS) != 0; + if (allowFds) { + mFlags |= FLAG_ALLOW_FDS; + } else { + mFlags &= ~FLAG_ALLOW_FDS; + } + return orig; + } + /** + * Mark if this Bundle is okay to "defuse." That is, it's okay for system + * processes to ignore any {@link BadParcelableException} encountered when + * unparceling it, leaving an empty bundle in its place. + *

+ * This should only be set when the Bundle reaches its final + * destination, otherwise a system process may clobber contents that were + * destined for an app that could have unparceled them. + * + * @hide + */ + public void setDefusable(boolean defusable) { + if (defusable) { + mFlags |= FLAG_DEFUSABLE; + } else { + mFlags &= ~FLAG_DEFUSABLE; + } + } + /** {@hide} */ + public static Bundle setDefusable(Bundle bundle, boolean defusable) { + if (bundle != null) { + bundle.setDefusable(defusable); + } + return bundle; + } + /** + * Clones the current Bundle. The internal map is cloned, but the keys and + * values to which it refers are copied by reference. + */ + @Override + public Object clone() { + return new Bundle(this); + } + /** + * Make a deep copy of the given bundle. Traverses into inner containers and copies + * them as well, so they are not shared across bundles. Will traverse in to + * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of + * primitive arrays. Other types of objects (such as Parcelable or Serializable) + * are referenced as-is and not copied in any way. + */ + public Bundle deepCopy() { + Bundle b = new Bundle(false); + b.copyInternal(this, true); + return b; + } + /** + * Removes all elements from the mapping of this Bundle. + */ + @Override + public void clear() { + super.clear(); + mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS; + } + /** + * Removes any entry with the given key from the mapping of this Bundle. + * + * @param key a String key + */ + public void remove(String key) { + super.remove(key); + if ((mFlags & FLAG_HAS_FDS) != 0) { + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + } + /** + * Inserts all mappings from the given Bundle into this Bundle. + * + * @param bundle a Bundle + */ + public void putAll(Bundle bundle) { + unparcel(); + bundle.unparcel(); + mMap.putAll(bundle.mMap); + // FD state is now known if and only if both bundles already knew + if ((bundle.mFlags & FLAG_HAS_FDS) != 0) { + mFlags |= FLAG_HAS_FDS; + } + if ((bundle.mFlags & FLAG_HAS_FDS_KNOWN) == 0) { + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + } + /** + * Return the size of {@link #mParcelledData} in bytes if available, otherwise {@code 0}. + * + * @hide + */ + public int getSize() { + if (mParcelledData != null) { + return mParcelledData.dataSize(); + } else { + return 0; + } + } + /** + * Reports whether the bundle contains any parcelled file descriptors. + */ + public boolean hasFileDescriptors() { + if ((mFlags & FLAG_HAS_FDS_KNOWN) == 0) { + boolean fdFound = false; // keep going until we find one or run out of data + if (mParcelledData != null) { + if (mParcelledData.hasFileDescriptors()) { + fdFound = true; + } + } else { + // It's been unparcelled, so we need to walk the map + for (int i=mMap.size()-1; i>=0; i--) { + Object obj = mMap.valueAt(i); + if (obj instanceof Parcelable) { + if ((((Parcelable)obj).describeContents() + & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) { + fdFound = true; + break; + } + } else if (obj instanceof Parcelable[]) { + Parcelable[] array = (Parcelable[]) obj; + for (int n = array.length - 1; n >= 0; n--) { + Parcelable p = array[n]; + if (p != null && ((p.describeContents() + & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) { + fdFound = true; + break; + } + } + } else if (obj instanceof SparseArray) { + SparseArray array = + (SparseArray) obj; + for (int n = array.size() - 1; n >= 0; n--) { + Parcelable p = array.valueAt(n); + if (p != null && (p.describeContents() + & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) { + fdFound = true; + break; + } + } + } else if (obj instanceof ArrayList) { + ArrayList array = (ArrayList) obj; + // an ArrayList here might contain either Strings or + // Parcelables; only look inside for Parcelables + if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) { + for (int n = array.size() - 1; n >= 0; n--) { + Parcelable p = (Parcelable) array.get(n); + if (p != null && ((p.describeContents() + & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) { + fdFound = true; + break; + } + } + } + } + } + } + if (fdFound) { + mFlags |= FLAG_HAS_FDS; + } else { + mFlags &= ~FLAG_HAS_FDS; + } + mFlags |= FLAG_HAS_FDS_KNOWN; + } + return (mFlags & FLAG_HAS_FDS) != 0; + } + /** + * Filter values in Bundle to only basic types. + * @hide + */ + public Bundle filterValues() { + unparcel(); + Bundle bundle = this; + if (mMap != null) { + ArrayMap map = mMap; + for (int i = map.size() - 1; i >= 0; i--) { + Object value = map.valueAt(i); + if (PersistableBundle.isValidType(value)) { + continue; + } + if (value instanceof Bundle) { + Bundle newBundle = ((Bundle)value).filterValues(); + if (newBundle != value) { + if (map == mMap) { + // The filter had to generate a new bundle, but we have not yet + // created a new one here. Do that now. + bundle = new Bundle(this); + // Note the ArrayMap<> constructor is guaranteed to generate + // a new object with items in the same order as the original. + map = bundle.mMap; + } + // Replace this current entry with the new child bundle. + map.setValueAt(i, newBundle); + } + continue; + } + if (value.getClass().getName().startsWith("android.")) { + continue; + } + if (map == mMap) { + // This is the first time we have had to remove something, that means we + // need to switch to a new Bundle. + bundle = new Bundle(this); + // Note the ArrayMap<> constructor is guaranteed to generate + // a new object with items in the same order as the original. + map = bundle.mMap; + } + map.removeAt(i); + } + } + mFlags |= FLAG_HAS_FDS_KNOWN; + mFlags &= ~FLAG_HAS_FDS; + return bundle; + } + /** + * Inserts a byte value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a byte + */ + @Override + public void putByte(@Nullable String key, byte value) { + super.putByte(key, value); + } + /** + * Inserts a char value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a char + */ + @Override + public void putChar(@Nullable String key, char value) { + super.putChar(key, value); + } + /** + * Inserts a short value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a short + */ + @Override + public void putShort(@Nullable String key, short value) { + super.putShort(key, value); + } + /** + * Inserts a float value into the mapping of this Bundle, replacing + * any existing value for the given key. + * + * @param key a String, or null + * @param value a float + */ + @Override + public void putFloat(@Nullable String key, float value) { + super.putFloat(key, value); + } + /** + * Inserts a CharSequence value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence, or null + */ + @Override + public void putCharSequence(@Nullable String key, @Nullable CharSequence value) { + super.putCharSequence(key, value); + } + /** + * Inserts a Parcelable value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Parcelable object, or null + */ + public void putParcelable(@Nullable String key, @Nullable Parcelable value) { + unparcel(); + mMap.put(key, value); + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + /** + * Inserts a Size value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Size object, or null + */ + public void putSize(@Nullable String key, @Nullable Size value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts a SizeF value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a SizeF object, or null + */ + public void putSizeF(@Nullable String key, @Nullable SizeF value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an array of Parcelable values into the mapping of this Bundle, + * replacing any existing value for the given key. Either key or value may + * be null. + * + * @param key a String, or null + * @param value an array of Parcelable objects, or null + */ + public void putParcelableArray(@Nullable String key, @Nullable Parcelable[] value) { + unparcel(); + mMap.put(key, value); + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + /** + * Inserts a List of Parcelable values into the mapping of this Bundle, + * replacing any existing value for the given key. Either key or value may + * be null. + * + * @param key a String, or null + * @param value an ArrayList of Parcelable objects, or null + */ + public void putParcelableArrayList(@Nullable String key, + @Nullable ArrayList value) { + unparcel(); + mMap.put(key, value); + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + /** {@hide} */ + public void putParcelableList(String key, List value) { + unparcel(); + mMap.put(key, value); + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + /** + * Inserts a SparceArray of Parcelable values into the mapping of this + * Bundle, replacing any existing value for the given key. Either key + * or value may be null. + * + * @param key a String, or null + * @param value a SparseArray of Parcelable objects, or null + */ + public void putSparseParcelableArray(@Nullable String key, + @Nullable SparseArray value) { + unparcel(); + mMap.put(key, value); + mFlags &= ~FLAG_HAS_FDS_KNOWN; + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + @Override + public void putIntegerArrayList(@Nullable String key, @Nullable ArrayList value) { + super.putIntegerArrayList(key, value); + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + @Override + public void putStringArrayList(@Nullable String key, @Nullable ArrayList value) { + super.putStringArrayList(key, value); + } + /** + * Inserts an ArrayList value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an ArrayList object, or null + */ + @Override + public void putCharSequenceArrayList(@Nullable String key, + @Nullable ArrayList value) { + super.putCharSequenceArrayList(key, value); + } + /** + * Inserts a Serializable value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Serializable object, or null + */ + @Override + public void putSerializable(@Nullable String key, @Nullable Serializable value) { + super.putSerializable(key, value); + } + /** + * Inserts a byte array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a byte array object, or null + */ + @Override + public void putByteArray(@Nullable String key, @Nullable byte[] value) { + super.putByteArray(key, value); + } + /** + * Inserts a short array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a short array object, or null + */ + @Override + public void putShortArray(@Nullable String key, @Nullable short[] value) { + super.putShortArray(key, value); + } + /** + * Inserts a char array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a char array object, or null + */ + @Override + public void putCharArray(@Nullable String key, @Nullable char[] value) { + super.putCharArray(key, value); + } + /** + * Inserts a float array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a float array object, or null + */ + @Override + public void putFloatArray(@Nullable String key, @Nullable float[] value) { + super.putFloatArray(key, value); + } + /** + * Inserts a CharSequence array value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a CharSequence array object, or null + */ + @Override + public void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) { + super.putCharSequenceArray(key, value); + } + /** + * Inserts a Bundle value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Bundle object, or null + */ + public void putBundle(@Nullable String key, @Nullable Bundle value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + *

You should be very careful when using this function. In many + * places where Bundles are used (such as inside of Intent objects), the Bundle + * can live longer inside of another process than the process that had originally + * created it. In that case, the IBinder you supply here will become invalid + * when your process goes away, and no longer usable, even if a new process is + * created for you later on.

+ * + * @param key a String, or null + * @param value an IBinder object, or null + */ + public void putBinder(@Nullable String key, @Nullable IBinder value) { + unparcel(); + mMap.put(key, value); + } + /** + * Inserts an IBinder value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value an IBinder object, or null + * + * @deprecated + * @hide This is the old name of the function. + */ + @Deprecated + public void putIBinder(@Nullable String key, @Nullable IBinder value) { + unparcel(); + mMap.put(key, value); + } + /** + * Returns the value associated with the given key, or (byte) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a byte value + */ + @Override + public byte getByte(String key) { + return super.getByte(key); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a byte value + */ + @Override + public Byte getByte(String key, byte defaultValue) { + return super.getByte(key, defaultValue); + } + /** + * Returns the value associated with the given key, or (char) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a char value + */ + @Override + public char getChar(String key) { + return super.getChar(key); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a char value + */ + @Override + public char getChar(String key, char defaultValue) { + return super.getChar(key, defaultValue); + } + /** + * Returns the value associated with the given key, or (short) 0 if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a short value + */ + @Override + public short getShort(String key) { + return super.getShort(key); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a short value + */ + @Override + public short getShort(String key, short defaultValue) { + return super.getShort(key, defaultValue); + } + /** + * Returns the value associated with the given key, or 0.0f if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @return a float value + */ + @Override + public float getFloat(String key) { + return super.getFloat(key); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a float value + */ + @Override + public float getFloat(String key, float defaultValue) { + return super.getFloat(key, defaultValue); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence value, or null + */ + @Override + @Nullable + public CharSequence getCharSequence(@Nullable String key) { + return super.getCharSequence(key); + } + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associatd with the given key. + * + * @param key a String, or null + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. + * @return the CharSequence value associated with the given key, or defaultValue + * if no valid CharSequence object is currently mapped to that key. + */ + @Override + public CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) { + return super.getCharSequence(key, defaultValue); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Size value, or null + */ + @Nullable + public Size getSize(@Nullable String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (Size) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Size", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Size value, or null + */ + @Nullable + public SizeF getSizeF(@Nullable String key) { + unparcel(); + final Object o = mMap.get(key); + try { + return (SizeF) o; + } catch (ClassCastException e) { + typeWarning(key, o, "SizeF", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Bundle value, or null + */ + @Nullable + public Bundle getBundle(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (Bundle) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Bundle", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Parcelable value, or null + */ + @Nullable + public T getParcelable(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (T) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Parcelable", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Parcelable[] value, or null + */ + @Nullable + public Parcelable[] getParcelableArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (Parcelable[]) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Parcelable[]", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Nullable + public ArrayList getParcelableArrayList(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (ArrayList) o; + } catch (ClassCastException e) { + typeWarning(key, o, "ArrayList", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * + * @return a SparseArray of T values, or null + */ + @Nullable + public SparseArray getSparseParcelableArray(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (SparseArray) o; + } catch (ClassCastException e) { + typeWarning(key, o, "SparseArray", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Serializable value, or null + */ + @Override + @Nullable + public Serializable getSerializable(@Nullable String key) { + return super.getSerializable(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Override + @Nullable + public ArrayList getIntegerArrayList(@Nullable String key) { + return super.getIntegerArrayList(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Override + @Nullable + public ArrayList getStringArrayList(@Nullable String key) { + return super.getStringArrayList(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an ArrayList value, or null + */ + @Override + @Nullable + public ArrayList getCharSequenceArrayList(@Nullable String key) { + return super.getCharSequenceArrayList(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a byte[] value, or null + */ + @Override + @Nullable + public byte[] getByteArray(@Nullable String key) { + return super.getByteArray(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a short[] value, or null + */ + @Override + @Nullable + public short[] getShortArray(@Nullable String key) { + return super.getShortArray(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a char[] value, or null + */ + @Override + @Nullable + public char[] getCharArray(@Nullable String key) { + return super.getCharArray(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a float[] value, or null + */ + @Override + @Nullable + public float[] getFloatArray(@Nullable String key) { + return super.getFloatArray(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a CharSequence[] value, or null + */ + @Override + @Nullable + public CharSequence[] getCharSequenceArray(@Nullable String key) { + return super.getCharSequenceArray(key); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an IBinder value, or null + */ + @Nullable + public IBinder getBinder(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (IBinder) o; + } catch (ClassCastException e) { + typeWarning(key, o, "IBinder", e); + return null; + } + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return an IBinder value, or null + * + * @deprecated + * @hide This is the old name of the function. + */ + @Deprecated + @Nullable + public IBinder getIBinder(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (IBinder) o; + } catch (ClassCastException e) { + typeWarning(key, o, "IBinder", e); + return null; + } + } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Bundle createFromParcel(Parcel in) { + return in.readBundle(); + } + @Override + public Bundle[] newArray(int size) { + return new Bundle[size]; + } + }; + /** + * Report the nature of this Parcelable's contents + */ + @Override + public int describeContents() { + int mask = 0; + if (hasFileDescriptors()) { + mask |= Parcelable.CONTENTS_FILE_DESCRIPTOR; + } + return mask; + } + /** + * Writes the Bundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + @Override + public void writeToParcel(Parcel parcel, int flags) { + throw new NotImplementedError(); +// final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0); +// try { +// super.writeToParcelInner(parcel, flags); +// } finally { +// parcel.restoreAllowFds(oldAllowFds); +// } + } + /** + * Reads the Parcel contents into this Bundle, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to overwrite this bundle from. + */ + public void readFromParcel(Parcel parcel) { + super.readFromParcelInner(parcel); + throw new NotImplementedError(); +// mFlags = FLAG_ALLOW_FDS; +// maybePrefillHasFds(); + } + @Override + public synchronized String toString() { + if (mParcelledData != null) { + if (isEmptyParcel()) { + return "Bundle[EMPTY_PARCEL]"; + } else { + return "Bundle[mParcelledData.dataSize=" + + mParcelledData.dataSize() + "]"; + } + } + return "Bundle[" + mMap.toString() + "]"; + } + /** + * @hide + */ + public synchronized String toShortString() { + if (mParcelledData != null) { + if (isEmptyParcel()) { + return "EMPTY_PARCEL"; + } else { + return "mParcelledData.dataSize=" + mParcelledData.dataSize(); + } + } + return mMap.toString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/Environment.java b/AndroidCompat/src/main/java/android/os/Environment.java new file mode 100644 index 00000000..179ae876 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/Environment.java @@ -0,0 +1,96 @@ +package android.os; + +import xyz.nulldev.androidcompat.io.AndroidFiles; +import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; + +import java.io.File; + +/** + * Android compatibility layer for files + */ +public class Environment { + private static AndroidFiles androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class); + + public static String DIRECTORY_ALARMS = getHomeDirectory("Alarms").getAbsolutePath(); + public static String DIRECTORY_DCIM = getHomeDirectory("DCIM").getAbsolutePath(); + public static String DIRECTORY_DOCUMENTS = getHomeDirectory("Documents").getAbsolutePath(); + public static String DIRECTORY_DOWNLOADS = getHomeDirectory("Downloads").getAbsolutePath(); + public static String DIRECTORY_MOVIES = getHomeDirectory("Movies").getAbsolutePath(); + public static String DIRECTORY_MUSIC = getHomeDirectory("Music").getAbsolutePath(); + public static String DIRECTORY_NOTIFICATIONS = getHomeDirectory("Notifications").getAbsolutePath(); + public static String DIRECTORY_PICTURES = getHomeDirectory("Pictures").getAbsolutePath(); + public static String DIRECTORY_PODCASTS = getHomeDirectory("Podcasts").getAbsolutePath(); + public static String DIRECTORY_RINGTONES = getHomeDirectory("Ringtones").getAbsolutePath(); + public static final String MEDIA_BAD_REMOVAL = "bad_removal"; + public static final String MEDIA_CHECKING = "checking"; + public static final String MEDIA_EJECTING = "ejecting"; + public static final String MEDIA_MOUNTED = "mounted"; + public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro"; + public static final String MEDIA_NOFS = "nofs"; + public static final String MEDIA_REMOVED = "removed"; + public static final String MEDIA_SHARED = "shared"; + public static final String MEDIA_UNKNOWN = "unknown"; + public static final String MEDIA_UNMOUNTABLE = "unmountable"; + public static final String MEDIA_UNMOUNTED = "unmounted"; + + public static File getHomeDirectory(String nestedFolder) { + return new File(getExternalStorageDirectory(), nestedFolder); + } + + public static File getRootDirectory() { + return androidFiles.getRootDir(); + } + + public static File getDataDirectory() { + return androidFiles.getDataDir(); + } + + public static File getExternalStorageDirectory() { + return androidFiles.getExternalStorageDir(); + } + + public static File getExternalStoragePublicDirectory(String type) { + return androidFiles.getExternalStorageDir(); + } + + public static File getDownloadCacheDirectory() { + return androidFiles.getDownloadCacheDir(); + } + + public static String getExternalStorageState() { + return MEDIA_MOUNTED; + } + + /** @deprecated */ + @Deprecated + public static String getStorageState(File path) { + //TODO Maybe actually check? + return MEDIA_MOUNTED; + } + + public static String getExternalStorageState(File path) { + //TODO Maybe actually check? + return MEDIA_MOUNTED; + } + + public static boolean isExternalStorageRemovable() { + return false; + } + + public static boolean isExternalStorageRemovable(File path) { + //TODO Maybe actually check? + return false; + } + + public static boolean isExternalStorageEmulated() { + return false; + } + + public static boolean isExternalStorageEmulated(File path) { + return false; + } + + public static File getLegacyExternalStorageDirectory() { + return getExternalStorageDirectory(); + } +} diff --git a/AndroidCompat/src/main/java/android/os/Parcelable.java b/AndroidCompat/src/main/java/android/os/Parcelable.java new file mode 100644 index 00000000..c5b00d91 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/Parcelable.java @@ -0,0 +1,148 @@ +/* + * 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 android.os; +/** + * Interface for classes whose instances can be written to + * and restored from a {@link Parcel}. Classes implementing the Parcelable + * interface must also have a non-null static field called CREATOR + * of a type that implements the {@link Parcelable.Creator} interface. + * + *

A typical implementation of Parcelable is:

+ * + *
+ * public class MyParcelable implements Parcelable {
+ *     private int mData;
+ *
+ *     public int describeContents() {
+ *         return 0;
+ *     }
+ *
+ *     public void writeToParcel(Parcel out, int flags) {
+ *         out.writeInt(mData);
+ *     }
+ *
+ *     public static final Parcelable.Creator<MyParcelable> CREATOR
+ *             = new Parcelable.Creator<MyParcelable>() {
+ *         public MyParcelable createFromParcel(Parcel in) {
+ *             return new MyParcelable(in);
+ *         }
+ *
+ *         public MyParcelable[] newArray(int size) {
+ *             return new MyParcelable[size];
+ *         }
+ *     };
+ *     
+ *     private MyParcelable(Parcel in) {
+ *         mData = in.readInt();
+ *     }
+ * }
+ */ +public interface Parcelable { + /** + * Flag for use with {@link #writeToParcel}: the object being written + * is a return value, that is the result of a function such as + * "Parcelable someFunction()", + * "void someFunction(out Parcelable)", or + * "void someFunction(inout Parcelable)". Some implementations + * may want to release resources at this point. + */ + public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001; + /** + * Flag for use with {@link #writeToParcel}: a parent object will take + * care of managing duplicate state/data that is nominally replicated + * across its inner data members. This flag instructs the inner data + * types to omit that data during marshaling. Exact behavior may vary + * on a case by case basis. + * @hide + */ + public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002; + /* + * Bit masks for use with {@link #describeContents}: each bit represents a + * kind of object considered to have potential special significance when + * marshalled. + */ + /** + * Descriptor bit used with {@link #describeContents()}: indicates that + * the Parcelable object's flattened representation includes a file descriptor. + * + * @see #describeContents() + */ + public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001; + + /** + * Describe the kinds of special objects contained in this Parcelable + * instance's marshaled representation. For example, if the object will + * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)}, + * the return value of this method must include the + * {@link #CONTENTS_FILE_DESCRIPTOR} bit. + * + * @return a bitmask indicating the set of special object types marshaled + * by this Parcelable object instance. + * + * @see #CONTENTS_FILE_DESCRIPTOR + */ + public int describeContents(); + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + public void writeToParcel(Parcel dest, int flags); + /** + * Interface that must be implemented and provided as a public CREATOR + * field that generates instances of your Parcelable class from a Parcel. + */ + public interface Creator { + /** + * Create a new instance of the Parcelable class, instantiating it + * from the given Parcel whose data had previously been written by + * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}. + * + * @param source The Parcel to read the object's data from. + * @return Returns a new instance of the Parcelable class. + */ + public T createFromParcel(Parcel source); + + /** + * Create a new array of the Parcelable class. + * + * @param size Size of the array. + * @return Returns an array of the Parcelable class, with every entry + * initialized to null. + */ + public T[] newArray(int size); + } + /** + * Specialization of {@link Creator} that allows you to receive the + * ClassLoader the object is being created in. + */ + public interface ClassLoaderCreator extends Creator { + /** + * Create a new instance of the Parcelable class, instantiating it + * from the given Parcel whose data had previously been written by + * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and + * using the given ClassLoader. + * + * @param source The Parcel to read the object's data from. + * @param loader The ClassLoader that this object is being created in. + * @return Returns a new instance of the Parcelable class. + */ + public T createFromParcel(Parcel source, ClassLoader loader); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/PersistableBundle.java b/AndroidCompat/src/main/java/android/os/PersistableBundle.java new file mode 100644 index 00000000..54c2858f --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/PersistableBundle.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2014 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 android.os; +import android.annotation.Nullable; +import android.util.ArrayMap; +import com.android.internal.util.XmlUtils; +import kotlin.NotImplementedError; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; +import java.util.ArrayList; +/** + * A mapping from String keys to values of various types. The set of types + * supported by this class is purposefully restricted to simple objects that can + * safely be persisted to and restored from disk. + * + * @see Bundle + */ +public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, + XmlUtils.WriteMapCallback { + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; + public static final PersistableBundle EMPTY; + static { + EMPTY = new PersistableBundle(); + EMPTY.mMap = ArrayMap.EMPTY; + } + /** @hide */ + public static boolean isValidType(Object value) { + return (value instanceof Integer) || (value instanceof Long) || + (value instanceof Double) || (value instanceof String) || + (value instanceof int[]) || (value instanceof long[]) || + (value instanceof double[]) || (value instanceof String[]) || + (value instanceof PersistableBundle) || (value == null) || + (value instanceof Boolean) || (value instanceof boolean[]); + } + /** + * Constructs a new, empty PersistableBundle. + */ + public PersistableBundle() { + super(); + mFlags = FLAG_DEFUSABLE; + } + /** + * Constructs a new, empty PersistableBundle sized to hold the given number of + * elements. The PersistableBundle will grow as needed. + * + * @param capacity the initial capacity of the PersistableBundle + */ + public PersistableBundle(int capacity) { + super(capacity); + mFlags = FLAG_DEFUSABLE; + } + /** + * Constructs a PersistableBundle containing a copy of the mappings from the given + * PersistableBundle. Does only a shallow copy of the original PersistableBundle -- see + * {@link #deepCopy()} if that is not what you want. + * + * @param b a PersistableBundle to be copied. + * + * @see #deepCopy() + */ + public PersistableBundle(PersistableBundle b) { + super(b); + mFlags = b.mFlags; + } + /** + * Constructs a PersistableBundle from a Bundle. Does only a shallow copy of the Bundle. + * + * @param b a Bundle to be copied. + * + * @throws IllegalArgumentException if any element of {@code b} cannot be persisted. + * + * @hide + */ + public PersistableBundle(Bundle b) { + this(b.getMap()); + } + /** + * Constructs a PersistableBundle containing the mappings passed in. + * + * @param map a Map containing only those items that can be persisted. + * @throws IllegalArgumentException if any element of #map cannot be persisted. + */ + private PersistableBundle(ArrayMap map) { + super(); + mFlags = FLAG_DEFUSABLE; + // First stuff everything in. + putAll(map); + // Now verify each item throwing an exception if there is a violation. + final int N = mMap.size(); + for (int i=0; i) value)); + } else if (value instanceof Bundle) { + mMap.setValueAt(i, new PersistableBundle(((Bundle) value))); + } else if (!isValidType(value)) { + throw new IllegalArgumentException("Bad value in PersistableBundle key=" + + mMap.keyAt(i) + " value=" + value); + } + } + } + /* package */ PersistableBundle(Parcel parcelledData, int length) { + super(parcelledData, length); + mFlags = FLAG_DEFUSABLE; + } + /** + * Constructs a PersistableBundle without initializing it. + */ + PersistableBundle(boolean doInit) { + super(doInit); + } + /** + * Make a PersistableBundle for a single key/value pair. + * + * @hide + */ + public static PersistableBundle forPair(String key, String value) { + PersistableBundle b = new PersistableBundle(1); + b.putString(key, value); + return b; + } + /** + * Clones the current PersistableBundle. The internal map is cloned, but the keys and + * values to which it refers are copied by reference. + */ + @Override + public Object clone() { + return new PersistableBundle(this); + } + /** + * Make a deep copy of the given bundle. Traverses into inner containers and copies + * them as well, so they are not shared across bundles. Will traverse in to + * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of + * primitive arrays. Other types of objects (such as Parcelable or Serializable) + * are referenced as-is and not copied in any way. + */ + public PersistableBundle deepCopy() { + PersistableBundle b = new PersistableBundle(false); + b.copyInternal(this, true); + return b; + } + /** + * Inserts a PersistableBundle value into the mapping of this Bundle, replacing + * any existing value for the given key. Either key or value may be null. + * + * @param key a String, or null + * @param value a Bundle object, or null + */ + public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) { + unparcel(); + mMap.put(key, value); + } + /** + * Returns the value associated with the given key, or null if + * no mapping of the desired type exists for the given key or a null + * value is explicitly associated with the key. + * + * @param key a String, or null + * @return a Bundle value, or null + */ + @Nullable + public PersistableBundle getPersistableBundle(@Nullable String key) { + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (PersistableBundle) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Bundle", e); + return null; + } + } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PersistableBundle createFromParcel(Parcel in) { + return in.readPersistableBundle(); + } + @Override + public PersistableBundle[] newArray(int size) { + return new PersistableBundle[size]; + } + }; + /** @hide */ + @Override + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException { + if (v instanceof PersistableBundle) { + out.startTag(null, TAG_PERSISTABLEMAP); + out.attribute(null, "name", name); + ((PersistableBundle) v).saveToXml(out); + out.endTag(null, TAG_PERSISTABLEMAP); + } else { + throw new XmlPullParserException("Unknown Object o=" + v); + } + } + /** @hide */ + public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + unparcel(); + XmlUtils.writeMapXml(mMap, out, this); + } + /** @hide */ + static class MyReadMapCallback implements XmlUtils.ReadMapCallback { + @Override + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException { + if (TAG_PERSISTABLEMAP.equals(tag)) { + return restoreFromXml(in); + } + throw new XmlPullParserException("Unknown tag=" + tag); + } + } + /** + * Report the nature of this Parcelable's contents + */ + @Override + public int describeContents() { + return 0; + } + /** + * Writes the PersistableBundle contents to a Parcel, typically in order for + * it to be passed through an IBinder connection. + * @param parcel The parcel to copy this bundle to. + */ + @Override + public void writeToParcel(Parcel parcel, int flags) { + throw new NotImplementedError(); +// final boolean oldAllowFds = parcel.pushAllowFds(false); +// try { +// writeToParcelInner(parcel, flags); +// } finally { +// parcel.restoreAllowFds(oldAllowFds); +// } + } + /** @hide */ + public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException, + XmlPullParserException { + final int outerDepth = in.getDepth(); + final String startTag = in.getName(); + final String[] tagName = new String[1]; + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + return new PersistableBundle((ArrayMap) + XmlUtils.readThisArrayMapXml(in, startTag, tagName, + new MyReadMapCallback())); + } + } + return EMPTY; + } + @Override + synchronized public String toString() { + if (mParcelledData != null) { + if (isEmptyParcel()) { + return "PersistableBundle[EMPTY_PARCEL]"; + } else { + return "PersistableBundle[mParcelledData.dataSize=" + + mParcelledData.dataSize() + "]"; + } + } + return "PersistableBundle[" + mMap.toString() + "]"; + } + /** @hide */ + synchronized public String toShortString() { + if (mParcelledData != null) { + if (isEmptyParcel()) { + return "EMPTY_PARCEL"; + } else { + return "mParcelledData.dataSize=" + mParcelledData.dataSize(); + } + } + return mMap.toString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/PowerManager.java b/AndroidCompat/src/main/java/android/os/PowerManager.java new file mode 100644 index 00000000..203546a6 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/PowerManager.java @@ -0,0 +1,99 @@ +package android.os; + +import kotlin.NotImplementedError; + +/** + * Power manager compat class + */ +public final class PowerManager { + public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; + public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; + public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED"; + /** @deprecated */ + @Deprecated + public static final int FULL_WAKE_LOCK = 26; + public static final int ON_AFTER_RELEASE = 536870912; + public static final int PARTIAL_WAKE_LOCK = 1; + public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; + public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1; + /** @deprecated */ + @Deprecated + public static final int SCREEN_BRIGHT_WAKE_LOCK = 10; + /** @deprecated */ + @Deprecated + public static final int SCREEN_DIM_WAKE_LOCK = 6; + + public static final PowerManager INSTANCE = new PowerManager(); + + public PowerManager.WakeLock newWakeLock(int levelAndFlags, String tag) { + return new WakeLock(); + } + + public boolean isWakeLockLevelSupported(int level) { + return true; + } + + /** @deprecated */ + @Deprecated + public boolean isScreenOn() { + return true; + } + + public boolean isInteractive() { + return true; + } + + public void reboot(String reason) { + throw new NotImplementedError("This device cannot be rebooted!"); + } + + public boolean isPowerSaveMode() { + return false; + } + + public boolean isDeviceIdleMode() { + return false; + } + + public boolean isIgnoringBatteryOptimizations(String packageName) { + return true; + } + + public boolean isSustainedPerformanceModeSupported() { + return true; + } + + public final class WakeLock { + int count = 0; + + public void setReferenceCounted(boolean value) { + count = -1; + } + + public void acquire() { + if(count != -1 && count != -2) + count++; + } + + public void acquire(long timeout) { + acquire(); + } + + public void release() { + if(count > 0 || count == -1) + count--; + } + + public void release(int flags) { + release(); + } + + public boolean isHeld() { + return count > 0 || count == -1; + } + + public void setWorkSource(WorkSource ws) { + //Do nothing + } + } +} diff --git a/AndroidCompat/src/main/java/android/os/Process.java b/AndroidCompat/src/main/java/android/os/Process.java new file mode 100644 index 00000000..65a47816 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/Process.java @@ -0,0 +1,916 @@ +/* + * 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 android.os; +import android.annotation.TestApi; +import android.system.Os; +import android.system.OsConstants; +import kotlin.NotImplementedError; + +/** + * Tools for managing OS processes. + */ +public class Process { + private static final String LOG_TAG = "Process"; + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SOCKET = "zygote"; + /** + * @hide for internal use only. + */ + public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; + /** + * Defines the root UID. + * @hide + */ + public static final int ROOT_UID = 0; + /** + * Defines the UID/GID under which system code runs. + */ + public static final int SYSTEM_UID = 1000; + /** + * Defines the UID/GID under which the telephony code runs. + */ + public static final int PHONE_UID = 1001; + /** + * Defines the UID/GID for the user shell. + * @hide + */ + public static final int SHELL_UID = 2000; + /** + * Defines the UID/GID for the log group. + * @hide + */ + public static final int LOG_UID = 1007; + /** + * Defines the UID/GID for the WIFI supplicant process. + * @hide + */ + public static final int WIFI_UID = 1010; + /** + * Defines the UID/GID for the mediaserver process. + * @hide + */ + public static final int MEDIA_UID = 1013; + /** + * Defines the UID/GID for the DRM process. + * @hide + */ + public static final int DRM_UID = 1019; + /** + * Defines the UID/GID for the group that controls VPN services. + * @hide + */ + public static final int VPN_UID = 1016; + /** + * Defines the UID/GID for keystore. + * @hide + */ + public static final int KEYSTORE_UID = 1017; + /** + * Defines the UID/GID for the NFC service process. + * @hide + */ + public static final int NFC_UID = 1027; + /** + * Defines the UID/GID for the Bluetooth service process. + * @hide + */ + public static final int BLUETOOTH_UID = 1002; + /** + * Defines the GID for the group that allows write access to the internal media storage. + * @hide + */ + public static final int MEDIA_RW_GID = 1023; + /** + * Access to installed package details + * @hide + */ + public static final int PACKAGE_INFO_GID = 1032; + /** + * Defines the UID/GID for the shared RELRO file updater process. + * @hide + */ + public static final int SHARED_RELRO_UID = 1037; + /** + * Defines the UID/GID for the audioserver process. + * @hide + */ + public static final int AUDIOSERVER_UID = 1041; + /** + * Defines the UID/GID for the cameraserver process + * @hide + */ + public static final int CAMERASERVER_UID = 1047; + /** + * Defines the UID/GID for the WebView zygote process. + * @hide + */ + public static final int WEBVIEW_ZYGOTE_UID = 1051; + /** + * Defines the UID used for resource tracking for OTA updates. + * @hide + */ + public static final int OTA_UPDATE_UID = 1061; + /** {@hide} */ + public static final int NOBODY_UID = 9999; + /** + * Defines the start of a range of UIDs (and GIDs), going from this + * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning + * to applications. + */ + public static final int FIRST_APPLICATION_UID = 10000; + /** + * Last of application-specific UIDs starting at + * {@link #FIRST_APPLICATION_UID}. + */ + public static final int LAST_APPLICATION_UID = 19999; + /** + * First uid used for fully isolated sandboxed processes (with no permissions of their own) + * @hide + */ + public static final int FIRST_ISOLATED_UID = 99000; + /** + * Last uid used for fully isolated sandboxed processes (with no permissions of their own) + * @hide + */ + public static final int LAST_ISOLATED_UID = 99999; + /** + * Defines the gid shared by all applications running under the same profile. + * @hide + */ + public static final int SHARED_USER_GID = 9997; + /** + * First gid for applications to share resources. Used when forward-locking + * is enabled but all UserHandles need to be able to read the resources. + * @hide + */ + public static final int FIRST_SHARED_APPLICATION_GID = 50000; + /** + * Last gid for applications to share resources. Used when forward-locking + * is enabled but all UserHandles need to be able to read the resources. + * @hide + */ + public static final int LAST_SHARED_APPLICATION_GID = 59999; + /** {@hide} */ + public static final int FIRST_APPLICATION_CACHE_GID = 20000; + /** {@hide} */ + public static final int LAST_APPLICATION_CACHE_GID = 29999; + /** + * Standard priority of application threads. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_DEFAULT = 0; + /* + * *************************************** + * ** Keep in sync with utils/threads.h ** + * *************************************** + */ + + /** + * Lowest available thread priority. Only for those who really, really + * don't want to run if anything else is happening. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_LOWEST = 19; + + /** + * Standard priority background threads. This gives your thread a slightly + * lower than normal priority, so that it will have less chance of impacting + * the responsiveness of the user interface. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_BACKGROUND = 10; + + /** + * Standard priority of threads that are currently running a user interface + * that the user is interacting with. Applications can not normally + * change to this priority; the system will automatically adjust your + * application threads as the user moves through the UI. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_FOREGROUND = -2; + + /** + * Standard priority of system display threads, involved in updating + * the user interface. Applications can not + * normally change to this priority. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_DISPLAY = -4; + + /** + * Standard priority of the most important display threads, for compositing + * the screen and retrieving input events. Applications can not normally + * change to this priority. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; + /** + * Standard priority of audio threads. Applications can not normally + * change to this priority. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_AUDIO = -16; + /** + * Standard priority of the most important audio threads. + * Applications can not normally change to this priority. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; + /** + * Minimum increment to make a priority more favorable. + */ + public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1; + /** + * Minimum increment to make a priority less favorable. + */ + public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1; + /** + * Default scheduling policy + * @hide + */ + public static final int SCHED_OTHER = 0; + /** + * First-In First-Out scheduling policy + * @hide + */ + public static final int SCHED_FIFO = 1; + /** + * Round-Robin scheduling policy + * @hide + */ + public static final int SCHED_RR = 2; + /** + * Batch scheduling policy + * @hide + */ + public static final int SCHED_BATCH = 3; + /** + * Idle scheduling policy + * @hide + */ + public static final int SCHED_IDLE = 5; + /** + * Reset scheduler choice on fork. + * @hide + */ + public static final int SCHED_RESET_ON_FORK = 0x40000000; + // Keep in sync with SP_* constants of enum type SchedPolicy + // declared in system/core/include/cutils/sched_policy.h, + // except THREAD_GROUP_DEFAULT does not correspond to any SP_* value. + /** + * Default thread group - + * has meaning with setProcessGroup() only, cannot be used with setThreadGroup(). + * When used with setProcessGroup(), the group of each thread in the process + * is conditionally changed based on that thread's current priority, as follows: + * threads with priority numerically less than THREAD_PRIORITY_BACKGROUND + * are moved to foreground thread group. All other threads are left unchanged. + * @hide + */ + public static final int THREAD_GROUP_DEFAULT = -1; + /** + * Background thread group - All threads in + * this group are scheduled with a reduced share of the CPU. + * Value is same as constant SP_BACKGROUND of enum SchedPolicy. + * FIXME rename to THREAD_GROUP_BACKGROUND. + * @hide + */ + public static final int THREAD_GROUP_BG_NONINTERACTIVE = 0; + /** + * Foreground thread group - All threads in + * this group are scheduled with a normal share of the CPU. + * Value is same as constant SP_FOREGROUND of enum SchedPolicy. + * Not used at this level. + * @hide + **/ + private static final int THREAD_GROUP_FOREGROUND = 1; + /** + * System thread group. + * @hide + **/ + public static final int THREAD_GROUP_SYSTEM = 2; + /** + * Application audio thread group. + * @hide + **/ + public static final int THREAD_GROUP_AUDIO_APP = 3; + /** + * System audio thread group. + * @hide + **/ + public static final int THREAD_GROUP_AUDIO_SYS = 4; + /** + * Thread group for top foreground app. + * @hide + **/ + public static final int THREAD_GROUP_TOP_APP = 5; + /** + * Thread group for RT app. + * @hide + **/ + public static final int THREAD_GROUP_RT_APP = 6; + public static final int SIGNAL_QUIT = 3; + public static final int SIGNAL_KILL = 9; + public static final int SIGNAL_USR1 = 10; + private static long sStartElapsedRealtime; + private static long sStartUptimeMillis; + /** + * State associated with the zygote process. + * @hide + */ + public static final ZygoteProcess zygoteProcess = + new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET); + /** + * Start a new process. + * + *

If processes are enabled, a new process is created and the + * static main() function of a processClass is executed there. + * The process will continue running after this function returns. + * + *

If processes are not enabled, a new thread in the caller's + * process is created and main() of processClass called there. + * + *

The niceName parameter, if not an empty string, is a custom name to + * give to the process instead of using processClass. This allows you to + * make easily identifyable processes even if you are using the same base + * processClass to start them. + * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * runtimeFlags contains DEBUG_ENABLE_DEBUGGER. + * + * @param processClass The class to use as the process's main entry + * point. + * @param niceName A more readable name to use for the process. + * @param uid The user-id under which the process will run. + * @param gid The group-id under which the process will run. + * @param gids Additional group-ids associated with the process. + * @param runtimeFlags Additional flags for the runtime. + * @param targetSdkVersion The target SDK version for the app. + * @param seInfo null-ok SELinux information for the new process. + * @param abi non-null the ABI this app should be started with. + * @param instructionSet null-ok the instruction set to use. + * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. + * @param zygoteArgs Additional arguments to supply to the zygote process. + * + * @return An object that describes the result of the attempt to start the process. + * @throws RuntimeException on fatal start failure + * + * {@hide} + */ + public static final ProcessStartResult start(final String processClass, + final String niceName, + int uid, int gid, int[] gids, + int runtimeFlags, int mountExternal, + int targetSdkVersion, + String seInfo, + String abi, + String instructionSet, + String appDataDir, + String invokeWith, + String[] zygoteArgs) { + return zygoteProcess.start(processClass, niceName, uid, gid, gids, + runtimeFlags, mountExternal, targetSdkVersion, seInfo, + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + } + /** @hide */ + public static final ProcessStartResult startWebView(final String processClass, + final String niceName, + int uid, int gid, int[] gids, + int runtimeFlags, int mountExternal, + int targetSdkVersion, + String seInfo, + String abi, + String instructionSet, + String appDataDir, + String invokeWith, + String[] zygoteArgs) { + throw new NotImplementedError(); + } + /** + * Returns elapsed milliseconds of the time this process has run. + * @return Returns the number of milliseconds this process has return. + */ + public static final native long getElapsedCpuTime(); + /** + * Return the {@link SystemClock#elapsedRealtime()} at which this process was started. + */ + public static final long getStartElapsedRealtime() { + return sStartElapsedRealtime; + } + /** + * Return the {@link SystemClock#uptimeMillis()} at which this process was started. + */ + public static final long getStartUptimeMillis() { + return sStartUptimeMillis; + } + /** @hide */ + public static final void setStartTimes(long elapsedRealtime, long uptimeMillis) { + sStartElapsedRealtime = elapsedRealtime; + sStartUptimeMillis = uptimeMillis; + } + /** + * Returns true if the current process is a 64-bit runtime. + */ + public static final boolean is64Bit() { + throw new NotImplementedError(); + } + /** + * Returns the identifier of this process, which can be used with + * {@link #killProcess} and {@link #sendSignal}. + */ + public static final int myPid() { + return Os.getpid(); + } + /** + * Returns the identifier of this process' parent. + * @hide + */ + public static final int myPpid() { + return Os.getppid(); + } + /** + * Returns the identifier of the calling thread, which be used with + * {@link #setThreadPriority(int, int)}. + */ + public static final int myTid() { + return Os.gettid(); + } + /** + * Returns the identifier of this process's uid. This is the kernel uid + * that the process is running under, which is the identity of its + * app-specific sandbox. It is different from {@link #myUserHandle} in that + * a uid identifies a specific app sandbox in a specific user. + */ + public static final int myUid() { + return Os.getuid(); + } + /** + * Returns this process's user handle. This is the + * user the process is running under. It is distinct from + * {@link #myUid()} in that a particular user will have multiple + * distinct apps running under it each with their own uid. + */ + public static UserHandle myUserHandle() { + return UserHandle.of(UserHandle.getUserId(myUid())); + } + /** + * Returns whether the given uid belongs to an application. + * @param uid A kernel uid. + * @return Whether the uid corresponds to an application sandbox running in + * a specific user. + */ + public static boolean isApplicationUid(int uid) { + return UserHandle.isApp(uid); + } + /** + * Returns whether the current process is in an isolated sandbox. + * @hide + */ + public static final boolean isIsolated() { + return isIsolated(myUid()); + } + /** {@hide} */ + public static final boolean isIsolated(int uid) { + uid = UserHandle.getAppId(uid); + return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID; + } + /** + * Returns the UID assigned to a particular user name, or -1 if there is + * none. If the given string consists of only numbers, it is converted + * directly to a uid. + */ + public static final native int getUidForName(String name); + + /** + * Returns the GID assigned to a particular user name, or -1 if there is + * none. If the given string consists of only numbers, it is converted + * directly to a gid. + */ + public static final native int getGidForName(String name); + /** + * Returns a uid for a currently running process. + * @param pid the process id + * @return the uid of the process, or -1 if the process is not running. + * @hide pending API council review + */ + public static final int getUidForPid(int pid) { + String[] procStatusLabels = { "Uid:" }; + long[] procStatusValues = new long[1]; + procStatusValues[0] = -1; + Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues); + return (int) procStatusValues[0]; + } + /** + * Returns the parent process id for a currently running process. + * @param pid the process id + * @return the parent process id of the process, or -1 if the process is not running. + * @hide + */ + public static final int getParentPid(int pid) { + String[] procStatusLabels = { "PPid:" }; + long[] procStatusValues = new long[1]; + procStatusValues[0] = -1; + Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues); + return (int) procStatusValues[0]; + } + /** + * Returns the thread group leader id for a currently running thread. + * @param tid the thread id + * @return the thread group leader id of the thread, or -1 if the thread is not running. + * This is same as what getpid(2) would return if called by tid. + * @hide + */ + public static final int getThreadGroupLeader(int tid) { + String[] procStatusLabels = { "Tgid:" }; + long[] procStatusValues = new long[1]; + procStatusValues[0] = -1; + Process.readProcLines("/proc/" + tid + "/status", procStatusLabels, procStatusValues); + return (int) procStatusValues[0]; + } + /** + * Set the priority of a thread, based on Linux priorities. + * + * @param tid The identifier of the thread/process to change. + * @param priority A Linux priority level, from -20 for highest scheduling + * priority to 19 for lowest scheduling priority. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + */ + public static final native void setThreadPriority(int tid, int priority) + throws IllegalArgumentException, SecurityException; + /** + * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to + * throw an exception if passed a background-level thread priority. This is only + * effective if the JNI layer is built with GUARD_THREAD_PRIORITY defined to 1. + * + * @hide + */ + public static final native void setCanSelfBackground(boolean backgroundOk); + /** + * Sets the scheduling group for a thread. + * @hide + * @param tid The identifier of the thread to change. + * @param group The target group for this thread from THREAD_GROUP_*. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + * If the thread is a thread group leader, that is it's gettid() == getpid(), + * then the other threads in the same thread group are _not_ affected. + * + * Does not set cpuset for some historical reason, just calls + * libcutils::set_sched_policy(). + */ + public static final native void setThreadGroup(int tid, int group) + throws IllegalArgumentException, SecurityException; + /** + * Sets the scheduling group and the corresponding cpuset group + * @hide + * @param tid The identifier of the thread to change. + * @param group The target group for this thread from THREAD_GROUP_*. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + */ + public static final native void setThreadGroupAndCpuset(int tid, int group) + throws IllegalArgumentException, SecurityException; + /** + * Sets the scheduling group for a process and all child threads + * @hide + * @param pid The identifier of the process to change. + * @param group The target group for this process from THREAD_GROUP_*. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + * + * group == THREAD_GROUP_DEFAULT means to move all non-background priority + * threads to the foreground scheduling group, but to leave background + * priority threads alone. group == THREAD_GROUP_BG_NONINTERACTIVE moves all + * threads, regardless of priority, to the background scheduling group. + * group == THREAD_GROUP_FOREGROUND is not allowed. + * + * Always sets cpusets. + */ + public static final native void setProcessGroup(int pid, int group) + throws IllegalArgumentException, SecurityException; + /** + * Return the scheduling group of requested process. + * + * @hide + */ + public static final native int getProcessGroup(int pid) + throws IllegalArgumentException, SecurityException; + /** + * On some devices, the foreground process may have one or more CPU + * cores exclusively reserved for it. This method can be used to + * retrieve which cores that are (if any), so the calling process + * can then use sched_setaffinity() to lock a thread to these cores. + * Note that the calling process must currently be running in the + * foreground for this method to return any cores. + * + * The CPU core(s) exclusively reserved for the foreground process will + * stay reserved for as long as the process stays in the foreground. + * + * As soon as a process leaves the foreground, those CPU cores will + * no longer be reserved for it, and will most likely be reserved for + * the new foreground process. It's not necessary to change the affinity + * of your process when it leaves the foreground (if you had previously + * set it to use a reserved core); the OS will automatically take care + * of resetting the affinity at that point. + * + * @return an array of integers, indicating the CPU cores exclusively + * reserved for this process. The array will have length zero if no + * CPU cores are exclusively reserved for this process at this point + * in time. + */ + public static final native int[] getExclusiveCores(); + /** + * Set the priority of the calling thread, based on Linux priorities. See + * {@link #setThreadPriority(int, int)} for more information. + * + * @param priority A Linux priority level, from -20 for highest scheduling + * priority to 19 for lowest scheduling priority. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + * + * @see #setThreadPriority(int, int) + */ + public static final native void setThreadPriority(int priority) + throws IllegalArgumentException, SecurityException; + + /** + * Return the current priority of a thread, based on Linux priorities. + * + * @param tid The identifier of the thread/process. If tid equals zero, the priority of the + * calling process/thread will be returned. + * + * @return Returns the current priority, as a Linux priority level, + * from -20 for highest scheduling priority to 19 for lowest scheduling + * priority. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist. + */ + public static final native int getThreadPriority(int tid) + throws IllegalArgumentException; + + /** + * Return the current scheduling policy of a thread, based on Linux. + * + * @param tid The identifier of the thread/process to get the scheduling policy. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist, or if priority is out of range for the policy. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * scheduling policy or priority. + * + * {@hide} + */ + + @TestApi + public static final native int getThreadScheduler(int tid) + throws IllegalArgumentException; + /** + * Set the scheduling policy and priority of a thread, based on Linux. + * + * @param tid The identifier of the thread/process to change. + * @param policy A Linux scheduling policy such as SCHED_OTHER etc. + * @param priority A Linux priority level in a range appropriate for the given policy. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * tid does not exist, or if priority is out of range for the policy. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * scheduling policy or priority. + * + * {@hide} + */ + public static final native void setThreadScheduler(int tid, int policy, int priority) + throws IllegalArgumentException; + /** + * Determine whether the current environment supports multiple processes. + * + * @return Returns true if the system can run in multiple processes, else + * false if everything is running in a single process. + * + * @deprecated This method always returns true. Do not use. + */ + @Deprecated + public static final boolean supportsProcesses() { + return true; + } + /** + * Adjust the swappiness level for a process. + * + * @param pid The process identifier to set. + * @param is_increased Whether swappiness should be increased or default. + * + * @return Returns true if the underlying system supports this + * feature, else false. + * + * {@hide} + */ + public static final native boolean setSwappiness(int pid, boolean is_increased); + /** + * Change this process's argv[0] parameter. This can be useful to show + * more descriptive information in things like the 'ps' command. + * + * @param text The new name of this process. + * + * {@hide} + */ + public static final native void setArgV0(String text); + /** + * Kill the process with the given PID. + * Note that, though this API allows us to request to + * kill any process based on its PID, the kernel will + * still impose standard restrictions on which PIDs you + * are actually able to kill. Typically this means only + * the process running the caller's packages/application + * and any additional processes created by that app; packages + * sharing a common UID will also be able to kill each + * other's processes. + */ + public static final void killProcess(int pid) { + sendSignal(pid, SIGNAL_KILL); + } + /** @hide */ + public static final native int setUid(int uid); + /** @hide */ + public static final native int setGid(int uid); + /** + * Send a signal to the given process. + * + * @param pid The pid of the target process. + * @param signal The signal to send. + */ + public static final native void sendSignal(int pid, int signal); + + /** + * @hide + * Private impl for avoiding a log message... DO NOT USE without doing + * your own log, or the Android Illuminati will find you some night and + * beat you up. + */ + public static final void killProcessQuiet(int pid) { + sendSignalQuiet(pid, SIGNAL_KILL); + } + /** + * @hide + * Private impl for avoiding a log message... DO NOT USE without doing + * your own log, or the Android Illuminati will find you some night and + * beat you up. + */ + public static final native void sendSignalQuiet(int pid, int signal); + + /** @hide */ + public static final native long getFreeMemory(); + + /** @hide */ + public static final native long getTotalMemory(); + + /** @hide */ + public static final native void readProcLines(String path, + String[] reqFields, long[] outSizes); + + /** @hide */ + public static final native int[] getPids(String path, int[] lastArray); + + /** @hide */ + public static final int PROC_TERM_MASK = 0xff; + /** @hide */ + public static final int PROC_ZERO_TERM = 0; + /** @hide */ + public static final int PROC_SPACE_TERM = (int)' '; + /** @hide */ + public static final int PROC_TAB_TERM = (int)'\t'; + /** @hide */ + public static final int PROC_COMBINE = 0x100; + /** @hide */ + public static final int PROC_PARENS = 0x200; + /** @hide */ + public static final int PROC_QUOTES = 0x400; + /** @hide */ + public static final int PROC_CHAR = 0x800; + /** @hide */ + public static final int PROC_OUT_STRING = 0x1000; + /** @hide */ + public static final int PROC_OUT_LONG = 0x2000; + /** @hide */ + public static final int PROC_OUT_FLOAT = 0x4000; + + /** @hide */ + public static final native boolean readProcFile(String file, int[] format, + String[] outStrings, long[] outLongs, float[] outFloats); + + /** @hide */ + public static final native boolean parseProcLine(byte[] buffer, int startIndex, + int endIndex, int[] format, String[] outStrings, long[] outLongs, float[] outFloats); + /** @hide */ + public static final native int[] getPidsForCommands(String[] cmds); + /** + * Gets the total Pss value for a given process, in bytes. + * + * @param pid the process to the Pss for + * @return the total Pss value for the given process in bytes, + * or -1 if the value cannot be determined + * @hide + */ + public static final native long getPss(int pid); + /** + * Specifies the outcome of having started a process. + * @hide + */ + public static final class ProcessStartResult { + /** + * The PID of the newly started process. + * Always >= 0. (If the start failed, an exception will have been thrown instead.) + */ + public int pid; + /** + * True if the process was started with a wrapper attached. + */ + public boolean usingWrapper; + } + /** + * Kill all processes in a process group started for the given + * pid. + * @hide + */ + public static final native int killProcessGroup(int uid, int pid); + /** + * Remove all process groups. Expected to be called when ActivityManager + * is restarted. + * @hide + */ + public static final native void removeAllProcessGroups(); + /** + * Check to see if a thread belongs to a given process. This may require + * more permissions than apps generally have. + * @return true if this thread belongs to a process + * @hide + */ + public static final boolean isThreadInProcess(int tid, int pid) { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + if (Os.access("/proc/" + tid + "/task/" + pid, OsConstants.F_OK)) { + return true; + } else { + return false; + } + } catch (Exception e) { + return false; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/SimpleClock.java b/AndroidCompat/src/main/java/android/os/SimpleClock.java new file mode 100644 index 00000000..b35b1c56 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/SimpleClock.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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 android.os; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; + +/** + * {@hide} + */ +public abstract class SimpleClock extends Clock { + private final ZoneId zone; + + public SimpleClock(ZoneId zone) { + this.zone = zone; + } + + @Override + public ZoneId getZone() { + return zone; + } + + @Override + public Clock withZone(ZoneId zone) { + return new SimpleClock(zone) { + @Override + public long millis() { + return SimpleClock.this.millis(); + } + }; + } + + @Override + public abstract long millis(); + + @Override + public Instant instant() { + return Instant.ofEpochMilli(millis()); + } +} + diff --git a/AndroidCompat/src/main/java/android/os/SystemClock.java b/AndroidCompat/src/main/java/android/os/SystemClock.java new file mode 100644 index 00000000..30a85845 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/SystemClock.java @@ -0,0 +1,296 @@ +/* + * 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 android.os; + +import android.annotation.NonNull; + +import java.lang.management.ManagementFactory; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.ZoneOffset; + +/** + * Core timekeeping facilities. + * + *

Three different clocks are available, and they should not be confused: + * + *

    + *
  • {@link System#currentTimeMillis System.currentTimeMillis()} + * is the standard "wall" clock (time and date) expressing milliseconds + * since the epoch. The wall clock can be set by the user or the phone + * network (see {@link #setCurrentTimeMillis}), so the time may jump + * backwards or forwards unpredictably. This clock should only be used + * when correspondence with real-world dates and times is important, such + * as in a calendar or alarm clock application. Interval or elapsed + * time measurements should use a different clock. If you are using + * System.currentTimeMillis(), consider listening to the + * {@link android.content.Intent#ACTION_TIME_TICK ACTION_TIME_TICK}, + * {@link android.content.Intent#ACTION_TIME_CHANGED ACTION_TIME_CHANGED} + * and {@link android.content.Intent#ACTION_TIMEZONE_CHANGED + * ACTION_TIMEZONE_CHANGED} {@link android.content.Intent Intent} + * broadcasts to find out when the time changes. + * + *

  • {@link #uptimeMillis} is counted in milliseconds since the + * system was booted. This clock stops when the system enters deep + * sleep (CPU off, display dark, device waiting for external input), + * but is not affected by clock scaling, idle, or other power saving + * mechanisms. This is the basis for most interval timing + * such as {@link Thread#sleep(long) Thread.sleep(millls)}, + * {@link Object#wait(long) Object.wait(millis)}, and + * {@link System#nanoTime System.nanoTime()}. This clock is guaranteed + * to be monotonic, and is suitable for interval timing when the + * interval does not span device sleep. Most methods that accept a + * timestamp value currently expect the {@link #uptimeMillis} clock. + * + *

  • {@link #elapsedRealtime} and {@link #elapsedRealtimeNanos} + * return the time since the system was booted, and include deep sleep. + * This clock is guaranteed to be monotonic, and continues to tick even + * when the CPU is in power saving modes, so is the recommend basis + * for general purpose interval timing. + * + *

+ *

+ * There are several mechanisms for controlling the timing of events: + * + *

    + *
  • Standard functions like {@link Thread#sleep(long) + * Thread.sleep(millis)} and {@link Object#wait(long) Object.wait(millis)} + * are always available. These functions use the {@link #uptimeMillis} + * clock; if the device enters sleep, the remainder of the time will be + * postponed until the device wakes up. These synchronous functions may + * be interrupted with {@link Thread#interrupt Thread.interrupt()}, and + * you must handle {@link InterruptedException}. + * + *

  • {@link #sleep SystemClock.sleep(millis)} is a utility function + * very similar to {@link Thread#sleep(long) Thread.sleep(millis)}, but it + * ignores {@link InterruptedException}. Use this function for delays if + * you do not use {@link Thread#interrupt Thread.interrupt()}, as it will + * preserve the interrupted state of the thread. + * + *

  • The {@link android.os.Handler} class can schedule asynchronous + * callbacks at an absolute or relative time. Handler objects also use the + * {@link #uptimeMillis} clock, and require an {@link android.os.Looper + * event loop} (normally present in any GUI application). + * + *

  • The {@link android.app.AlarmManager} can trigger one-time or + * recurring events which occur even when the device is in deep sleep + * or your application is not running. Events may be scheduled with your + * choice of {@link java.lang.System#currentTimeMillis} (RTC) or + * {@link #elapsedRealtime} (ELAPSED_REALTIME), and cause an + * {@link android.content.Intent} broadcast when they occur. + *

+ */ +public final class SystemClock { + private static final String TAG = "SystemClock"; + + /** + * This class is uninstantiable. + */ + private SystemClock() { + // This space intentionally left blank. + } + + /** + * Waits a given number of milliseconds (of uptimeMillis) before returning. + * Similar to {@link java.lang.Thread#sleep(long)}, but does not throw + * {@link InterruptedException}; {@link Thread#interrupt()} events are + * deferred until the next interruptible operation. Does not return until + * at least the specified number of milliseconds has elapsed. + * + * @param ms to sleep before returning, in milliseconds of uptime. + */ + public static void sleep(long ms) { + long start = uptimeMillis(); + long duration = ms; + boolean interrupted = false; + do { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + interrupted = true; + } + duration = start + ms - uptimeMillis(); + } while (duration > 0); + if (interrupted) { + // Important: we don't want to quietly eat an interrupt() event, + // so we make sure to re-interrupt the thread so that the next + // call to Thread.sleep() or Object.wait() will be interrupted. + Thread.currentThread().interrupt(); + } + } + + /** + * Sets the current wall time, in milliseconds. Requires the calling + * process to have appropriate permissions. + * + * @return if the clock was successfully set to the specified time. + */ + public static boolean setCurrentTimeMillis(long millis) { + // Permission denied + return false; + } + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * + * @return milliseconds of non-sleep uptime since boot. + */ + public static long uptimeMillis() { + return ManagementFactory.getRuntimeMXBean().getUptime(); + } + + /** + * @removed + */ + @Deprecated + public static @NonNull + Clock uptimeMillisClock() { + return uptimeClock(); + } + + /** + * Return {@link Clock} that starts at system boot, not counting time spent + * in deep sleep. + * + * @removed + */ + public static @NonNull + Clock uptimeClock() { + return new SimpleClock(ZoneOffset.UTC) { + @Override + public long millis() { + return SystemClock.uptimeMillis(); + } + }; + } + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @return elapsed milliseconds since boot. + */ + public static long elapsedRealtime() { + // Just return JVM uptime, no application will ever need the real system uptime + return uptimeMillis(); + } + + /** + * Return {@link Clock} that starts at system boot, including time spent in + * sleep. + * + * @removed + */ + public static @NonNull + Clock elapsedRealtimeClock() { + return new SimpleClock(ZoneOffset.UTC) { + @Override + public long millis() { + return SystemClock.elapsedRealtime(); + } + }; + } + + /** + * Returns nanoseconds since boot, including time spent in sleep. + * + * @return elapsed nanoseconds since boot. + */ + public static long elapsedRealtimeNanos() { + // Just convert ms realtime to nanos, we can't get nano uptime + return elapsedRealtime() * 1000000; + } + + /** + * Returns milliseconds running in the current thread. + * + * @return elapsed milliseconds in the thread + */ + public static long currentThreadTimeMillis() { + // Return JVM uptime instead, we have no means of getting this info + return uptimeMillis(); + } + + /** + * Returns microseconds running in the current thread. + * + * @return elapsed microseconds in the thread + * @hide + */ + public static long currentThreadTimeMicro() { + // Return JVM uptime converted to microseconds instead, we have no means of getting this info + return uptimeMillis() * 1000; + } + + /** + * Returns current wall time in microseconds. + * + * @return elapsed microseconds in wall time + * @hide + */ + public static long currentTimeMicro() { + // Return millis converted to microseconds, we have no means of getting system time in microseconds + return System.currentTimeMillis() * 1000; + } + + /** + * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized + * using a remote network source outside the device. + *

+ * While the time returned by {@link System#currentTimeMillis()} can be + * adjusted by the user, the time returned by this method cannot be adjusted + * by the user. Note that synchronization may occur using an insecure + * network protocol, so the returned time should not be used for security + * purposes. + *

+ * This performs no blocking network operations and returns values based on + * a recent successful synchronization event; it will either return a valid + * time or throw. + * + * @throws DateTimeException when no accurate network time can be provided. + * @hide + */ + public static long currentNetworkTimeMillis() { + // No need for such accurate time + return System.currentTimeMillis(); + } + + /** + * Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC, + * synchronized using a remote network source outside the device. + *

+ * While the time returned by {@link System#currentTimeMillis()} can be + * adjusted by the user, the time returned by this method cannot be adjusted + * by the user. Note that synchronization may occur using an insecure + * network protocol, so the returned time should not be used for security + * purposes. + *

+ * This performs no blocking network operations and returns values based on + * a recent successful synchronization event; it will either return a valid + * time or throw. + * + * @throws DateTimeException when no accurate network time can be provided. + * @hide + */ + public static @NonNull + Clock currentNetworkTimeClock() { + return new SimpleClock(ZoneOffset.UTC) { + @Override + public long millis() { + return SystemClock.currentNetworkTimeMillis(); + } + }; + } +} diff --git a/AndroidCompat/src/main/java/android/os/SystemProperties.java b/AndroidCompat/src/main/java/android/os/SystemProperties.java new file mode 100644 index 00000000..2c092e0d --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/SystemProperties.java @@ -0,0 +1,126 @@ +/* + * 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 android.os; + +import kotlin.NotImplementedError; +import xyz.nulldev.androidcompat.config.SystemConfigModule; +import xyz.nulldev.ts.config.ConfigManager; +import xyz.nulldev.ts.config.GlobalConfigManager; + +/** + * Gives access to the system properties store. The system properties + * store contains a list of string key-value pairs. + * + * {@hide} + */ +public class SystemProperties { + private static ConfigManager configManager = GlobalConfigManager.INSTANCE; + private static SystemConfigModule configModule = configManager.module(SystemConfigModule.class); + + public static final int PROP_VALUE_MAX = 91; + + private static String native_get(String key) { + return configModule.getStringProperty(key); + } + private static String native_get(String key, String def) { + if(!configModule.hasProperty(key)) + return def; + else + return native_get(key); + } + private static int native_get_int(String key, int def) { + if(configModule.hasProperty(key)) + return def; + else + return configModule.getIntProperty(key); + } + private static long native_get_long(String key, long def) { + if(configModule.hasProperty(key)) + return def; + else + return configModule.getLongProperty(key); + } + private static boolean native_get_boolean(String key, boolean def) { + if(configModule.hasProperty(key)) + return def; + else + return configModule.getBooleanProperty(key); + } + private static void native_set(String key, String def) { + throw new NotImplementedError("TODO"); + } + + /** + * Get the value for the given key. + * @return an empty string if the key isn't found + */ + public static String get(String key) { + return native_get(key); + } + /** + * Get the value for the given key. + * @return if the key isn't found, return def if it isn't null, or an empty string otherwise + */ + public static String get(String key, String def) { + return native_get(key, def); + } + /** + * Get the value for the given key, and return as an integer. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as an integer, or def if the key isn't found or + * cannot be parsed + */ + public static int getInt(String key, int def) { + return native_get_int(key, def); + } + /** + * Get the value for the given key, and return as a long. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as a long, or def if the key isn't found or + * cannot be parsed + */ + public static long getLong(String key, long def) { + return native_get_long(key, def); + } + /** + * Get the value for the given key, returned as a boolean. + * Values 'n', 'no', '0', 'false' or 'off' are considered false. + * Values 'y', 'yes', '1', 'true' or 'on' are considered true. + * (case sensitive). + * If the key does not exist, or has any other value, then the default + * result is returned. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as a boolean, or def if the key isn't found or is + * not able to be parsed as a boolean. + */ + public static boolean getBoolean(String key, boolean def) { + return native_get_boolean(key, def); + } + /** + * Set the value for the given key. + * @throws IllegalArgumentException if the value exceeds 92 characters + */ + public static void set(String key, String val) { + if (val != null && val.length() > PROP_VALUE_MAX) { + throw new IllegalArgumentException("val.length > " + + PROP_VALUE_MAX); + } + native_set(key, val); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/UserHandle.java b/AndroidCompat/src/main/java/android/os/UserHandle.java new file mode 100644 index 00000000..4b78b913 --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/UserHandle.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2011 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 android.os; +import android.annotation.AppIdInt; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.annotation.UserIdInt; +import java.io.PrintWriter; +/** + * Representation of a user on the device. + */ +public final class UserHandle implements Parcelable { + // NOTE: keep logic in sync with system/core/libcutils/multiuser.c + /** + * @hide Range of uids allocated for a user. + */ + public static final int PER_USER_RANGE = 100000; + /** @hide A user id to indicate all users on the device */ + public static final @UserIdInt int USER_ALL = -1; + /** @hide A user handle to indicate all users on the device */ + public static final UserHandle ALL = new UserHandle(USER_ALL); + /** @hide A user id to indicate the currently active user */ + public static final @UserIdInt int USER_CURRENT = -2; + /** @hide A user handle to indicate the current user of the device */ + public static final UserHandle CURRENT = new UserHandle(USER_CURRENT); + /** @hide A user id to indicate that we would like to send to the current + * user, but if this is calling from a user process then we will send it + * to the caller's user instead of failing with a security exception */ + public static final @UserIdInt int USER_CURRENT_OR_SELF = -3; + /** @hide A user handle to indicate that we would like to send to the current + * user, but if this is calling from a user process then we will send it + * to the caller's user instead of failing with a security exception */ + public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF); + /** @hide An undefined user id */ + public static final @UserIdInt int USER_NULL = -10000; + /** + * @hide A user id constant to indicate the "owner" user of the device + * @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or + * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. + */ + @Deprecated + public static final @UserIdInt int USER_OWNER = 0; + /** + * @hide A user handle to indicate the primary/owner user of the device + * @deprecated Consider using either {@link UserHandle#SYSTEM} constant or + * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. + */ + @Deprecated + public static final UserHandle OWNER = new UserHandle(USER_OWNER); + /** @hide A user id constant to indicate the "system" user of the device */ + public static final @UserIdInt int USER_SYSTEM = 0; + /** @hide A user serial constant to indicate the "system" user of the device */ + public static final int USER_SERIAL_SYSTEM = 0; + /** @hide A user handle to indicate the "system" user of the device */ + public static final UserHandle SYSTEM = new UserHandle(USER_SYSTEM); + /** + * @hide Enable multi-user related side effects. Set this to false if + * there are problems with single user use-cases. + */ + public static final boolean MU_ENABLED = true; + /** @hide */ + public static final int ERR_GID = -1; + /** @hide */ + public static final int AID_ROOT = android.os.Process.ROOT_UID; + /** @hide */ + public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID; + /** @hide */ + public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID; + /** @hide */ + public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID; + /** @hide */ + public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID; + final int mHandle; + /** + * Checks to see if the user id is the same for the two uids, i.e., they belong to the same + * user. + * @hide + */ + public static boolean isSameUser(int uid1, int uid2) { + return getUserId(uid1) == getUserId(uid2); + } + /** + * Checks to see if both uids are referring to the same app id, ignoring the user id part of the + * uids. + * @param uid1 uid to compare + * @param uid2 other uid to compare + * @return whether the appId is the same for both uids + * @hide + */ + public static boolean isSameApp(int uid1, int uid2) { + return getAppId(uid1) == getAppId(uid2); + } + /** @hide */ + public static boolean isIsolated(int uid) { + if (uid > 0) { + final int appId = getAppId(uid); + return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID; + } else { + return false; + } + } + /** @hide */ + public static boolean isApp(int uid) { + if (uid > 0) { + final int appId = getAppId(uid); + return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID; + } else { + return false; + } + } + /** + * Returns the user for a given uid. + * @param uid A uid for an application running in a particular user. + * @return A {@link UserHandle} for that user. + */ + public static UserHandle getUserHandleForUid(int uid) { + return of(getUserId(uid)); + } + /** + * Returns the user id for a given uid. + * @hide + */ + public static @UserIdInt int getUserId(int uid) { + if (MU_ENABLED) { + return uid / PER_USER_RANGE; + } else { + return UserHandle.USER_SYSTEM; + } + } + /** @hide */ + public static @UserIdInt int getCallingUserId() { + return getUserId(Binder.getCallingUid()); + } + /** @hide */ + public static @AppIdInt int getCallingAppId() { + return getAppId(Binder.getCallingUid()); + } + /** @hide */ + @SystemApi + public static UserHandle of(@UserIdInt int userId) { + return userId == USER_SYSTEM ? SYSTEM : new UserHandle(userId); + } + /** + * Returns the uid that is composed from the userId and the appId. + * @hide + */ + public static int getUid(@UserIdInt int userId, @AppIdInt int appId) { + if (MU_ENABLED) { + return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); + } else { + return appId; + } + } + /** + * Returns the app id (or base uid) for a given uid, stripping out the user id from it. + * @hide + */ + @TestApi + public static @AppIdInt int getAppId(int uid) { + return uid % PER_USER_RANGE; + } + /** + * Returns the gid shared between all apps with this userId. + * @hide + */ + public static int getUserGid(@UserIdInt int userId) { + return getUid(userId, Process.SHARED_USER_GID); + } + /** @hide */ + public static int getSharedAppGid(int uid) { + return getSharedAppGid(getUserId(uid), getAppId(uid)); + } + /** @hide */ + public static int getSharedAppGid(int userId, int appId) { + if (appId >= AID_APP_START && appId <= AID_APP_END) { + return (appId - AID_APP_START) + AID_SHARED_GID_START; + } else if (appId >= AID_ROOT && appId <= AID_APP_START) { + return appId; + } else { + return -1; + } + } + /** + * Returns the app id for a given shared app gid. Returns -1 if the ID is invalid. + * @hide + */ + public static @AppIdInt int getAppIdFromSharedAppGid(int gid) { + final int appId = getAppId(gid) + Process.FIRST_APPLICATION_UID + - Process.FIRST_SHARED_APPLICATION_GID; + if (appId < 0 || appId >= Process.FIRST_SHARED_APPLICATION_GID) { + return -1; + } + return appId; + } + /** @hide */ + public static int getCacheAppGid(int uid) { + return getCacheAppGid(getUserId(uid), getAppId(uid)); + } + /** @hide */ + public static int getCacheAppGid(int userId, int appId) { + if (appId >= AID_APP_START && appId <= AID_APP_END) { + return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START); + } else { + return -1; + } + } + /** + * Generate a text representation of the uid, breaking out its individual + * components -- user, app, isolated, etc. + * @hide + */ + public static void formatUid(StringBuilder sb, int uid) { + if (uid < Process.FIRST_APPLICATION_UID) { + sb.append(uid); + } else { + sb.append('u'); + sb.append(getUserId(uid)); + final int appId = getAppId(uid); + if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { + sb.append('i'); + sb.append(appId - Process.FIRST_ISOLATED_UID); + } else if (appId >= Process.FIRST_APPLICATION_UID) { + sb.append('a'); + sb.append(appId - Process.FIRST_APPLICATION_UID); + } else { + sb.append('s'); + sb.append(appId); + } + } + } + /** + * Generate a text representation of the uid, breaking out its individual + * components -- user, app, isolated, etc. + * @hide + */ + public static String formatUid(int uid) { + StringBuilder sb = new StringBuilder(); + formatUid(sb, uid); + return sb.toString(); + } + /** + * Generate a text representation of the uid, breaking out its individual + * components -- user, app, isolated, etc. + * @hide + */ + public static void formatUid(PrintWriter pw, int uid) { + if (uid < Process.FIRST_APPLICATION_UID) { + pw.print(uid); + } else { + pw.print('u'); + pw.print(getUserId(uid)); + final int appId = getAppId(uid); + if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { + pw.print('i'); + pw.print(appId - Process.FIRST_ISOLATED_UID); + } else if (appId >= Process.FIRST_APPLICATION_UID) { + pw.print('a'); + pw.print(appId - Process.FIRST_APPLICATION_UID); + } else { + pw.print('s'); + pw.print(appId); + } + } + } + /** @hide */ + public static @UserIdInt int parseUserArg(String arg) { + int userId; + if ("all".equals(arg)) { + userId = UserHandle.USER_ALL; + } else if ("current".equals(arg) || "cur".equals(arg)) { + userId = UserHandle.USER_CURRENT; + } else { + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Bad user number: " + arg); + } + } + return userId; + } + /** + * Returns the user id of the current process + * @return user id of the current process + * @hide + */ + @SystemApi + public static @UserIdInt int myUserId() { + return getUserId(Process.myUid()); + } + /** + * Returns true if this UserHandle refers to the owner user; false otherwise. + * @return true if this UserHandle refers to the owner user; false otherwise. + * @hide + * @deprecated please use {@link #isSystem()} or check for + * {@link android.content.pm.UserInfo#isPrimary()} + * {@link android.content.pm.UserInfo#isAdmin()} based on your particular use case. + */ + @Deprecated + @SystemApi + public boolean isOwner() { + return this.equals(OWNER); + } + /** + * @return true if this UserHandle refers to the system user; false otherwise. + * @hide + */ + @SystemApi + public boolean isSystem() { + return this.equals(SYSTEM); + } + /** @hide */ + public UserHandle(int h) { + mHandle = h; + } + /** + * Returns the userId stored in this UserHandle. + * @hide + */ + @SystemApi + @TestApi + public @UserIdInt int getIdentifier() { + return mHandle; + } + @Override + public String toString() { + return "UserHandle{" + mHandle + "}"; + } + @Override + public boolean equals(Object obj) { + try { + if (obj != null) { + UserHandle other = (UserHandle)obj; + return mHandle == other.mHandle; + } + } catch (ClassCastException e) { + } + return false; + } + @Override + public int hashCode() { + return mHandle; + } + + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mHandle); + } + /** + * Write a UserHandle to a Parcel, handling null pointers. Must be + * read with {@link #readFromParcel(Parcel)}. + * + * @param h The UserHandle to be written. + * @param out The Parcel in which the UserHandle will be placed. + * + * @see #readFromParcel(Parcel) + */ + public static void writeToParcel(UserHandle h, Parcel out) { + if (h != null) { + h.writeToParcel(out, 0); + } else { + out.writeInt(USER_NULL); + } + } + + /** + * Read a UserHandle from a Parcel that was previously written + * with {@link #writeToParcel(UserHandle, Parcel)}, returning either + * a null or new object as appropriate. + * + * @param in The Parcel from which to read the UserHandle + * @return Returns a new UserHandle matching the previously written + * object, or null if a null had been written. + * + * @see #writeToParcel(UserHandle, Parcel) + */ + public static UserHandle readFromParcel(Parcel in) { + int h = in.readInt(); + return h != USER_NULL ? new UserHandle(h) : null; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public UserHandle createFromParcel(Parcel in) { + return new UserHandle(in); + } + public UserHandle[] newArray(int size) { + return new UserHandle[size]; + } + }; + /** + * Instantiate a new UserHandle from the data in a Parcel that was + * previously written with {@link #writeToParcel(Parcel, int)}. Note that you + * must not use this with data written by + * {@link #writeToParcel(UserHandle, Parcel)} since it is not possible + * to handle a null UserHandle here. + * + * @param in The Parcel containing the previously written UserHandle, + * positioned at the location in the buffer where it was written. + */ + public UserHandle(Parcel in) { + mHandle = in.readInt(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/os/ZygoteProcess.java b/AndroidCompat/src/main/java/android/os/ZygoteProcess.java new file mode 100644 index 00000000..77a0baec --- /dev/null +++ b/AndroidCompat/src/main/java/android/os/ZygoteProcess.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2016 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 android.os; + +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.util.Log; +import com.android.internal.util.Preconditions; +import kotlin.NotImplementedError; + +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +/*package*/ class ZygoteStartFailedEx extends Exception { + ZygoteStartFailedEx(String s) { + super(s); + } + ZygoteStartFailedEx(Throwable cause) { + super(cause); + } + ZygoteStartFailedEx(String s, Throwable cause) { + super(s, cause); + } +} +/** + * Maintains communication state with the zygote processes. This class is responsible + * for the sockets opened to the zygotes and for starting processes on behalf of the + * {@link android.os.Process} class. + * + * {@hide} + */ +public class ZygoteProcess { + private static final String LOG_TAG = "ZygoteProcess"; + /** + * The name of the socket used to communicate with the primary zygote. + */ + private final String mSocket; + /** + * The name of the secondary (alternate ABI) zygote socket. + */ + private final String mSecondarySocket; + public ZygoteProcess(String primarySocket, String secondarySocket) { + mSocket = primarySocket; + mSecondarySocket = secondarySocket; + } + /** + * State for communicating with the zygote process. + */ + public static class ZygoteState { + final LocalSocket socket; + final DataInputStream inputStream; + final BufferedWriter writer; + final List abiList; + boolean mClosed; + private ZygoteState(LocalSocket socket, DataInputStream inputStream, + BufferedWriter writer, List abiList) { + this.socket = socket; + this.inputStream = inputStream; + this.writer = writer; + this.abiList = abiList; + } + public static ZygoteState connect(String socketAddress) throws IOException { + DataInputStream zygoteInputStream = null; + BufferedWriter zygoteWriter = null; + final LocalSocket zygoteSocket = new LocalSocket(); + try { + zygoteSocket.connect(new LocalSocketAddress(socketAddress, + LocalSocketAddress.Namespace.RESERVED)); + zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream()); + zygoteWriter = new BufferedWriter(new OutputStreamWriter( + zygoteSocket.getOutputStream()), 256); + } catch (IOException ex) { + try { + zygoteSocket.close(); + } catch (IOException ignore) { + } + throw ex; + } + String abiListString = getAbiList(zygoteWriter, zygoteInputStream); + Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: " + + abiListString); + return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, + Arrays.asList(abiListString.split(","))); + } + boolean matches(String abi) { + return abiList.contains(abi); + } + public void close() { + try { + socket.close(); + } catch (IOException ex) { + Log.e(LOG_TAG,"I/O exception on routine close", ex); + } + mClosed = true; + } + boolean isClosed() { + return mClosed; + } + } + /** + * Lock object to protect access to the two ZygoteStates below. This lock must be + * acquired while communicating over the ZygoteState's socket, to prevent + * interleaved access. + */ + private final Object mLock = new Object(); + /** + * The state of the connection to the primary zygote. + */ + private ZygoteState primaryZygoteState; + /** + * The state of the connection to the secondary zygote. + */ + private ZygoteState secondaryZygoteState; + /** + * Start a new process. + * + *

If processes are enabled, a new process is created and the + * static main() function of a processClass is executed there. + * The process will continue running after this function returns. + * + *

If processes are not enabled, a new thread in the caller's + * process is created and main() of processClass called there. + * + *

The niceName parameter, if not an empty string, is a custom name to + * give to the process instead of using processClass. This allows you to + * make easily identifyable processes even if you are using the same base + * processClass to start them. + * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * runtimeFlags contains DEBUG_ENABLE_DEBUGGER. + * + * @param processClass The class to use as the process's main entry + * point. + * @param niceName A more readable name to use for the process. + * @param uid The user-id under which the process will run. + * @param gid The group-id under which the process will run. + * @param gids Additional group-ids associated with the process. + * @param runtimeFlags Additional flags. + * @param targetSdkVersion The target SDK version for the app. + * @param seInfo null-ok SELinux information for the new process. + * @param abi non-null the ABI this app should be started with. + * @param instructionSet null-ok the instruction set to use. + * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. + * @param zygoteArgs Additional arguments to supply to the zygote process. + * + * @return An object that describes the result of the attempt to start the process. + * @throws RuntimeException on fatal start failure + */ + public final Process.ProcessStartResult start(final String processClass, + final String niceName, + int uid, int gid, int[] gids, + int runtimeFlags, int mountExternal, + int targetSdkVersion, + String seInfo, + String abi, + String instructionSet, + String appDataDir, + String invokeWith, + String[] zygoteArgs) { + try { + return startViaZygote(processClass, niceName, uid, gid, gids, + runtimeFlags, mountExternal, targetSdkVersion, seInfo, + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + } catch (ZygoteStartFailedEx ex) { + Log.e(LOG_TAG, + "Starting VM process through Zygote failed"); + throw new RuntimeException( + "Starting VM process through Zygote failed", ex); + } + } + /** retry interval for opening a zygote socket */ + static final int ZYGOTE_RETRY_MILLIS = 500; + /** + * Queries the zygote for the list of ABIS it supports. + * + * @throws ZygoteStartFailedEx if the query failed. + */ + private static String getAbiList(BufferedWriter writer, DataInputStream inputStream) + throws IOException { + // Each query starts with the argument count (1 in this case) + writer.write("1"); + // ... followed by a new-line. + writer.newLine(); + // ... followed by our only argument. + writer.write("--query-abi-list"); + writer.newLine(); + writer.flush(); + // The response is a length prefixed stream of ASCII bytes. + int numBytes = inputStream.readInt(); + byte[] bytes = new byte[numBytes]; + inputStream.readFully(bytes); + return new String(bytes, StandardCharsets.US_ASCII); + } + /** + * Sends an argument list to the zygote process, which starts a new child + * and returns the child's pid. Please note: the present implementation + * replaces newlines in the argument list with spaces. + * + * @throws ZygoteStartFailedEx if process start failed for any reason + */ + private static Process.ProcessStartResult zygoteSendArgsAndGetResult( + ZygoteState zygoteState, ArrayList args) + throws ZygoteStartFailedEx { + try { + // Throw early if any of the arguments are malformed. This means we can + // avoid writing a partial response to the zygote. + int sz = args.size(); + for (int i = 0; i < sz; i++) { + if (args.get(i).indexOf('\n') >= 0) { + throw new ZygoteStartFailedEx("embedded newlines not allowed"); + } + } + /** + * See com.android.internal.os.SystemZygoteInit.readArgumentList() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure, followed by boolean to + * indicate whether a wrapper process was used. + */ + final BufferedWriter writer = zygoteState.writer; + final DataInputStream inputStream = zygoteState.inputStream; + writer.write(Integer.toString(args.size())); + writer.newLine(); + for (int i = 0; i < sz; i++) { + String arg = args.get(i); + writer.write(arg); + writer.newLine(); + } + writer.flush(); + // Should there be a timeout on this? + Process.ProcessStartResult result = new Process.ProcessStartResult(); + // Always read the entire result from the input stream to avoid leaving + // bytes in the stream for future process starts to accidentally stumble + // upon. + result.pid = inputStream.readInt(); + result.usingWrapper = inputStream.readBoolean(); + if (result.pid < 0) { + throw new ZygoteStartFailedEx("fork() failed"); + } + return result; + } catch (IOException ex) { + zygoteState.close(); + throw new ZygoteStartFailedEx(ex); + } + } + /** + * Starts a new process via the zygote mechanism. + * + * @param processClass Class name whose static main() to run + * @param niceName 'nice' process name to appear in ps + * @param uid a POSIX uid that the new process should setuid() to + * @param gid a POSIX gid that the new process shuold setgid() to + * @param gids null-ok; a list of supplementary group IDs that the + * new process should setgroup() to. + * @param runtimeFlags Additional flags for the runtime. + * @param targetSdkVersion The target SDK version for the app. + * @param seInfo null-ok SELinux information for the new process. + * @param abi the ABI the process should use. + * @param instructionSet null-ok the instruction set to use. + * @param appDataDir null-ok the data directory of the app. + * @param extraArgs Additional arguments to supply to the zygote process. + * @return An object that describes the result of the attempt to start the process. + * @throws ZygoteStartFailedEx if process start failed for any reason + */ + private Process.ProcessStartResult startViaZygote(final String processClass, + final String niceName, + final int uid, final int gid, + final int[] gids, + int runtimeFlags, int mountExternal, + int targetSdkVersion, + String seInfo, + String abi, + String instructionSet, + String appDataDir, + String invokeWith, + String[] extraArgs) + throws ZygoteStartFailedEx { + throw new NotImplementedError(); + } + /** + * Tries to establish a connection to the zygote that handles a given {@code abi}. Might block + * and retry if the zygote is unresponsive. This method is a no-op if a connection is + * already open. + */ + public void establishZygoteConnectionForAbi(String abi) { + try { + synchronized(mLock) { + openZygoteSocketIfNeeded(abi); + } + } catch (ZygoteStartFailedEx ex) { + throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex); + } + } + /** + * Tries to open socket to Zygote process if not already open. If + * already open, does nothing. May block and retry. Requires that mLock be held. + */ + private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { + Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); + if (primaryZygoteState == null || primaryZygoteState.isClosed()) { + try { + primaryZygoteState = ZygoteState.connect(mSocket); + } catch (IOException ioe) { + throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); + } + } + if (primaryZygoteState.matches(abi)) { + return primaryZygoteState; + } + // The primary zygote didn't match. Try the secondary. + if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { + try { + secondaryZygoteState = ZygoteState.connect(mSecondarySocket); + } catch (IOException ioe) { + throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); + } + } + if (secondaryZygoteState.matches(abi)) { + return secondaryZygoteState; + } + throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); + } + /** + * Instructs the zygote to pre-load the classes and native libraries at the given paths + * for the specified abi. Not all zygotes support this function. + */ + public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey, + String abi) throws ZygoteStartFailedEx, IOException { + synchronized(mLock) { + ZygoteState state = openZygoteSocketIfNeeded(abi); + state.writer.write("4"); + state.writer.newLine(); + state.writer.write("--preload-package"); + state.writer.newLine(); + state.writer.write(packagePath); + state.writer.newLine(); + state.writer.write(libsPath); + state.writer.newLine(); + state.writer.write(cacheKey); + state.writer.newLine(); + state.writer.flush(); + return (state.inputStream.readInt() == 0); + } + } + /** + * Instructs the zygote to preload the default set of classes and resources. Returns + * {@code true} if a preload was performed as a result of this call, and {@code false} + * otherwise. The latter usually means that the zygote eagerly preloaded at startup + * or due to a previous call to {@code preloadDefault}. Note that this call is synchronous. + */ + public boolean preloadDefault(String abi) throws ZygoteStartFailedEx, IOException { + synchronized (mLock) { + ZygoteState state = openZygoteSocketIfNeeded(abi); + // Each query starts with the argument count (1 in this case) + state.writer.write("1"); + state.writer.newLine(); + state.writer.write("--preload-default"); + state.writer.newLine(); + state.writer.flush(); + return (state.inputStream.readInt() == 0); + } + } + /** + * Try connecting to the Zygote over and over again until we hit a time-out. + * @param socketName The name of the socket to connect to. + */ + public static void waitForConnectionToZygote(String socketName) { + for (int n = 20; n >= 0; n--) { + try { + final ZygoteState zs = ZygoteState.connect(socketName); + zs.close(); + return; + } catch (IOException ioe) { + Log.w(LOG_TAG, + "Got error connecting to zygote, retrying. msg= " + ioe.getMessage()); + } + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + } + } +// Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + socketName); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt b/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt new file mode 100644 index 00000000..6d539dbb --- /dev/null +++ b/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt @@ -0,0 +1,16 @@ +package android.preference + +import android.content.Context + +/** + * Created by nulldev on 3/26/17. + */ + +class PreferenceManager { + companion object { + @JvmStatic + fun getDefaultSharedPreferences(context: Context) + = context.getSharedPreferences(context.applicationInfo.packageName, + Context.MODE_PRIVATE)!! + } +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/AnimRes.java b/AndroidCompat/src/main/java/android/support/annotation/AnimRes.java new file mode 100644 index 00000000..ebbcb507 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/AnimRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an anim resource reference (e.g. {@code android.R.anim.fade_in}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnimRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/AnimatorRes.java b/AndroidCompat/src/main/java/android/support/annotation/AnimatorRes.java new file mode 100644 index 00000000..42fc3ba8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/AnimatorRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an animator resource reference (e.g. {@code android.R.animator.fade_in}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnimatorRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/AnyRes.java b/AndroidCompat/src/main/java/android/support/annotation/AnyRes.java new file mode 100644 index 00000000..84068b48 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/AnyRes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a resource reference of any type. If the specific type is known, use + * one of the more specific annotations instead, such as {@link StringRes} or + * {@link DrawableRes}. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnyRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/AnyThread.java b/AndroidCompat/src/main/java/android/support/annotation/AnyThread.java new file mode 100644 index 00000000..d3cc2745 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/AnyThread.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method can be called from any thread (e.g. it is "thread safe".) + * If the annotated element is a class, then all methods in the class can be called + * from any thread. + *

+ * The main purpose of this method is to indicate that you believe a method can be called + * from any thread; static tools can then check that nothing you call from within this method + * or class have more strict threading requirements. + *

+ * Example: + *


+ *  @AnyThread
+ *  public void deliverResult(D data) { ... }
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface AnyThread { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/ArrayRes.java b/AndroidCompat/src/main/java/android/support/annotation/ArrayRes.java new file mode 100644 index 00000000..5fa010a6 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/ArrayRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an array resource reference (e.g. {@code android.R.array.phoneTypes}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface ArrayRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/AttrRes.java b/AndroidCompat/src/main/java/android/support/annotation/AttrRes.java new file mode 100644 index 00000000..255824b3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/AttrRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an attribute reference (e.g. {@code android.R.attr.action}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AttrRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/BinderThread.java b/AndroidCompat/src/main/java/android/support/annotation/BinderThread.java new file mode 100644 index 00000000..b9361f53 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/BinderThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method should only be called on the binder thread. + * If the annotated element is a class, then all methods in the class should be called + * on the binder thread. + *

+ * Example: + *


+ *  @BinderThread
+ *  public BeamShareData createBeamShareData() { ... }
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface BinderThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/BoolRes.java b/AndroidCompat/src/main/java/android/support/annotation/BoolRes.java new file mode 100644 index 00000000..a9b1af89 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/BoolRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a boolean resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface BoolRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/CallSuper.java b/AndroidCompat/src/main/java/android/support/annotation/CallSuper.java new file mode 100644 index 00000000..9c01cdf2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/CallSuper.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that any overriding methods should invoke this method as well. + *

+ * Example: + *


+ *  @CallSuper
+ *  public abstract void onFocusLost();
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD}) +public @interface CallSuper { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/CheckResult.java b/AndroidCompat/src/main/java/android/support/annotation/CheckResult.java new file mode 100644 index 00000000..7f40676b --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/CheckResult.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method returns a result that it typically is + * an error to ignore. This is usually used for methods that have no side effect, + * so calling it without actually looking at the result usually means the developer + * has misunderstood what the method does. + *

+ * Example: + *

{@code
+ *  public @CheckResult String trim(String s) { return s.trim(); }
+ *  ...
+ *  s.trim(); // this is probably an error
+ *  s = s.trim(); // ok
+ * }
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD}) +public @interface CheckResult { + /** Defines the name of the suggested method to use instead, if applicable (using + * the same signature format as javadoc.) If there is more than one possibility, + * list them all separated by commas. + *

+ * For example, ProcessBuilder has a method named {@code redirectErrorStream()} + * which sounds like it might redirect the error stream. It does not. It's just + * a getter which returns whether the process builder will redirect the error stream, + * and to actually set it, you must call {@code redirectErrorStream(boolean)}. + * In that case, the method should be defined like this: + *

+     *  @CheckResult(suggest="#redirectErrorStream(boolean)")
+     *  public boolean redirectErrorStream() { ... }
+     * 
+ */ + String suggest() default ""; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/ColorInt.java b/AndroidCompat/src/main/java/android/support/annotation/ColorInt.java new file mode 100644 index 00000000..843006d4 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/ColorInt.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element represents a packed color + * int, {@code AARRGGBB}. If applied to an int array, every element + * in the array represents a color integer. + *

+ * Example: + *

{@code
+ *  public abstract void setTextColor(@ColorInt int color);
+ * }
+ */ +@Retention(CLASS) +@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD}) +public @interface ColorInt { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/ColorRes.java b/AndroidCompat/src/main/java/android/support/annotation/ColorRes.java new file mode 100644 index 00000000..f3d9d666 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/ColorRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a color resource reference (e.g. {@code android.R.color.black}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface ColorRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/DimenRes.java b/AndroidCompat/src/main/java/android/support/annotation/DimenRes.java new file mode 100644 index 00000000..4c9d786f --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/DimenRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a dimension resource reference (e.g. {@code android.R.dimen.app_icon_size}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface DimenRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/Dimension.java b/AndroidCompat/src/main/java/android/support/annotation/Dimension.java new file mode 100644 index 00000000..e06165b3 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/Dimension.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to represent a dimension. + */ +@Documented +@Retention(CLASS) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) +public @interface Dimension { + @Unit + int unit() default PX; + + int DP = 0; + int PX = 1; + int SP = 2; + + @IntDef({PX, DP, SP}) + @Retention(RetentionPolicy.SOURCE) + @interface Unit {} +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/DrawableRes.java b/AndroidCompat/src/main/java/android/support/annotation/DrawableRes.java new file mode 100644 index 00000000..1a5fd7ec --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/DrawableRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a drawable resource reference (e.g. {@code android.R.attr.alertDialogIcon}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface DrawableRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/FloatRange.java b/AndroidCompat/src/main/java/android/support/annotation/FloatRange.java new file mode 100644 index 00000000..4656de11 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/FloatRange.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should be a float or double in the given range + *

+ * Example: + *


+ *  @FloatRange(from=0.0,to=1.0)
+ *  public float getAlpha() {
+ *      ...
+ *  }
+ * 
+ */ +@Retention(CLASS) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) +public @interface FloatRange { + /** Smallest value. Whether it is inclusive or not is determined + * by {@link #fromInclusive} */ + double from() default Double.NEGATIVE_INFINITY; + /** Largest value. Whether it is inclusive or not is determined + * by {@link #toInclusive} */ + double to() default Double.POSITIVE_INFINITY; + + /** Whether the from value is included in the range */ + boolean fromInclusive() default true; + + /** Whether the to value is included in the range */ + boolean toInclusive() default true; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/FractionRes.java b/AndroidCompat/src/main/java/android/support/annotation/FractionRes.java new file mode 100644 index 00000000..e53c403a --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/FractionRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a fraction resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface FractionRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/IdRes.java b/AndroidCompat/src/main/java/android/support/annotation/IdRes.java new file mode 100644 index 00000000..af74a85e --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/IdRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an id resource reference (e.g. {@code android.R.id.copy}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface IdRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/IntDef.java b/AndroidCompat/src/main/java/android/support/annotation/IntDef.java new file mode 100644 index 00000000..95abb675 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/IntDef.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated element of integer type, represents + * a logical type and that its value should be one of the explicitly + * named constants. If the IntDef#flag() attribute is set to true, + * multiple constants can be combined. + *

+ * Example: + *


+ *  @Retention(SOURCE)
+ *  @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
+ *  public static final int NAVIGATION_MODE_STANDARD = 0;
+ *  public static final int NAVIGATION_MODE_LIST = 1;
+ *  public static final int NAVIGATION_MODE_TABS = 2;
+ *  ...
+ *  public abstract void setNavigationMode(@NavigationMode int mode);
+ *  @NavigationMode
+ *  public abstract int getNavigationMode();
+ * 
+ * For a flag, set the flag attribute: + *

+ *  @IntDef(
+ *      flag = true
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * 
+ */ +@Retention(SOURCE) +@Target({ANNOTATION_TYPE}) +public @interface IntDef { + /** Defines the allowed constants for this element */ + long[] value() default {}; + + /** Defines whether the constants can be used as a flag, or just as an enum (the default) */ + boolean flag() default false; +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/IntRange.java b/AndroidCompat/src/main/java/android/support/annotation/IntRange.java new file mode 100644 index 00000000..cbebf6c5 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/IntRange.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should be an int or long in the given range + *

+ * Example: + *


+ *  @IntRange(from=0,to=255)
+ *  public int getAlpha() {
+ *      ...
+ *  }
+ * 
+ */ +@Retention(CLASS) +@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) +public @interface IntRange { + /** Smallest value, inclusive */ + long from() default Long.MIN_VALUE; + /** Largest value, inclusive */ + long to() default Long.MAX_VALUE; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/IntegerRes.java b/AndroidCompat/src/main/java/android/support/annotation/IntegerRes.java new file mode 100644 index 00000000..8f384e59 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/IntegerRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an integer resource reference (e.g. {@code android.R.integer.config_shortAnimTime}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface IntegerRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/InterpolatorRes.java b/AndroidCompat/src/main/java/android/support/annotation/InterpolatorRes.java new file mode 100644 index 00000000..074a744a --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/InterpolatorRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an interpolator resource reference (e.g. {@code android.R.interpolator.cycle}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface InterpolatorRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/Keep.java b/AndroidCompat/src/main/java/android/support/annotation/Keep.java new file mode 100644 index 00000000..9e5450be --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/Keep.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should not be removed when + * the code is minified at build time. This is typically used + * on methods and classes that are accessed only via reflection + * so a compiler may think that the code is unused. + *

+ * Example: + *


+ *  @Keep
+ *  public void foo() {
+ *      ...
+ *  }
+ * 
+ */ +@Retention(CLASS) +@Target({PACKAGE,TYPE,ANNOTATION_TYPE,CONSTRUCTOR,METHOD,FIELD}) +public @interface Keep { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/LayoutRes.java b/AndroidCompat/src/main/java/android/support/annotation/LayoutRes.java new file mode 100644 index 00000000..c0046ceb --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/LayoutRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a layout resource reference (e.g. {@code android.R.layout.list_content}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface LayoutRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/MainThread.java b/AndroidCompat/src/main/java/android/support/annotation/MainThread.java new file mode 100644 index 00000000..da05dd3a --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/MainThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method should only be called on the main thread. + * If the annotated element is a class, then all methods in the class should be called + * on the main thread. + *

+ * Example: + *


+ *  @MainThread
+ *  public void deliverResult(D data) { ... }
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface MainThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/MenuRes.java b/AndroidCompat/src/main/java/android/support/annotation/MenuRes.java new file mode 100644 index 00000000..7832e0a2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/MenuRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a menu resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface MenuRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/NonNull.java b/AndroidCompat/src/main/java/android/support/annotation/NonNull.java new file mode 100644 index 00000000..bae1a338 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/NonNull.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that a parameter, field or method return value can never be null. + *

+ * This is a marker annotation and it has no specific attributes. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD}) +public @interface NonNull { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/Nullable.java b/AndroidCompat/src/main/java/android/support/annotation/Nullable.java new file mode 100644 index 00000000..eae30c54 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/Nullable.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that a parameter, field or method return value can be null. + *

+ * When decorating a method call parameter, this denotes that the parameter can + * legitimately be null and the method will gracefully deal with it. Typically + * used on optional parameters. + *

+ * When decorating a method, this denotes the method might legitimately return + * null. + *

+ * This is a marker annotation and it has no specific attributes. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD}) +public @interface Nullable { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/PluralsRes.java b/AndroidCompat/src/main/java/android/support/annotation/PluralsRes.java new file mode 100644 index 00000000..0c820390 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/PluralsRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a plurals resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface PluralsRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/Px.java b/AndroidCompat/src/main/java/android/support/annotation/Px.java new file mode 100644 index 00000000..2f6529a2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/Px.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to represent a pixel dimension. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +@Dimension(unit = Dimension.PX) +public @interface Px { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/RawRes.java b/AndroidCompat/src/main/java/android/support/annotation/RawRes.java new file mode 100644 index 00000000..7bd23026 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/RawRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a raw resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface RawRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/RequiresApi.java b/AndroidCompat/src/main/java/android/support/annotation/RequiresApi.java new file mode 100644 index 00000000..0ef092ac --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/RequiresApi.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should only be called on the given API level + * or higher. + *

+ * This is similar in purpose to the older {@code @TargetApi} annotation, but more + * clearly expresses that this is a requirement on the caller, rather than being + * used to "suppress" warnings within the method that exceed the {@code minSdkVersion}. + */ +@Retention(CLASS) +@Target({TYPE,METHOD,CONSTRUCTOR,FIELD}) +public @interface RequiresApi { + /** + * The API level to require. Alias for {@link #api} which allows you to leave out the + * {@code api=} part. + */ + @IntRange(from=1) + int value() default 1; + + /** The API level to require */ + @IntRange(from=1) + int api() default 1; +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/RequiresPermission.java b/AndroidCompat/src/main/java/android/support/annotation/RequiresPermission.java new file mode 100644 index 00000000..9dd833ca --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/RequiresPermission.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element requires (or may require) one or more permissions. + *

+ * Example of requiring a single permission: + *


+ *   @RequiresPermission(Manifest.permission.SET_WALLPAPER)
+ *   public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+ *
+ *   @RequiresPermission(ACCESS_COARSE_LOCATION)
+ *   public abstract Location getLastKnownLocation(String provider);
+ * 
+ * Example of requiring at least one permission from a set: + *

+ *   @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * 
+ * Example of requiring multiple permissions: + *

+ *   @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * 
+ * Example of requiring separate read and write permissions for a content provider: + *

+ *   @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ *   @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ *   public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * 
+ *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter. For example, consider + * {@code android.app.Activity.startActivity(android.content.Intent)}: + *

{@code
+ *   public void startActivity(@RequiresPermission Intent intent) { ... }
+ * }
+ * Notice how there are no actual permission names listed in the annotation. The actual + * permissions required will depend on the particular intent passed in. For example, + * the code may look like this: + *
{@code
+ *   Intent intent = new Intent(Intent.ACTION_CALL);
+ *   startActivity(intent);
+ * }
+ * and the actual permission requirement for this particular intent is described on + * the Intent name itself: + *

+ *   @RequiresPermission(Manifest.permission.CALL_PHONE)
+ *   public static final String ACTION_CALL = "android.intent.action.CALL";
+ * 
+ */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER}) +public @interface RequiresPermission { + /** + * The name of the permission that is required, if precisely one permission + * is required. If more than one permission is required, specify either + * {@link #allOf()} or {@link #anyOf()} instead. + *

+ * If specified, {@link #anyOf()} and {@link #allOf()} must both be null. + */ + String value() default ""; + + /** + * Specifies a list of permission names that are all required. + *

+ * If specified, {@link #anyOf()} and {@link #value()} must both be null. + */ + String[] allOf() default {}; + + /** + * Specifies a list of permission names where at least one is required + *

+ * If specified, {@link #allOf()} and {@link #value()} must both be null. + */ + String[] anyOf() default {}; + + /** + * If true, the permission may not be required in all cases (e.g. it may only be + * enforced on certain platforms, or for certain call parameters, etc. + */ + boolean conditional() default false; + + /** + * Specifies that the given permission is required for read operations. + *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter (and typically + * the corresponding field passed in will be one of a set of constants which have + * been annotated with a {@code @RequiresPermission} annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Read { + RequiresPermission value() default @RequiresPermission; + } + + /** + * Specifies that the given permission is required for write operations. + *

+ * When specified on a parameter, the annotation indicates that the method requires + * a permission which depends on the value of the parameter (and typically + * the corresponding field passed in will be one of a set of constants which have + * been annotated with a {@code @RequiresPermission} annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Write { + RequiresPermission value() default @RequiresPermission; + } +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/Size.java b/AndroidCompat/src/main/java/android/support/annotation/Size.java new file mode 100644 index 00000000..ee97b9d1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/Size.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated element should have a given size or length. + * Note that "-1" means "unset". Typically used with a parameter or + * return value of type array or collection. + *

+ * Example: + *

{@code
+ *  public void getLocationInWindow(@Size(2) int[] location) {
+ *      ...
+ *  }
+ * }
+ */ +@Retention(CLASS) +@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD,ANNOTATION_TYPE}) +public @interface Size { + /** An exact size (or -1 if not specified) */ + long value() default -1; + /** A minimum size, inclusive */ + long min() default Long.MIN_VALUE; + /** A maximum size, inclusive */ + long max() default Long.MAX_VALUE; + /** The size must be a multiple of this factor */ + long multiple() default 1; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/StringDef.java b/AndroidCompat/src/main/java/android/support/annotation/StringDef.java new file mode 100644 index 00000000..fac23d0c --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/StringDef.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that the annotated String element, represents a logical + * type and that its value should be one of the explicitly named constants. + *

+ * Example: + *


+ *  @Retention(SOURCE)
+ *  @StringDef({
+ *     POWER_SERVICE,
+ *     WINDOW_SERVICE,
+ *     LAYOUT_INFLATER_SERVICE
+ *  })
+ *  public @interface ServiceName {}
+ *  public static final String POWER_SERVICE = "power";
+ *  public static final String WINDOW_SERVICE = "window";
+ *  public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ *  ...
+ *  public abstract Object getSystemService(@ServiceName String name);
+ * 
+ */ +@Retention(SOURCE) +@Target({ANNOTATION_TYPE}) +public @interface StringDef { + /** Defines the allowed constants for this element */ + String[] value() default {}; +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/StringRes.java b/AndroidCompat/src/main/java/android/support/annotation/StringRes.java new file mode 100644 index 00000000..1cc3bfdc --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/StringRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a String resource reference (e.g. {@code android.R.string.ok}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StringRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/StyleRes.java b/AndroidCompat/src/main/java/android/support/annotation/StyleRes.java new file mode 100644 index 00000000..c0ad7d44 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/StyleRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a style resource reference (e.g. {@code android.R.style.TextAppearance}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StyleRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/StyleableRes.java b/AndroidCompat/src/main/java/android/support/annotation/StyleableRes.java new file mode 100644 index 00000000..591cb472 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/StyleableRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a styleable resource reference (e.g. {@code android.R.styleable.TextView_text}). + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StyleableRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/TransitionRes.java b/AndroidCompat/src/main/java/android/support/annotation/TransitionRes.java new file mode 100644 index 00000000..78c5efa0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/TransitionRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a transition resource reference. + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface TransitionRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/UiThread.java b/AndroidCompat/src/main/java/android/support/annotation/UiThread.java new file mode 100644 index 00000000..30476d99 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/UiThread.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method or constructor should only be called on the UI thread. + * If the annotated element is a class, then all methods in the class should be called + * on the UI thread. + *

+ * Example: + *


+ *  @UiThread
+ *
+ *  public abstract void setText(@NonNull String text) { ... }
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface UiThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/VisibleForTesting.java b/AndroidCompat/src/main/java/android/support/annotation/VisibleForTesting.java new file mode 100644 index 00000000..0c893ffa --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/VisibleForTesting.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the class, method or field has its visibility relaxed, so that it is more widely + * visible than otherwise necessary to make code testable. + */ +@Retention(CLASS) +public @interface VisibleForTesting { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/WorkerThread.java b/AndroidCompat/src/main/java/android/support/annotation/WorkerThread.java new file mode 100644 index 00000000..0468b1cd --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/WorkerThread.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that the annotated method should only be called on a worker thread. + * If the annotated element is a class, then all methods in the class should be called + * on a worker thread. + *

+ * Example: + *


+ *  @WorkerThread
+ *  protected abstract FilterResults performFiltering(CharSequence constraint);
+ * 
+ */ +@Documented +@Retention(CLASS) +@Target({METHOD,CONSTRUCTOR,TYPE}) +public @interface WorkerThread { +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/annotation/XmlRes.java b/AndroidCompat/src/main/java/android/support/annotation/XmlRes.java new file mode 100644 index 00000000..e5b8a8e1 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/XmlRes.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 android.support.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be an XML resource reference. + */ +@Documented +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface XmlRes { +} diff --git a/AndroidCompat/src/main/java/android/support/annotation/package-info.java b/AndroidCompat/src/main/java/android/support/annotation/package-info.java new file mode 100644 index 00000000..44f311ad --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * All of the files in this package were copied unchanged from the Android Open Source Project (AOSP) + */ +package android.support.annotation; \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/multidex/MultiDex.java b/AndroidCompat/src/main/java/android/support/multidex/MultiDex.java new file mode 100644 index 00000000..c3d940e2 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/multidex/MultiDex.java @@ -0,0 +1,16 @@ +package android.support.multidex; + +import android.content.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * MultiDex that does nothing. + */ +public class MultiDex { + private static Logger logger = LoggerFactory.getLogger(MultiDex.class); + + public static void install(Context context) { + logger.debug("Ignoring MultiDex installation attempt for app: {}", context.getPackageName()); + } +} diff --git a/AndroidCompat/src/main/java/android/support/v4/content/ContextCompat.java b/AndroidCompat/src/main/java/android/support/v4/content/ContextCompat.java new file mode 100644 index 00000000..508253ec --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/v4/content/ContextCompat.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012 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 android.support.v4.content; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.StatFs; +import android.support.v4.os.EnvironmentCompat; + +import java.io.File; + +/** + * Helper for accessing features in {@link android.content.Context} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class ContextCompat { + /** + * Start a set of activities as a synthesized task stack, if able. + * + *

In API level 11 (Android 3.0/Honeycomb) the recommended conventions for + * app navigation using the back key changed. The back key's behavior is local + * to the current task and does not capture navigation across different tasks. + * Navigating across tasks and easily reaching the previous task is accomplished + * through the "recents" UI, accessible through the software-provided Recents key + * on the navigation or system bar. On devices with the older hardware button configuration + * the recents UI can be accessed with a long press on the Home key.

+ * + *

When crossing from one task stack to another post-Android 3.0, + * the application should synthesize a back stack/history for the new task so that + * the user may navigate out of the new task and back to the Launcher by repeated + * presses of the back key. Back key presses should not navigate across task stacks.

+ * + *

startActivities provides a mechanism for constructing a synthetic task stack of + * multiple activities. If the underlying API is not available on the system this method + * will return false.

+ * + * @param context Start activities using this activity as the starting context + * @param intents Array of intents defining the activities that will be started. The element + * length-1 will correspond to the top activity on the resulting task stack. + * @return true if the underlying API was available and the call was successful, false otherwise + */ + public static boolean startActivities(Context context, Intent[] intents) { + return startActivities(context, intents, null); + } + + /** + * Start a set of activities as a synthesized task stack, if able. + * + *

In API level 11 (Android 3.0/Honeycomb) the recommended conventions for + * app navigation using the back key changed. The back key's behavior is local + * to the current task and does not capture navigation across different tasks. + * Navigating across tasks and easily reaching the previous task is accomplished + * through the "recents" UI, accessible through the software-provided Recents key + * on the navigation or system bar. On devices with the older hardware button configuration + * the recents UI can be accessed with a long press on the Home key.

+ * + *

When crossing from one task stack to another post-Android 3.0, + * the application should synthesize a back stack/history for the new task so that + * the user may navigate out of the new task and back to the Launcher by repeated + * presses of the back key. Back key presses should not navigate across task stacks.

+ * + *

startActivities provides a mechanism for constructing a synthetic task stack of + * multiple activities. If the underlying API is not available on the system this method + * will return false.

+ * + * @param context Start activities using this activity as the starting context + * @param intents Array of intents defining the activities that will be started. The element + * length-1 will correspond to the top activity on the resulting task stack. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * @return true if the underlying API was available and the call was successful, false otherwise + */ + public static boolean startActivities(Context context, Intent[] intents, + Bundle options) { + context.startActivities(intents, options); + return true; + } + + /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application's OBB files (if there are + * any) can be found. Note if the application does not have any OBB files, + * these directories may not exist. + *

+ * This is like {@link Context#getFilesDir()} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + *

    + *
  • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
  • There is no security enforced with these files. + *
+ *

+ * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to write to the returned paths; they're always accessible to + * the calling app. Before then, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to + * write. Write access outside of these paths on secondary external storage + * devices is not available. To request external storage access in a + * backwards compatible way, consider using {@code android:maxSdkVersion} + * like this: + * + *

<uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" />
+ *

+ * The first path returned is the same as {@link Context#getObbDir()}. + * Returned paths may be {@code null} if a storage device is unavailable. + * + * @see Context#getObbDir() + * @see EnvironmentCompat#getStorageState(File) + */ + public static File[] getObbDirs(Context context) { + return context.getObbDirs(); + } + + /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place persistent files + * it owns. These files are internal to the application, and not typically + * visible to the user as media. + *

+ * This is like {@link Context#getFilesDir()} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + *

    + *
  • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
  • There is no security enforced with these files. + *
+ *

+ * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to write to the returned paths; they're always accessible to + * the calling app. Before then, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to + * write. Write access outside of these paths on secondary external storage + * devices is not available. To request external storage access in a + * backwards compatible way, consider using {@code android:maxSdkVersion} + * like this: + * + *

<uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" />
+ *

+ * The first path returned is the same as + * {@link Context#getExternalFilesDir(String)}. Returned paths may be + * {@code null} if a storage device is unavailable. + * + * @see Context#getExternalFilesDir(String) + * @see EnvironmentCompat#getStorageState(File) + */ + public static File[] getExternalFilesDirs(Context context, String type) { + return context.getExternalFilesDirs(type); + } + + /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place cache files it + * owns. These files are internal to the application, and not typically + * visible to the user as media. + *

+ * This is like {@link Context#getCacheDir()} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + *

    + *
  • External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + *
  • There is no security enforced with these files. + *
+ *

+ * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + *

+ * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions + * are required to write to the returned paths; they're always accessible to + * the calling app. Before then, + * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to + * write. Write access outside of these paths on secondary external storage + * devices is not available. To request external storage access in a + * backwards compatible way, consider using {@code android:maxSdkVersion} + * like this: + * + *

<uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" />
+ *

+ * The first path returned is the same as + * {@link Context#getExternalCacheDir()}. Returned paths may be {@code null} + * if a storage device is unavailable. + * + * @see Context#getExternalCacheDir() + * @see EnvironmentCompat#getStorageState(File) + */ + public static File[] getExternalCacheDirs(Context context) { + return context.getExternalCacheDirs(); + } + + /** + * Return a drawable object associated with a particular resource ID. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned + * drawable will be styled for the specified Context's theme. + * + * @param id The desired resource identifier, as generated by the aapt tool. + * This integer encodes the package, type, and resource entry. + * The value 0 is an invalid identifier. + * @return Drawable An object that can be used to draw this resource. + */ + public static final Drawable getDrawable(Context context, int id) { + return context.getDrawable(id); + } + + /** + * Returns the absolute path to the directory on the filesystem similar to + * {@link Context#getFilesDir()}. The difference is that files placed under this + * directory will be excluded from automatic backup to remote storage on + * devices running {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later. See + * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion + * of the automatic backup mechanism in Android. + * + *

No permissions are required to read or write to the returned path, since this + * path is internal storage. + * + * @return The path of the directory holding application files that will not be + * automatically backed up to remote storage. + * + * @see android.content.Context.getFilesDir + */ + public final File getNoBackupFilesDir(Context context) { + return context.getNoBackupFilesDir(); + } + + /** + * Returns the absolute path to the application specific cache directory on + * the filesystem designed for storing cached code. On devices running + * {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, the system will delete + * any files stored in this location both when your specific application is + * upgraded, and when the entire platform is upgraded. + *

+ * This location is optimal for storing compiled or optimized code generated + * by your application at runtime. + *

+ * Apps require no extra permissions to read or write to the returned path, + * since this path lives in their private storage. + * + * @return The path of the directory holding application code cache files. + */ + public final File getCodeCacheDir(Context context) { + return context.getCodeCacheDir(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/v4/os/EnvironmentCompat.java b/AndroidCompat/src/main/java/android/support/v4/os/EnvironmentCompat.java new file mode 100644 index 00000000..e98982fc --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/v4/os/EnvironmentCompat.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 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 android.support.v4.os; + +import android.os.Environment; + +import java.io.File; + +/** + * Helper for accessing features in {@link Environment} introduced after API + * level 4 in a backwards compatible fashion. + */ +public class EnvironmentCompat { + /** + * Unknown storage state, such as when a path isn't backed by known storage + * media. + * + * @see #getStorageState(File) + */ + public static final String MEDIA_UNKNOWN = "unknown"; + + /** + * Returns the current state of the storage device that provides the given + * path. + * + * @return one of {@link #MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED}, + * {@link Environment#MEDIA_UNMOUNTED}, + * {@link Environment#MEDIA_CHECKING}, + * {@link Environment#MEDIA_NOFS}, + * {@link Environment#MEDIA_MOUNTED}, + * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, + * {@link Environment#MEDIA_SHARED}, + * {@link Environment#MEDIA_BAD_REMOVAL}, or + * {@link Environment#MEDIA_UNMOUNTABLE}. + */ + public static String getStorageState(File path) { + return Environment.getStorageState(path); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceDataStore.java b/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceDataStore.java new file mode 100644 index 00000000..ab7ec0c8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceDataStore.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017 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 android.support.v7.preference; + +import android.support.annotation.Nullable; + +import java.util.Set; + +/** + * A data store interface to be implemented and provided to the Preferences framework. This can be + * used to replace the default {@link android.content.SharedPreferences}, if needed. + * + *

In most cases you want to use {@link android.content.SharedPreferences} as it is automatically + * backed up and migrated to new devices. However, providing custom data store to preferences can be + * useful if your app stores its preferences in a local db, cloud or they are device specific like + * "Developer settings". It might be also useful when you want to use the preferences UI but + * the data are not supposed to be stored at all because they are valid per session only. + * + *

Once a put method is called it is full responsibility of the data store implementation to + * safely store the given values. Time expensive operations need to be done in the background to + * prevent from blocking the UI. You also need to have a plan on how to serialize the data in case + * the activity holding this object gets destroyed. + * + *

By default, all "put" methods throw {@link UnsupportedOperationException}. + */ +public abstract class PreferenceDataStore { + + /** + * Sets a {@link String} value to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param value the new value for the preference + * @see #getString(String, String) + */ + public void putString(String key, @Nullable String value) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Sets a set of Strings to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param values the set of new values for the preference + * @see #getStringSet(String, Set) + */ + public void putStringSet(String key, @Nullable Set values) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Sets an {@link Integer} value to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param value the new value for the preference + * @see #getInt(String, int) + */ + public void putInt(String key, int value) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Sets a {@link Long} value to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param value the new value for the preference + * @see #getLong(String, long) + */ + public void putLong(String key, long value) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Sets a {@link Float} value to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param value the new value for the preference + * @see #getFloat(String, float) + */ + public void putFloat(String key, float value) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Sets a {@link Boolean} value to the data store. + * + *

Once the value is set the data store is responsible for holding it. + * + * @param key the name of the preference to modify + * @param value the new value for the preference + * @see #getBoolean(String, boolean) + */ + public void putBoolean(String key, boolean value) { + throw new UnsupportedOperationException("Not implemented on this data store"); + } + + /** + * Retrieves a {@link String} value from the data store. + * + * @param key the name of the preference to retrieve + * @param defValue value to return if this preference does not exist in the storage + * @return the value from the data store or the default return value + * @see #putString(String, String) + */ + @Nullable + public String getString(String key, @Nullable String defValue) { + return defValue; + } + + /** + * Retrieves a set of Strings from the data store. + * + * @param key the name of the preference to retrieve + * @param defValues values to return if this preference does not exist in the storage + * @return the values from the data store or the default return values + * @see #putStringSet(String, Set) + */ + @Nullable + public Set getStringSet(String key, @Nullable Set defValues) { + return defValues; + } + + /** + * Retrieves an {@link Integer} value from the data store. + * + * @param key the name of the preference to retrieve + * @param defValue value to return if this preference does not exist in the storage + * @return the value from the data store or the default return value + * @see #putInt(String, int) + */ + public int getInt(String key, int defValue) { + return defValue; + } + + /** + * Retrieves a {@link Long} value from the data store. + * + * @param key the name of the preference to retrieve + * @param defValue value to return if this preference does not exist in the storage + * @return the value from the data store or the default return value + * @see #putLong(String, long) + */ + public long getLong(String key, long defValue) { + return defValue; + } + + /** + * Retrieves a {@link Float} value from the data store. + * + * @param key the name of the preference to retrieve + * @param defValue value to return if this preference does not exist in the storage + * @return the value from the data store or the default return value + * @see #putFloat(String, float) + */ + public float getFloat(String key, float defValue) { + return defValue; + } + + /** + * Retrieves a {@link Boolean} value from the data store. + * + * @param key the name of the preference to retrieve + * @param defValue value to return if this preference does not exist in the storage + * @return the value from the data store or the default return value + * @see #getBoolean(String, boolean) + */ + public boolean getBoolean(String key, boolean defValue) { + return defValue; + } +} diff --git a/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceScreen.java b/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceScreen.java new file mode 100644 index 00000000..cff9a9f0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/support/v7/preference/PreferenceScreen.java @@ -0,0 +1,4 @@ +package android.support.v7.preference; + +public class PreferenceScreen { +} diff --git a/AndroidCompat/src/main/java/android/text/Html.java b/AndroidCompat/src/main/java/android/text/Html.java new file mode 100644 index 00000000..052afed0 --- /dev/null +++ b/AndroidCompat/src/main/java/android/text/Html.java @@ -0,0 +1,110 @@ +package android.text; + + +import android.graphics.drawable.Drawable; +import org.jetbrains.annotations.NotNull; +import org.jsoup.Jsoup; +import org.jsoup.safety.Whitelist; +import org.xml.sax.XMLReader; + +/** + * Project: TachiServer + * Author: nulldev + * Creation Date: 16/08/16 + * + * Android compat class for processing HTML + */ + +public class Html { + + public static Spanned fromHtml(String source) { + return new FakeSpanned(Jsoup.clean(source, Whitelist.none())); + } + + public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) { + throw new RuntimeException("Stub!"); + } + + public static String toHtml(Spanned text) { + return text.toString(); + } + + /** From: http://stackoverflow.com/a/25228492/5054192 **/ + public static String escapeHtml(CharSequence s) { + StringBuilder out = new StringBuilder(Math.max(16, s.length())); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c > 127 || c == '"' || c == '<' || c == '>' || c == '&') { + out.append("&#"); + out.append((int) c); + out.append(';'); + } else { + out.append(c); + } + } + return out.toString(); + } + + public interface TagHandler { + void handleTag(boolean var1, String var2, Editable var3, XMLReader var4); + } + + public interface ImageGetter { + Drawable getDrawable(String var1); + } + + private static class FakeSpanned implements Spanned { + + String string; + + public FakeSpanned(String string) { + this.string = string; + } + + @Override + public T[] getSpans(int i, int i1, Class aClass) { + return null; + } + + @Override + public int getSpanStart(Object o) { + return 0; + } + + @Override + public int getSpanEnd(Object o) { + return 0; + } + + @Override + public int getSpanFlags(Object o) { + return 0; + } + + @Override + public int nextSpanTransition(int i, int i1, Class aClass) { + return 0; + } + + @Override + public int length() { + return 0; + } + + @Override + public char charAt(int index) { + return 0; + } + + @Override + public CharSequence subSequence(int start, int end) { + return null; + } + + @NotNull + @Override + public String toString() { + return string; + } + } +} diff --git a/AndroidCompat/src/main/java/android/text/TextUtils.java b/AndroidCompat/src/main/java/android/text/TextUtils.java new file mode 100644 index 00000000..003890e4 --- /dev/null +++ b/AndroidCompat/src/main/java/android/text/TextUtils.java @@ -0,0 +1,1044 @@ +/* + * 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 android.text; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.text.style.ReplacementSpan; +import android.util.Printer; +import com.android.internal.util.ArrayUtils; + +import java.lang.reflect.Array; +import java.util.Iterator; +import java.util.regex.Pattern; +public class TextUtils { + private static final String TAG = "TextUtils"; + /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..." + /** {@hide} */ + public static final String ELLIPSIS_STRING = new String(ELLIPSIS_NORMAL); + /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".." + private static final String ELLIPSIS_TWO_DOTS_STRING = new String(ELLIPSIS_TWO_DOTS); + private TextUtils() { /* cannot be instantiated */ } + public static void getChars(CharSequence s, int start, int end, + char[] dest, int destoff) { + Class c = s.getClass(); + if (c == String.class) + ((String) s).getChars(start, end, dest, destoff); + else if (c == StringBuffer.class) + ((StringBuffer) s).getChars(start, end, dest, destoff); + else if (c == StringBuilder.class) + ((StringBuilder) s).getChars(start, end, dest, destoff); + else if (s instanceof GetChars) + ((GetChars) s).getChars(start, end, dest, destoff); + else { + for (int i = start; i < end; i++) + dest[destoff++] = s.charAt(i); + } + } + public static int indexOf(CharSequence s, char ch) { + return indexOf(s, ch, 0); + } + public static int indexOf(CharSequence s, char ch, int start) { + Class c = s.getClass(); + if (c == String.class) + return ((String) s).indexOf(ch, start); + return indexOf(s, ch, start, s.length()); + } + public static int indexOf(CharSequence s, char ch, int start, int end) { + Class c = s.getClass(); + if (s instanceof GetChars || c == StringBuffer.class || + c == StringBuilder.class || c == String.class) { + final int INDEX_INCREMENT = 500; + char[] temp = obtain(INDEX_INCREMENT); + while (start < end) { + int segend = start + INDEX_INCREMENT; + if (segend > end) + segend = end; + getChars(s, start, segend, temp, 0); + int count = segend - start; + for (int i = 0; i < count; i++) { + if (temp[i] == ch) { + recycle(temp); + return i + start; + } + } + start = segend; + } + recycle(temp); + return -1; + } + for (int i = start; i < end; i++) + if (s.charAt(i) == ch) + return i; + return -1; + } + public static int lastIndexOf(CharSequence s, char ch) { + return lastIndexOf(s, ch, s.length() - 1); + } + public static int lastIndexOf(CharSequence s, char ch, int last) { + Class c = s.getClass(); + if (c == String.class) + return ((String) s).lastIndexOf(ch, last); + return lastIndexOf(s, ch, 0, last); + } + public static int lastIndexOf(CharSequence s, char ch, + int start, int last) { + if (last < 0) + return -1; + if (last >= s.length()) + last = s.length() - 1; + int end = last + 1; + Class c = s.getClass(); + if (s instanceof GetChars || c == StringBuffer.class || + c == StringBuilder.class || c == String.class) { + final int INDEX_INCREMENT = 500; + char[] temp = obtain(INDEX_INCREMENT); + while (start < end) { + int segstart = end - INDEX_INCREMENT; + if (segstart < start) + segstart = start; + getChars(s, segstart, end, temp, 0); + int count = end - segstart; + for (int i = count - 1; i >= 0; i--) { + if (temp[i] == ch) { + recycle(temp); + return i + segstart; + } + } + end = segstart; + } + recycle(temp); + return -1; + } + for (int i = end - 1; i >= start; i--) + if (s.charAt(i) == ch) + return i; + return -1; + } + public static int indexOf(CharSequence s, CharSequence needle) { + return indexOf(s, needle, 0, s.length()); + } + public static int indexOf(CharSequence s, CharSequence needle, int start) { + return indexOf(s, needle, start, s.length()); + } + public static int indexOf(CharSequence s, CharSequence needle, + int start, int end) { + int nlen = needle.length(); + if (nlen == 0) + return start; + char c = needle.charAt(0); + for (;;) { + start = indexOf(s, c, start); + if (start > end - nlen) { + break; + } + if (start < 0) { + return -1; + } + if (regionMatches(s, start, needle, 0, nlen)) { + return start; + } + start++; + } + return -1; + } + public static boolean regionMatches(CharSequence one, int toffset, + CharSequence two, int ooffset, + int len) { + int tempLen = 2 * len; + if (tempLen < len) { + // Integer overflow; len is unreasonably large + throw new IndexOutOfBoundsException(); + } + char[] temp = obtain(tempLen); + getChars(one, toffset, toffset + len, temp, 0); + getChars(two, ooffset, ooffset + len, temp, len); + boolean match = true; + for (int i = 0; i < len; i++) { + if (temp[i] != temp[i + len]) { + match = false; + break; + } + } + recycle(temp); + return match; + } + /** + * Create a new String object containing the given range of characters + * from the source string. This is different than simply calling + * {@link CharSequence#subSequence(int, int) CharSequence.subSequence} + * in that it does not preserve any style runs in the source sequence, + * allowing a more efficient implementation. + */ + public static String substring(CharSequence source, int start, int end) { + if (source instanceof String) + return ((String) source).substring(start, end); + if (source instanceof StringBuilder) + return ((StringBuilder) source).substring(start, end); + if (source instanceof StringBuffer) + return ((StringBuffer) source).substring(start, end); + char[] temp = obtain(end - start); + getChars(source, start, end, temp, 0); + String ret = new String(temp, 0, end - start); + recycle(temp); + return ret; + } + /** + * Returns a string containing the tokens joined by delimiters. + * @param tokens an array objects to be joined. Strings will be formed from + * the objects by calling object.toString(). + */ + public static String join(CharSequence delimiter, Object[] tokens) { + StringBuilder sb = new StringBuilder(); + boolean firstTime = true; + for (Object token: tokens) { + if (firstTime) { + firstTime = false; + } else { + sb.append(delimiter); + } + sb.append(token); + } + return sb.toString(); + } + /** + * Returns a string containing the tokens joined by delimiters. + * @param tokens an array objects to be joined. Strings will be formed from + * the objects by calling object.toString(). + */ + public static String join(CharSequence delimiter, Iterable tokens) { + StringBuilder sb = new StringBuilder(); + Iterator it = tokens.iterator(); + if (it.hasNext()) { + sb.append(it.next()); + while (it.hasNext()) { + sb.append(delimiter); + sb.append(it.next()); + } + } + return sb.toString(); + } + /** + * String.split() returns [''] when the string to be split is empty. This returns []. This does + * not remove any empty strings from the result. For example split("a,", "," ) returns {"a", ""}. + * + * @param text the string to split + * @param expression the regular expression to match + * @return an array of strings. The array will be empty if text is empty + * + * @throws NullPointerException if expression or text is null + */ + public static String[] split(String text, String expression) { + if (text.length() == 0) { + return EMPTY_STRING_ARRAY; + } else { + return text.split(expression, -1); + } + } + /** + * Splits a string on a pattern. String.split() returns [''] when the string to be + * split is empty. This returns []. This does not remove any empty strings from the result. + * @param text the string to split + * @param pattern the regular expression to match + * @return an array of strings. The array will be empty if text is empty + * + * @throws NullPointerException if expression or text is null + */ + public static String[] split(String text, Pattern pattern) { + if (text.length() == 0) { + return EMPTY_STRING_ARRAY; + } else { + return pattern.split(text, -1); + } + } + /** + * An interface for splitting strings according to rules that are opaque to the user of this + * interface. This also has less overhead than split, which uses regular expressions and + * allocates an array to hold the results. + * + *

The most efficient way to use this class is: + * + *

+     * // Once
+     * TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(delimiter);
+     *
+     * // Once per string to split
+     * splitter.setString(string);
+     * for (String s : splitter) {
+     *     ...
+     * }
+     * 
+ */ + public interface StringSplitter extends Iterable { + public void setString(String string); + } + /** + * A simple string splitter. + * + *

If the final character in the string to split is the delimiter then no empty string will + * be returned for the empty string after that delimeter. That is, splitting "a,b," on + * comma will return "a", "b", not "a", "b", "". + */ + public static class SimpleStringSplitter implements StringSplitter, Iterator { + private String mString; + private char mDelimiter; + private int mPosition; + private int mLength; + /** + * Initializes the splitter. setString may be called later. + * @param delimiter the delimeter on which to split + */ + public SimpleStringSplitter(char delimiter) { + mDelimiter = delimiter; + } + /** + * Sets the string to split + * @param string the string to split + */ + public void setString(String string) { + mString = string; + mPosition = 0; + mLength = mString.length(); + } + public Iterator iterator() { + return this; + } + public boolean hasNext() { + return mPosition < mLength; + } + public String next() { + int end = mString.indexOf(mDelimiter, mPosition); + if (end == -1) { + end = mLength; + } + String nextString = mString.substring(mPosition, end); + mPosition = end + 1; // Skip the delimiter. + return nextString; + } + public void remove() { + throw new UnsupportedOperationException(); + } + } + public static CharSequence stringOrSpannedString(CharSequence source) { + if (source == null) + return null; + if (source instanceof SpannedString) + return source; + if (source instanceof Spanned) + return new SpannedString(source); + return source.toString(); + } + /** + * Returns true if the string is null or 0-length. + * @param str the string to be examined + * @return true if str is null or zero length + */ + public static boolean isEmpty(@Nullable CharSequence str) { + if (str == null || str.length() == 0) + return true; + else + return false; + } + /** {@hide} */ + public static String nullIfEmpty(@Nullable String str) { + return isEmpty(str) ? null : str; + } + /** + * Returns the length that the specified CharSequence would have if + * spaces and ASCII control characters were trimmed from the start and end, + * as by {@link String#trim}. + */ + public static int getTrimmedLength(CharSequence s) { + int len = s.length(); + int start = 0; + while (start < len && s.charAt(start) <= ' ') { + start++; + } + int end = len; + while (end > start && s.charAt(end - 1) <= ' ') { + end--; + } + return end - start; + } + /** + * Returns true if a and b are equal, including if they are both null. + *

Note: In platform versions 1.1 and earlier, this method only worked well if + * both the arguments were instances of String.

+ * @param a first CharSequence to check + * @param b second CharSequence to check + * @return true if a and b are equal + */ + public static boolean equals(CharSequence a, CharSequence b) { + if (a == b) return true; + int length; + if (a != null && b != null && (length = a.length()) == b.length()) { + if (a instanceof String && b instanceof String) { + return a.equals(b); + } else { + for (int i = 0; i < length; i++) { + if (a.charAt(i) != b.charAt(i)) return false; + } + return true; + } + } + return false; + } + /** + * This function only reverses individual {@code char}s and not their associated + * spans. It doesn't support surrogate pairs (that correspond to non-BMP code points), combining + * sequences or conjuncts either. + * @deprecated Do not use. + */ + @Deprecated + public static CharSequence getReverse(CharSequence source, int start, int end) { + return new Reverser(source, start, end); + } + private static class Reverser + implements CharSequence, GetChars + { + public Reverser(CharSequence source, int start, int end) { + mSource = source; + mStart = start; + mEnd = end; + } + public int length() { + return mEnd - mStart; + } + public CharSequence subSequence(int start, int end) { + char[] buf = new char[end - start]; + getChars(start, end, buf, 0); + return new String(buf); + } + @Override + public String toString() { + return subSequence(0, length()).toString(); + } + public char charAt(int off) { + return AndroidCharacter.getMirror(mSource.charAt(mEnd - 1 - off)); + } + public void getChars(int start, int end, char[] dest, int destoff) { + TextUtils.getChars(mSource, start + mStart, end + mStart, + dest, destoff); + AndroidCharacter.mirror(dest, 0, end - start); + int len = end - start; + int n = (end - start) / 2; + for (int i = 0; i < n; i++) { + char tmp = dest[destoff + i]; + dest[destoff + i] = dest[destoff + len - i - 1]; + dest[destoff + len - i - 1] = tmp; + } + } + private CharSequence mSource; + private int mStart; + private int mEnd; + } + private static void writeWhere(Parcel p, Spanned sp, Object o) { + p.writeInt(sp.getSpanStart(o)); + p.writeInt(sp.getSpanEnd(o)); + p.writeInt(sp.getSpanFlags(o)); + } + /** + * Debugging tool to print the spans in a CharSequence. The output will + * be printed one span per line. If the CharSequence is not a Spanned, + * then the entire string will be printed on a single line. + */ + public static void dumpSpans(CharSequence cs, Printer printer, String prefix) { + if (cs instanceof Spanned) { + Spanned sp = (Spanned) cs; + Object[] os = sp.getSpans(0, cs.length(), Object.class); + for (int i = 0; i < os.length; i++) { + Object o = os[i]; + printer.println(prefix + cs.subSequence(sp.getSpanStart(o), + sp.getSpanEnd(o)) + ": " + + Integer.toHexString(System.identityHashCode(o)) + + " " + o.getClass().getCanonicalName() + + " (" + sp.getSpanStart(o) + "-" + sp.getSpanEnd(o) + + ") fl=#" + sp.getSpanFlags(o)); + } + } else { + printer.println(prefix + cs + ": (no spans)"); + } + } + /** + * Return a new CharSequence in which each of the source strings is + * replaced by the corresponding element of the destinations. + */ + public static CharSequence replace(CharSequence template, + String[] sources, + CharSequence[] destinations) { + SpannableStringBuilder tb = new SpannableStringBuilder(template); + for (int i = 0; i < sources.length; i++) { + int where = indexOf(tb, sources[i]); + if (where >= 0) + tb.setSpan(sources[i], where, where + sources[i].length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + for (int i = 0; i < sources.length; i++) { + int start = tb.getSpanStart(sources[i]); + int end = tb.getSpanEnd(sources[i]); + if (start >= 0) { + tb.replace(start, end, destinations[i]); + } + } + return tb; + } + /** + * Replace instances of "^1", "^2", etc. in the + * template CharSequence with the corresponding + * values. "^^" is used to produce a single caret in + * the output. Only up to 9 replacement values are supported, + * "^10" will be produce the first replacement value followed by a + * '0'. + * + * @param template the input text containing "^1"-style + * placeholder values. This object is not modified; a copy is + * returned. + * + * @param values CharSequences substituted into the template. The + * first is substituted for "^1", the second for "^2", and so on. + * + * @return the new CharSequence produced by doing the replacement + * + * @throws IllegalArgumentException if the template requests a + * value that was not provided, or if more than 9 values are + * provided. + */ + public static CharSequence expandTemplate(CharSequence template, + CharSequence... values) { + if (values.length > 9) { + throw new IllegalArgumentException("max of 9 values are supported"); + } + SpannableStringBuilder ssb = new SpannableStringBuilder(template); + try { + int i = 0; + while (i < ssb.length()) { + if (ssb.charAt(i) == '^') { + char next = ssb.charAt(i+1); + if (next == '^') { + ssb.delete(i+1, i+2); + ++i; + continue; + } else if (Character.isDigit(next)) { + int which = Character.getNumericValue(next) - 1; + if (which < 0) { + throw new IllegalArgumentException( + "template requests value ^" + (which+1)); + } + if (which >= values.length) { + throw new IllegalArgumentException( + "template requests value ^" + (which+1) + + "; only " + values.length + " provided"); + } + ssb.replace(i, i+2, values[which]); + i += values[which].length(); + continue; + } + } + ++i; + } + } catch (IndexOutOfBoundsException ignore) { + // happens when ^ is the last character in the string. + } + return ssb; + } + public static int getOffsetBefore(CharSequence text, int offset) { + if (offset == 0) + return 0; + if (offset == 1) + return 0; + char c = text.charAt(offset - 1); + if (c >= '\uDC00' && c <= '\uDFFF') { + char c1 = text.charAt(offset - 2); + if (c1 >= '\uD800' && c1 <= '\uDBFF') + offset -= 2; + else + offset -= 1; + } else { + offset -= 1; + } + if (text instanceof Spanned) { + ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset, + ReplacementSpan.class); + for (int i = 0; i < spans.length; i++) { + int start = ((Spanned) text).getSpanStart(spans[i]); + int end = ((Spanned) text).getSpanEnd(spans[i]); + if (start < offset && end > offset) + offset = start; + } + } + return offset; + } + public static int getOffsetAfter(CharSequence text, int offset) { + int len = text.length(); + if (offset == len) + return len; + if (offset == len - 1) + return len; + char c = text.charAt(offset); + if (c >= '\uD800' && c <= '\uDBFF') { + char c1 = text.charAt(offset + 1); + if (c1 >= '\uDC00' && c1 <= '\uDFFF') + offset += 2; + else + offset += 1; + } else { + offset += 1; + } + if (text instanceof Spanned) { + ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset, + ReplacementSpan.class); + for (int i = 0; i < spans.length; i++) { + int start = ((Spanned) text).getSpanStart(spans[i]); + int end = ((Spanned) text).getSpanEnd(spans[i]); + if (start < offset && end > offset) + offset = end; + } + } + return offset; + } + private static void readSpan(Parcel p, Spannable sp, Object o) { + sp.setSpan(o, p.readInt(), p.readInt(), p.readInt()); + } + /** + * Copies the spans from the region start...end in + * source to the region + * destoff...destoff+end-start in dest. + * Spans in source that begin before start + * or end after end but overlap this range are trimmed + * as if they began at start or ended at end. + * + * @throws IndexOutOfBoundsException if any of the copied spans + * are out of range in dest. + */ + public static void copySpansFrom(Spanned source, int start, int end, + Class kind, + Spannable dest, int destoff) { + if (kind == null) { + kind = Object.class; + } + Object[] spans = source.getSpans(start, end, kind); + for (int i = 0; i < spans.length; i++) { + int st = source.getSpanStart(spans[i]); + int en = source.getSpanEnd(spans[i]); + int fl = source.getSpanFlags(spans[i]); + if (st < start) + st = start; + if (en > end) + en = end; + dest.setSpan(spans[i], st - start + destoff, en - start + destoff, + fl); + } + } + public enum TruncateAt { + START, + MIDDLE, + END, + MARQUEE, + /** + * @hide + */ + END_SMALL + } + private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; + /* package */ + static boolean doesNotNeedBidi(CharSequence s, int start, int end) { + for (int i = start; i < end; i++) { + if (s.charAt(i) >= FIRST_RIGHT_TO_LEFT) { + return false; + } + } + return true; + } + /* package */ + static boolean doesNotNeedBidi(char[] text, int start, int len) { + for (int i = start, e = i + len; i < e; i++) { + if (text[i] >= FIRST_RIGHT_TO_LEFT) { + return false; + } + } + return true; + } + /* package */ static char[] obtain(int len) { + char[] buf; + synchronized (sLock) { + buf = sTemp; + sTemp = null; + } + if (buf == null || buf.length < len) + buf = ArrayUtils.newUnpaddedCharArray(len); + return buf; + } + /* package */ static void recycle(char[] temp) { + if (temp.length > 1000) + return; + synchronized (sLock) { + sTemp = temp; + } + } + /** + * Html-encode the string. + * @param s the string to be encoded + * @return the encoded string + */ + public static String htmlEncode(String s) { + StringBuilder sb = new StringBuilder(); + char c; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i); + switch (c) { + case '<': + sb.append("<"); //$NON-NLS-1$ + break; + case '>': + sb.append(">"); //$NON-NLS-1$ + break; + case '&': + sb.append("&"); //$NON-NLS-1$ + break; + case '\'': + //http://www.w3.org/TR/xhtml1 + // The named character reference ' (the apostrophe, U+0027) was introduced in + // XML 1.0 but does not appear in HTML. Authors should therefore use ' instead + // of ' to work as expected in HTML 4 user agents. + sb.append("'"); //$NON-NLS-1$ + break; + case '"': + sb.append("""); //$NON-NLS-1$ + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + /** + * Returns a CharSequence concatenating the specified CharSequences, + * retaining their spans if any. + */ + public static CharSequence concat(CharSequence... text) { + if (text.length == 0) { + return ""; + } + if (text.length == 1) { + return text[0]; + } + boolean spanned = false; + for (int i = 0; i < text.length; i++) { + if (text[i] instanceof Spanned) { + spanned = true; + break; + } + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < text.length; i++) { + sb.append(text[i]); + } + if (!spanned) { + return sb.toString(); + } + SpannableString ss = new SpannableString(sb); + int off = 0; + for (int i = 0; i < text.length; i++) { + int len = text[i].length(); + if (text[i] instanceof Spanned) { + copySpansFrom((Spanned) text[i], 0, len, Object.class, ss, off); + } + off += len; + } + return new SpannedString(ss); + } + /** + * Returns whether the given CharSequence contains any printable characters. + */ + public static boolean isGraphic(CharSequence str) { + final int len = str.length(); + for (int cp, i=0; ireqModes will be + * checked. Note that the caps mode flags here are explicitly defined + * to match those in {@link InputType}. + * + * @param cs The text that should be checked for caps modes. + * @param off Location in the text at which to check. + * @param reqModes The modes to be checked: may be any combination of + * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and + * {@link #CAP_MODE_SENTENCES}. + * + * @return Returns the actual capitalization modes that can be in effect + * at the current position, which is any combination of + * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and + * {@link #CAP_MODE_SENTENCES}. + */ + public static int getCapsMode(CharSequence cs, int off, int reqModes) { + if (off < 0) { + return 0; + } + int i; + char c; + int mode = 0; + if ((reqModes&CAP_MODE_CHARACTERS) != 0) { + mode |= CAP_MODE_CHARACTERS; + } + if ((reqModes&(CAP_MODE_WORDS|CAP_MODE_SENTENCES)) == 0) { + return mode; + } + // Back over allowed opening punctuation. + for (i = off; i > 0; i--) { + c = cs.charAt(i - 1); + if (c != '"' && c != '\'' && + Character.getType(c) != Character.START_PUNCTUATION) { + break; + } + } + // Start of paragraph, with optional whitespace. + int j = i; + while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) { + j--; + } + if (j == 0 || cs.charAt(j - 1) == '\n') { + return mode | CAP_MODE_WORDS; + } + // Or start of word if we are that style. + if ((reqModes&CAP_MODE_SENTENCES) == 0) { + if (i != j) mode |= CAP_MODE_WORDS; + return mode; + } + // There must be a space if not the start of paragraph. + if (i == j) { + return mode; + } + // Back over allowed closing punctuation. + for (; j > 0; j--) { + c = cs.charAt(j - 1); + if (c != '"' && c != '\'' && + Character.getType(c) != Character.END_PUNCTUATION) { + break; + } + } + if (j > 0) { + c = cs.charAt(j - 1); + if (c == '.' || c == '?' || c == '!') { + // Do not capitalize if the word ends with a period but + // also contains a period, in which case it is an abbreviation. + if (c == '.') { + for (int k = j - 2; k >= 0; k--) { + c = cs.charAt(k); + if (c == '.') { + return mode; + } + if (!Character.isLetter(c)) { + break; + } + } + } + return mode | CAP_MODE_SENTENCES; + } + } + return mode; + } + /** + * Does a comma-delimited list 'delimitedString' contain a certain item? + * (without allocating memory) + * + * @hide + */ + public static boolean delimitedStringContains( + String delimitedString, char delimiter, String item) { + if (isEmpty(delimitedString) || isEmpty(item)) { + return false; + } + int pos = -1; + int length = delimitedString.length(); + while ((pos = delimitedString.indexOf(item, pos + 1)) != -1) { + if (pos > 0 && delimitedString.charAt(pos - 1) != delimiter) { + continue; + } + int expectedDelimiterPos = pos + item.length(); + if (expectedDelimiterPos == length) { + // Match at end of string. + return true; + } + if (delimitedString.charAt(expectedDelimiterPos) == delimiter) { + return true; + } + } + return false; + } + /** + * Removes empty spans from the spans array. + * + * When parsing a Spanned using {@link Spanned#nextSpanTransition(int, int, Class)}, empty spans + * will (correctly) create span transitions, and calling getSpans on a slice of text bounded by + * one of these transitions will (correctly) include the empty overlapping span. + * + * However, these empty spans should not be taken into account when layouting or rendering the + * string and this method provides a way to filter getSpans' results accordingly. + * + * @param spans A list of spans retrieved using {@link Spanned#getSpans(int, int, Class)} from + * the spanned + * @param spanned The Spanned from which spans were extracted + * @return A subset of spans where empty spans ({@link Spanned#getSpanStart(Object)} == + * {@link Spanned#getSpanEnd(Object)} have been removed. The initial order is preserved + * @hide + */ + @SuppressWarnings("unchecked") + public static T[] removeEmptySpans(T[] spans, Spanned spanned, Class klass) { + T[] copy = null; + int count = 0; + for (int i = 0; i < spans.length; i++) { + final T span = spans[i]; + final int start = spanned.getSpanStart(span); + final int end = spanned.getSpanEnd(span); + if (start == end) { + if (copy == null) { + copy = (T[]) Array.newInstance(klass, spans.length - 1); + System.arraycopy(spans, 0, copy, 0, i); + count = i; + } + } else { + if (copy != null) { + copy[count] = span; + count++; + } + } + } + if (copy != null) { + T[] result = (T[]) Array.newInstance(klass, count); + System.arraycopy(copy, 0, result, 0, count); + return result; + } else { + return spans; + } + } + /** + * Pack 2 int values into a long, useful as a return value for a range + * @see #unpackRangeStartFromLong(long) + * @see #unpackRangeEndFromLong(long) + * @hide + */ + public static long packRangeInLong(int start, int end) { + return (((long) start) << 32) | end; + } + /** + * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)} + * @see #unpackRangeEndFromLong(long) + * @see #packRangeInLong(int, int) + * @hide + */ + public static int unpackRangeStartFromLong(long range) { + return (int) (range >>> 32); + } + /** + * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)} + * @see #unpackRangeStartFromLong(long) + * @see #packRangeInLong(int, int) + * @hide + */ + public static int unpackRangeEndFromLong(long range) { + return (int) (range & 0x00000000FFFFFFFFL); + } + private static Object sLock = new Object(); + private static char[] sTemp = null; + private static String[] EMPTY_STRING_ARRAY = new String[]{}; + private static final char ZWNBS_CHAR = '\uFEFF'; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/text/format/Formatter.java b/AndroidCompat/src/main/java/android/text/format/Formatter.java new file mode 100644 index 00000000..9b1cd4d8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/text/format/Formatter.java @@ -0,0 +1,31 @@ +package android.text.format; + +import android.content.Context; + +import java.text.DecimalFormat; + +/** + * Custom reimplementation of some of the methods used in Android. + */ +public class Formatter { + private Formatter() { + throw new RuntimeException("Stub!"); + } + + public static String formatFileSize(Context context, long size) { + if(size <= 0) return "0"; + final String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; + int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); + return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + public static String formatShortFileSize(Context context, long sizeBytes) { + return formatFileSize(context, sizeBytes); + } + + /** @deprecated */ + @Deprecated + public static String formatIpAddress(int ipv4Address) { + throw new RuntimeException("Stub!"); + } +} diff --git a/AndroidCompat/src/main/java/android/util/ArrayMap.java b/AndroidCompat/src/main/java/android/util/ArrayMap.java new file mode 100644 index 00000000..5ea423e8 --- /dev/null +++ b/AndroidCompat/src/main/java/android/util/ArrayMap.java @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2013 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 android.util; +import libcore.util.EmptyArray; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Map; +import java.util.Set; +/** + * ArrayMap is a generic key->value mapping data structure that is + * designed to be more memory efficient than a traditional {@link java.util.HashMap}. + * It keeps its mappings in an array data structure -- an integer array of hash + * codes for each item, and an Object array of the key/value pairs. This allows it to + * avoid having to create an extra object for every entry put in to the map, and it + * also tries to control the growth of the size of these arrays more aggressively + * (since growing them only requires copying the entries in the array, not rebuilding + * a hash map). + * + *

Note that this implementation is not intended to be appropriate for data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

Because this container is intended to better balance memory use, unlike most other + * standard Java containers it will shrink its array as items are removed from it. Currently + * you have no control over this shrinking -- if you set a capacity and then remove an + * item, it may reduce the capacity to better match the current size. In the future an + * explicit call to set the capacity should turn off this aggressive shrinking behavior.

+ */ +public final class ArrayMap implements Map { + private static final boolean DEBUG = false; + private static final String TAG = "ArrayMap"; + /** + * Attempt to spot concurrent modifications to this data structure. + * + * It's best-effort, but any time we can throw something more diagnostic than an + * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to + * save a lot of development time. + * + * Good times to look for CME include after any allocArrays() call and at the end of + * functions that change mSize (put/remove/clear). + */ + private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true; + /** + * The minimum amount by which the capacity of a ArrayMap will increase. + * This is tuned to be relatively space-efficient. + */ + private static final int BASE_SIZE = 4; + /** + * Maximum number of entries to have in array caches. + */ + private static final int CACHE_SIZE = 10; + /** + * Special hash array value that indicates the container is immutable. + */ + static final int[] EMPTY_IMMUTABLE_INTS = new int[0]; + /** + * @hide Special immutable empty ArrayMap. + */ + public static final ArrayMap EMPTY = new ArrayMap<>(-1); + /** + * Caches of small array objects to avoid spamming garbage. The cache + * Object[] variable is a pointer to a linked list of array objects. + * The first entry in the array is a pointer to the next array in the + * list; the second entry is a pointer to the int[] hash code array for it. + */ + static Object[] mBaseCache; + static int mBaseCacheSize; + static Object[] mTwiceBaseCache; + static int mTwiceBaseCacheSize; + final boolean mIdentityHashCode; + int[] mHashes; + Object[] mArray; + int mSize; + MapCollections mCollections; + private static int binarySearchHashes(int[] hashes, int N, int hash) { + try { + return ContainerHelpers.binarySearch(hashes, N, hash); + } catch (ArrayIndexOutOfBoundsException e) { + if (CONCURRENT_MODIFICATION_EXCEPTIONS) { + throw new ConcurrentModificationException(); + } else { + throw e; // the cache is poisoned at this point, there's not much we can do + } + } + } + int indexOf(Object key, int hash) { + final int N = mSize; + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + int index = binarySearchHashes(mHashes, N, hash); + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + // If the key at the returned index matches, that's what we want. + if (key.equals(mArray[index<<1])) { + return index; + } + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == hash; end++) { + if (key.equals(mArray[end << 1])) return end; + } + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { + if (key.equals(mArray[i << 1])) return i; + } + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + int indexOfNull() { + final int N = mSize; + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + int index = binarySearchHashes(mHashes, N, 0); + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + // If the key at the returned index matches, that's what we want. + if (null == mArray[index<<1]) { + return index; + } + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == 0; end++) { + if (null == mArray[end << 1]) return end; + } + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { + if (null == mArray[i << 1]) return i; + } + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + private void allocArrays(final int size) { + if (mHashes == EMPTY_IMMUTABLE_INTS) { + throw new UnsupportedOperationException("ArrayMap is immutable"); + } + if (size == (BASE_SIZE*2)) { + synchronized (ArrayMap.class) { + if (mTwiceBaseCache != null) { + final Object[] array = mTwiceBaseCache; + mArray = array; + mTwiceBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mTwiceBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + + " now have " + mTwiceBaseCacheSize + " entries"); + return; + } + } + } else if (size == BASE_SIZE) { + synchronized (ArrayMap.class) { + if (mBaseCache != null) { + final Object[] array = mBaseCache; + mArray = array; + mBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + + " now have " + mBaseCacheSize + " entries"); + return; + } + } + } + mHashes = new int[size]; + mArray = new Object[size<<1]; + } + private static void freeArrays(final int[] hashes, final Object[] array, final int size) { + if (hashes.length == (BASE_SIZE*2)) { + synchronized (ArrayMap.class) { + if (mTwiceBaseCacheSize < CACHE_SIZE) { + array[0] = mTwiceBaseCache; + array[1] = hashes; + for (int i=(size<<1)-1; i>=2; i--) { + array[i] = null; + } + mTwiceBaseCache = array; + mTwiceBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + + " now have " + mTwiceBaseCacheSize + " entries"); + } + } + } else if (hashes.length == BASE_SIZE) { + synchronized (ArrayMap.class) { + if (mBaseCacheSize < CACHE_SIZE) { + array[0] = mBaseCache; + array[1] = hashes; + for (int i=(size<<1)-1; i>=2; i--) { + array[i] = null; + } + mBaseCache = array; + mBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + + " now have " + mBaseCacheSize + " entries"); + } + } + } + } + /** + * Create a new empty ArrayMap. The default capacity of an array map is 0, and + * will grow once items are added to it. + */ + public ArrayMap() { + this(0, false); + } + /** + * Create a new ArrayMap with a given initial capacity. + */ + public ArrayMap(int capacity) { + this(capacity, false); + } + /** {@hide} */ + public ArrayMap(int capacity, boolean identityHashCode) { + mIdentityHashCode = identityHashCode; + // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS + // instance instead of the usual EmptyArray.INT. The reference + // is checked later to see if the array is allowed to grow. + if (capacity < 0) { + mHashes = EMPTY_IMMUTABLE_INTS; + mArray = EmptyArray.OBJECT; + } else if (capacity == 0) { + mHashes = EmptyArray.INT; + mArray = EmptyArray.OBJECT; + } else { + allocArrays(capacity); + } + mSize = 0; + } + /** + * Create a new ArrayMap with the mappings from the given ArrayMap. + */ + public ArrayMap(ArrayMap map) { + this(); + if (map != null) { + putAll(map); + } + } + /** + * Make the array map empty. All storage is released. + */ + @Override + public void clear() { + if (mSize > 0) { + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + final int osize = mSize; + mHashes = EmptyArray.INT; + mArray = EmptyArray.OBJECT; + mSize = 0; + freeArrays(ohashes, oarray, osize); + } + if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) { + throw new ConcurrentModificationException(); + } + } + /** + * @hide + * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap. + */ + public void erase() { + if (mSize > 0) { + final int N = mSize<<1; + final Object[] array = mArray; + for (int i=0; iminimumCapacity + * items. + */ + public void ensureCapacity(int minimumCapacity) { + final int osize = mSize; + if (mHashes.length < minimumCapacity) { + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(minimumCapacity); + if (mSize > 0) { + System.arraycopy(ohashes, 0, mHashes, 0, osize); + System.arraycopy(oarray, 0, mArray, 0, osize<<1); + } + freeArrays(ohashes, oarray, osize); + } + if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) { + throw new ConcurrentModificationException(); + } + } + /** + * Check whether a key exists in the array. + * + * @param key The key to search for. + * @return Returns true if the key exists, else false. + */ + @Override + public boolean containsKey(Object key) { + return indexOfKey(key) >= 0; + } + /** + * Returns the index of a key in the set. + * + * @param key The key to search for. + * @return Returns the index of the key if it exists, else a negative integer. + */ + public int indexOfKey(Object key) { + return key == null ? indexOfNull() + : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode()); + } + int indexOfValue(Object value) { + final int N = mSize*2; + final Object[] array = mArray; + if (value == null) { + for (int i=1; i>1; + } + } + } else { + for (int i=1; i>1; + } + } + } + return -1; + } + /** + * Check whether a value exists in the array. This requires a linear search + * through the entire array. + * + * @param value The value to search for. + * @return Returns true if the value exists, else false. + */ + @Override + public boolean containsValue(Object value) { + return indexOfValue(value) >= 0; + } + /** + * Retrieve a value from the array. + * @param key The key of the value to retrieve. + * @return Returns the value associated with the given key, + * or null if there is no such key. + */ + @Override + public V get(Object key) { + final int index = indexOfKey(key); + return index >= 0 ? (V)mArray[(index<<1)+1] : null; + } + /** + * Return the key at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the key stored at the given index. + */ + public K keyAt(int index) { + return (K)mArray[index << 1]; + } + /** + * Return the value at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value stored at the given index. + */ + public V valueAt(int index) { + return (V)mArray[(index << 1) + 1]; + } + /** + * Set the value at a given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @param value The new value to store at this index. + * @return Returns the previous value at the given index. + */ + public V setValueAt(int index, V value) { + index = (index << 1) + 1; + V old = (V)mArray[index]; + mArray[index] = value; + return old; + } + /** + * Return true if the array map contains no items. + */ + @Override + public boolean isEmpty() { + return mSize <= 0; + } + /** + * Add a new value to the array map. + * @param key The key under which to store the value. If + * this key already exists in the array, its value will be replaced. + * @param value The value to store for the given key. + * @return Returns the old value that was stored for the given key, or null if there + * was no such key. + */ + @Override + public V put(K key, V value) { + final int osize = mSize; + final int hash; + int index; + if (key == null) { + hash = 0; + index = indexOfNull(); + } else { + hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode(); + index = indexOf(key, hash); + } + if (index >= 0) { + index = (index<<1) + 1; + final V old = (V)mArray[index]; + mArray[index] = value; + return old; + } + index = ~index; + if (osize >= mHashes.length) { + final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)) + : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); + if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) { + throw new ConcurrentModificationException(); + } + if (mHashes.length > 0) { + if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); + System.arraycopy(oarray, 0, mArray, 0, oarray.length); + } + freeArrays(ohashes, oarray, osize); + } + if (index < osize) { + if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index) + + " to " + (index+1)); + System.arraycopy(mHashes, index, mHashes, index + 1, osize - index); + System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); + } + if (CONCURRENT_MODIFICATION_EXCEPTIONS) { + if (osize != mSize || index >= mHashes.length) { + throw new ConcurrentModificationException(); + } + } + mHashes[index] = hash; + mArray[index<<1] = key; + mArray[(index<<1)+1] = value; + mSize++; + return null; + } + /** + * Special fast path for appending items to the end of the array without validation. + * The array must already be large enough to contain the item. + * @hide + */ + public void append(K key, V value) { + int index = mSize; + final int hash = key == null ? 0 + : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode()); + if (index >= mHashes.length) { + throw new IllegalStateException("Array is full"); + } + if (index > 0 && mHashes[index-1] > hash) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "New hash " + hash + + " is before end of array hash " + mHashes[index-1] + + " at index " + index + " key " + key, e); + put(key, value); + return; + } + mSize = index+1; + mHashes[index] = hash; + index <<= 1; + mArray[index] = key; + mArray[index+1] = value; + } + /** + * The use of the {@link #append} function can result in invalid array maps, in particular + * an array map where the same key appears multiple times. This function verifies that + * the array map is valid, throwing IllegalArgumentException if a problem is found. The + * main use for this method is validating an array map after unpacking from an IPC, to + * protect against malicious callers. + * @hide + */ + public void validate() { + final int N = mSize; + if (N <= 1) { + // There can't be dups. + return; + } + int basehash = mHashes[0]; + int basei = 0; + for (int i=1; i=basei; j--) { + final Object prev = mArray[j<<1]; + if (cur == prev) { + throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); + } + if (cur != null && prev != null && cur.equals(prev)) { + throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); + } + } + } + } + /** + * Perform a {@link #put(Object, Object)} of all key/value pairs in array + * @param array The array whose contents are to be retrieved. + */ + public void putAll(ArrayMap array) { + final int N = array.mSize; + ensureCapacity(mSize + N); + if (mSize == 0) { + if (N > 0) { + System.arraycopy(array.mHashes, 0, mHashes, 0, N); + System.arraycopy(array.mArray, 0, mArray, 0, N<<1); + mSize = N; + } + } else { + for (int i=0; i= 0) { + return removeAt(index); + } + return null; + } + /** + * Remove the key/value mapping at the given index. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value that was stored at this index. + */ + public V removeAt(int index) { + final Object old = mArray[(index << 1) + 1]; + final int osize = mSize; + final int nsize; + if (osize <= 1) { + // Now empty. + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); + freeArrays(mHashes, mArray, osize); + mHashes = EmptyArray.INT; + mArray = EmptyArray.OBJECT; + nsize = 0; + } else { + nsize = osize - 1; + if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { + // Shrunk enough to reduce size of arrays. We don't allow it to + // shrink smaller than (BASE_SIZE*2) to avoid flapping between + // that and BASE_SIZE. + final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2); + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) { + throw new ConcurrentModificationException(); + } + if (index > 0) { + if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, index); + System.arraycopy(oarray, 0, mArray, 0, index << 1); + } + if (index < nsize) { + if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize + + " to " + index); + System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index); + System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, + (nsize - index) << 1); + } + } else { + if (index < nsize) { + if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize + + " to " + index); + System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index); + System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, + (nsize - index) << 1); + } + mArray[nsize << 1] = null; + mArray[(nsize << 1) + 1] = null; + } + } + if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) { + throw new ConcurrentModificationException(); + } + mSize = nsize; + return (V)old; + } + /** + * Return the number of items in this array map. + */ + @Override + public int size() { + return mSize; + } + /** + * {@inheritDoc} + * + *

This implementation returns false if the object is not a map, or + * if the maps have different sizes. Otherwise, for each key in this map, + * values of both maps are compared. If the values for any key are not + * equal, the method returns false, otherwise it returns true. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Map) { + Map map = (Map) object; + if (size() != map.size()) { + return false; + } + try { + for (int i=0; iThis implementation composes a string by iterating over its mappings. If + * this map contains itself as a key or a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (isEmpty()) { + return "{}"; + } + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + Object key = keyAt(i); + if (key != this) { + buffer.append(key); + } else { + buffer.append("(this Map)"); + } + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); + } + // ------------------------------------------------------------------------ + // Interop with traditional Java containers. Not as efficient as using + // specialized collection APIs. + // ------------------------------------------------------------------------ + private MapCollections getCollection() { + if (mCollections == null) { + mCollections = new MapCollections() { + @Override + protected int colGetSize() { + return mSize; + } + @Override + protected Object colGetEntry(int index, int offset) { + return mArray[(index<<1) + offset]; + } + @Override + protected int colIndexOfKey(Object key) { + return indexOfKey(key); + } + @Override + protected int colIndexOfValue(Object value) { + return indexOfValue(value); + } + @Override + protected Map colGetMap() { + return ArrayMap.this; + } + @Override + protected void colPut(K key, V value) { + put(key, value); + } + @Override + protected V colSetValue(int index, V value) { + return setValueAt(index, value); + } + @Override + protected void colRemoveAt(int index) { + removeAt(index); + } + @Override + protected void colClear() { + clear(); + } + }; + } + return mCollections; + } + /** + * Determine if the array map contains all of the keys in the given collection. + * @param collection The collection whose contents are to be checked against. + * @return Returns true if this array map contains a key for every entry + * in collection, else returns false. + */ + public boolean containsAll(Collection collection) { + return MapCollections.containsAllHelper(this, collection); + } + /** + * Perform a {@link #put(Object, Object)} of all key/value pairs in map + * @param map The map whose contents are to be retrieved. + */ + @Override + public void putAll(Map map) { + ensureCapacity(mSize + map.size()); + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + /** + * Remove all keys in the array map that exist in the given collection. + * @param collection The collection whose contents are to be used to remove keys. + * @return Returns true if any keys were removed from the array map, else false. + */ + public boolean removeAll(Collection collection) { + return MapCollections.removeAllHelper(this, collection); + } + /** + * Remove all keys in the array map that do not exist in the given collection. + * @param collection The collection whose contents are to be used to determine which + * keys to keep. + * @return Returns true if any keys were removed from the array map, else false. + */ + public boolean retainAll(Collection collection) { + return MapCollections.retainAllHelper(this, collection); + } + /** + * Return a {@link java.util.Set} for iterating over and interacting with all mappings + * in the array map. + * + *

Note: this is a very inefficient way to access the array contents, it + * requires generating a number of temporary objects and allocates additional state + * information associated with the container that will remain for the life of the container.

+ * + *

Note:

the semantics of this + * Set are subtly different than that of a {@link java.util.HashMap}: most important, + * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single + * object that exists for the entire iterator, so you can not hold on to it + * after calling {@link java.util.Iterator#next() Iterator.next}.

+ */ + @Override + public Set> entrySet() { + return getCollection().getEntrySet(); + } + /** + * Return a {@link java.util.Set} for iterating over and interacting with all keys + * in the array map. + * + *

Note: this is a fairly inefficient way to access the array contents, it + * requires generating a number of temporary objects and allocates additional state + * information associated with the container that will remain for the life of the container.

+ */ + @Override + public Set keySet() { + return getCollection().getKeySet(); + } + /** + * Return a {@link java.util.Collection} for iterating over and interacting with all values + * in the array map. + * + *

Note: this is a fairly inefficient way to access the array contents, it + * requires generating a number of temporary objects and allocates additional state + * information associated with the container that will remain for the life of the container.

+ */ + @Override + public Collection values() { + return getCollection().getValues(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/util/Base64.java b/AndroidCompat/src/main/java/android/util/Base64.java new file mode 100644 index 00000000..5c375067 --- /dev/null +++ b/AndroidCompat/src/main/java/android/util/Base64.java @@ -0,0 +1,676 @@ +/* + * 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 android.util; + +import java.nio.charset.StandardCharsets; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + /** + * Flag to pass to {@link Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + private Base64() { + } // don't instantiate + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + return new String(encode(input, flags), StandardCharsets.US_ASCII); + } + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + return new String(encode(input, offset, len, flags), StandardCharsets.US_ASCII); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: + break; + case 1: + output_len += 2; + break; + case 2: + output_len += 3; + break; + } + } + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + assert encoder.op == output_len; + return encoder.output; + } + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int[] DECODE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int[] DECODE_WEBSAFE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Non-data values in the DECODE arrays. + */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + final private int[] alphabet; + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + public Decoder(int flags, byte[] output) { + this.output = output; + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3 / 4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + int p = offset; + len += offset; + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p + 4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p + 1] & 0xff] << 12) | + (alphabet[input[p + 2] & 0xff] << 6) | + (alphabet[input[p + 3] & 0xff]))) >= 0) { + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + int d = alphabet[input[p++] & 0xff]; + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op + 1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + this.state = state; + this.op = op; + return true; + } + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE_WEBSAFE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] tail; + final private byte[] alphabet; + /* package */ int tailLen; + private int count; + + public Encoder(int flags, byte[] output) { + this.output = output; + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + tail = new byte[2]; + tailLen = 0; + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8 / 5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + int p = offset; + len += offset; + int v = -1; + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p + 2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + case 2: + if (p + 1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p + 3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p + 1] & 0xff) << 8) | + (input[p + 2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op + 1] = alphabet[(v >> 12) & 0x3f]; + output[op + 2] = alphabet[(v >> 6) & 0x3f]; + output[op + 3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + if (p - tailLen == len - 1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p - tailLen == len - 2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + if (p == len - 1) { + tail[tailLen++] = input[p]; + } else if (p == len - 2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p + 1]; + } + } + this.op = op; + this.count = count; + return true; + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/util/ContainerHelpers.java b/AndroidCompat/src/main/java/android/util/ContainerHelpers.java new file mode 100644 index 00000000..de76e430 --- /dev/null +++ b/AndroidCompat/src/main/java/android/util/ContainerHelpers.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013 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 android.util; +class ContainerHelpers { + // This is Arrays.binarySearch(), but doesn't do any argument validation. + static int binarySearch(int[] array, int size, int value) { + int lo = 0; + int hi = size - 1; + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final int midVal = array[mid]; + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + static int binarySearch(long[] array, int size, long value) { + int lo = 0; + int hi = size - 1; + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final long midVal = array[mid]; + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/util/Log.java b/AndroidCompat/src/main/java/android/util/Log.java new file mode 100644 index 00000000..03314a9c --- /dev/null +++ b/AndroidCompat/src/main/java/android/util/Log.java @@ -0,0 +1,139 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package android.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public final class Log { + public static final int ASSERT = 7; + public static final int DEBUG = 3; + public static final int ERROR = 6; + public static final int INFO = 4; + public static final int VERBOSE = 2; + public static final int WARN = 5; + + private static Logger logger = LoggerFactory.getLogger(Log.class); + + public static int v(String tag, String msg) { + return log(VERBOSE, tag, msg); + } + + public static int v(String tag, String msg, Throwable tr) { + return log(VERBOSE, tag, msg, tr); + } + + public static int d(String tag, String msg) { + return log(DEBUG, tag, msg); + } + + public static int d(String tag, String msg, Throwable tr) { + return log(DEBUG, tag, msg, tr); + } + + public static int i(String tag, String msg) { + return log(INFO, tag, msg); + } + + public static int i(String tag, String msg, Throwable tr) { + return log(INFO, tag, msg, tr); + } + + public static int w(String tag, String msg) { + return log(WARN, tag, msg); + } + + public static int w(String tag, String msg, Throwable tr) { + return log(WARN, tag, msg, tr); + } + + public static boolean isLoggable(String var0, int var1) { + return true; + } + + public static int w(String tag, Throwable tr) { + return log(WARN, tag, tr); + } + + public static int e(String tag, String msg) { + return log(ERROR, tag, msg); + } + + public static int e(String tag, String msg, Throwable tr) { + return log(ERROR, tag, msg, tr); + } + + //Level? + public static int wtf(String tag, String msg) { + return log(ERROR, tag, msg); + } + public static int wtf(String tag, Throwable tr) { + return log(ERROR, tag, tr); + } + public static int wtf(String tag, String msg, Throwable tr) { + return log(ERROR, tag, msg, tr); + } + + public static String getStackTraceString(Throwable tr) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + tr.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + public static int println(int priority, String tag, String msg) { + return log(priority, tag, msg); + } + + private static int log(int level, String tag, String msg) { + logger.info(formatLog(level, tag, msg)); + return tag.length() + msg.length(); //Not accurate, but never used anyways + } + + private static int log(int level, String tag, Throwable t) { + return log(level, tag, "An exception occured!", t); + } + + private static int log(int level, String tag, String msg, Throwable t) { + logger.info(formatLog(level, tag, msg), t); + return tag.length() + msg.length(); //Not accurate, but never used anyways + } + + private static String formatLog(int level, String tag, String msg) { + StringBuilder first = new StringBuilder("["); + switch(level) { + case ASSERT: + first.append("ASSERT"); + break; + case DEBUG: + first.append("DEBUG"); + break; + case ERROR: + first.append("ERROR"); + break; + case INFO: + first.append("INFO"); + break; + case VERBOSE: + first.append("VERBOSE"); + break; + case WARN: + first.append("WARN"); + break; + default: + first.append("UNKNOWN"); + break; + } + first.append("] "); + first.append(tag); + first.append(": "); + first.append(msg); + return first.toString(); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/util/MapCollections.java b/AndroidCompat/src/main/java/android/util/MapCollections.java new file mode 100644 index 00000000..860d2906 --- /dev/null +++ b/AndroidCompat/src/main/java/android/util/MapCollections.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2013 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 android.util; +import java.lang.reflect.Array; +import java.util.*; + +/** + * Helper for writing standard Java collection interfaces to a data + * structure like {@link ArrayMap}. + * @hide + */ +abstract class MapCollections { + EntrySet mEntrySet; + KeySet mKeySet; + ValuesCollection mValues; + final class ArrayIterator implements Iterator { + final int mOffset; + int mSize; + int mIndex; + boolean mCanRemove = false; + ArrayIterator(int offset) { + mOffset = offset; + mSize = colGetSize(); + } + @Override + public boolean hasNext() { + return mIndex < mSize; + } + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + Object res = colGetEntry(mIndex, mOffset); + mIndex++; + mCanRemove = true; + return (T)res; + } + @Override + public void remove() { + if (!mCanRemove) { + throw new IllegalStateException(); + } + mIndex--; + mSize--; + mCanRemove = false; + colRemoveAt(mIndex); + } + } + final class MapIterator implements Iterator>, Map.Entry { + int mEnd; + int mIndex; + boolean mEntryValid = false; + MapIterator() { + mEnd = colGetSize() - 1; + mIndex = -1; + } + @Override + public boolean hasNext() { + return mIndex < mEnd; + } + @Override + public Map.Entry next() { + if (!hasNext()) throw new NoSuchElementException(); + mIndex++; + mEntryValid = true; + return this; + } + @Override + public void remove() { + if (!mEntryValid) { + throw new IllegalStateException(); + } + colRemoveAt(mIndex); + mIndex--; + mEnd--; + mEntryValid = false; + } + @Override + public K getKey() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return (K)colGetEntry(mIndex, 0); + } + @Override + public V getValue() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return (V)colGetEntry(mIndex, 1); + } + @Override + public V setValue(V object) { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + return colSetValue(mIndex, object); + } + @Override + public final boolean equals(Object o) { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + return Objects.equals(e.getKey(), colGetEntry(mIndex, 0)) + && Objects.equals(e.getValue(), colGetEntry(mIndex, 1)); + } + @Override + public final int hashCode() { + if (!mEntryValid) { + throw new IllegalStateException( + "This container does not support retaining Map.Entry objects"); + } + final Object key = colGetEntry(mIndex, 0); + final Object value = colGetEntry(mIndex, 1); + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + @Override + public final String toString() { + return getKey() + "=" + getValue(); + } + } + final class EntrySet implements Set> { + @Override + public boolean add(Map.Entry object) { + throw new UnsupportedOperationException(); + } + @Override + public boolean addAll(Collection> collection) { + int oldSize = colGetSize(); + for (Map.Entry entry : collection) { + colPut(entry.getKey(), entry.getValue()); + } + return oldSize != colGetSize(); + } + @Override + public void clear() { + colClear(); + } + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + int index = colIndexOfKey(e.getKey()); + if (index < 0) { + return false; + } + Object foundVal = colGetEntry(index, 1); + return Objects.equals(foundVal, e.getValue()); + } + @Override + public boolean containsAll(Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + @Override + public Iterator> iterator() { + return new MapIterator(); + } + @Override + public boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + @Override + public boolean removeAll(Collection collection) { + throw new UnsupportedOperationException(); + } + @Override + public boolean retainAll(Collection collection) { + throw new UnsupportedOperationException(); + } + @Override + public int size() { + return colGetSize(); + } + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + @Override + public T[] toArray(T[] array) { + throw new UnsupportedOperationException(); + } + @Override + public boolean equals(Object object) { + return equalsSetHelper(this, object); + } + @Override + public int hashCode() { + int result = 0; + for (int i=colGetSize()-1; i>=0; i--) { + final Object key = colGetEntry(i, 0); + final Object value = colGetEntry(i, 1); + result += ( (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()) ); + } + return result; + } + }; + final class KeySet implements Set { + @Override + public boolean add(K object) { + throw new UnsupportedOperationException(); + } + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + @Override + public void clear() { + colClear(); + } + @Override + public boolean contains(Object object) { + return colIndexOfKey(object) >= 0; + } + @Override + public boolean containsAll(Collection collection) { + return containsAllHelper(colGetMap(), collection); + } + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + @Override + public Iterator iterator() { + return new ArrayIterator(0); + } + @Override + public boolean remove(Object object) { + int index = colIndexOfKey(object); + if (index >= 0) { + colRemoveAt(index); + return true; + } + return false; + } + @Override + public boolean removeAll(Collection collection) { + return removeAllHelper(colGetMap(), collection); + } + @Override + public boolean retainAll(Collection collection) { + return retainAllHelper(colGetMap(), collection); + } + @Override + public int size() { + return colGetSize(); + } + @Override + public Object[] toArray() { + return toArrayHelper(0); + } + @Override + public T[] toArray(T[] array) { + return toArrayHelper(array, 0); + } + @Override + public boolean equals(Object object) { + return equalsSetHelper(this, object); + } + @Override + public int hashCode() { + int result = 0; + for (int i=colGetSize()-1; i>=0; i--) { + Object obj = colGetEntry(i, 0); + result += obj == null ? 0 : obj.hashCode(); + } + return result; + } + }; + final class ValuesCollection implements Collection { + @Override + public boolean add(V object) { + throw new UnsupportedOperationException(); + } + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + @Override + public void clear() { + colClear(); + } + @Override + public boolean contains(Object object) { + return colIndexOfValue(object) >= 0; + } + @Override + public boolean containsAll(Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + @Override + public boolean isEmpty() { + return colGetSize() == 0; + } + @Override + public Iterator iterator() { + return new ArrayIterator(1); + } + @Override + public boolean remove(Object object) { + int index = colIndexOfValue(object); + if (index >= 0) { + colRemoveAt(index); + return true; + } + return false; + } + @Override + public boolean removeAll(Collection collection) { + int N = colGetSize(); + boolean changed = false; + for (int i=0; i collection) { + int N = colGetSize(); + boolean changed = false; + for (int i=0; i T[] toArray(T[] array) { + return toArrayHelper(array, 1); + } + }; + public static boolean containsAllHelper(Map map, Collection collection) { + Iterator it = collection.iterator(); + while (it.hasNext()) { + if (!map.containsKey(it.next())) { + return false; + } + } + return true; + } + public static boolean removeAllHelper(Map map, Collection collection) { + int oldSize = map.size(); + Iterator it = collection.iterator(); + while (it.hasNext()) { + map.remove(it.next()); + } + return oldSize != map.size(); + } + public static boolean retainAllHelper(Map map, Collection collection) { + int oldSize = map.size(); + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + if (!collection.contains(it.next())) { + it.remove(); + } + } + return oldSize != map.size(); + } + public Object[] toArrayHelper(int offset) { + final int N = colGetSize(); + Object[] result = new Object[N]; + for (int i=0; i T[] toArrayHelper(T[] array, int offset) { + final int N = colGetSize(); + if (array.length < N) { + @SuppressWarnings("unchecked") T[] newArray + = (T[]) Array.newInstance(array.getClass().getComponentType(), N); + array = newArray; + } + for (int i=0; i N) { + array[N] = null; + } + return array; + } + public static boolean equalsSetHelper(Set set, Object object) { + if (set == object) { + return true; + } + if (object instanceof Set) { + Set s = (Set) object; + try { + return set.size() == s.size() && set.containsAll(s); + } catch (NullPointerException ignored) { + return false; + } catch (ClassCastException ignored) { + return false; + } + } + return false; + } + public Set> getEntrySet() { + if (mEntrySet == null) { + mEntrySet = new EntrySet(); + } + return mEntrySet; + } + public Set getKeySet() { + if (mKeySet == null) { + mKeySet = new KeySet(); + } + return mKeySet; + } + public Collection getValues() { + if (mValues == null) { + mValues = new ValuesCollection(); + } + return mValues; + } + protected abstract int colGetSize(); + protected abstract Object colGetEntry(int index, int offset); + protected abstract int colIndexOfKey(Object key); + protected abstract int colIndexOfValue(Object key); + protected abstract Map colGetMap(); + protected abstract void colPut(K key, V value); + protected abstract V colSetValue(int index, V value); + protected abstract void colRemoveAt(int index); + protected abstract void colClear(); +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/view/DisplayAdjustments.java b/AndroidCompat/src/main/java/android/view/DisplayAdjustments.java new file mode 100644 index 00000000..c9ae5dfe --- /dev/null +++ b/AndroidCompat/src/main/java/android/view/DisplayAdjustments.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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 android.view; + +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; + +/** @hide */ +public class DisplayAdjustments { + + public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = null; + + private volatile CompatibilityInfo mCompatInfo = null; + + private Configuration mConfiguration = null; + + public DisplayAdjustments() { + throw new RuntimeException("Stub!"); + } + + public DisplayAdjustments(Configuration configuration) { + throw new RuntimeException("Stub!"); + } + + public DisplayAdjustments(DisplayAdjustments daj) { + throw new RuntimeException("Stub!"); + } + + public void setCompatibilityInfo(CompatibilityInfo compatInfo) { + throw new RuntimeException("Stub!"); + } + + public CompatibilityInfo getCompatibilityInfo() { + throw new RuntimeException("Stub!"); + } + + public void setConfiguration(Configuration configuration) { + throw new RuntimeException("Stub!"); + } + + public Configuration getConfiguration() { + throw new RuntimeException("Stub!"); + } + + @Override + public int hashCode() { + throw new RuntimeException("Stub!"); + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("Stub!"); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/webkit/MimeTypeMap.java b/AndroidCompat/src/main/java/android/webkit/MimeTypeMap.java new file mode 100644 index 00000000..8b6ec347 --- /dev/null +++ b/AndroidCompat/src/main/java/android/webkit/MimeTypeMap.java @@ -0,0 +1,168 @@ +/* + * 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 android.webkit; + +import android.text.TextUtils; +import libcore.net.MimeUtils; + +import java.util.regex.Pattern; + +/** + * Two-way map that maps MIME-types to file extensions and vice versa. + * + *

See also {@link java.net.URLConnection#guessContentTypeFromName} + * and {@link java.net.URLConnection#guessContentTypeFromStream}. This + * class and {@code URLConnection} share the same MIME-type database. + */ +public class MimeTypeMap { + private static final MimeTypeMap sMimeTypeMap = new MimeTypeMap(); + + private MimeTypeMap() { + } + + /** + * Returns the file extension or an empty string iff there is no + * extension. This method is a convenience method for obtaining the + * extension of a url and has undefined results for other Strings. + * @param url + * @return The file extension of the given url. + */ + public static String getFileExtensionFromUrl(String url) { + if (!TextUtils.isEmpty(url)) { + int fragment = url.lastIndexOf('#'); + if (fragment > 0) { + url = url.substring(0, fragment); + } + + int query = url.lastIndexOf('?'); + if (query > 0) { + url = url.substring(0, query); + } + + int filenamePos = url.lastIndexOf('/'); + String filename = + 0 <= filenamePos ? url.substring(filenamePos + 1) : url; + + // if the filename contains special characters, we don't + // consider it valid for our matching purposes: + if (!filename.isEmpty() && + Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) { + int dotPos = filename.lastIndexOf('.'); + if (0 <= dotPos) { + return filename.substring(dotPos + 1); + } + } + } + + return ""; + } + + /** + * Return true if the given MIME type has an entry in the map. + * @param mimeType A MIME type (i.e. text/plain) + * @return True iff there is a mimeType entry in the map. + */ + public boolean hasMimeType(String mimeType) { + return MimeUtils.hasMimeType(mimeType); + } + + /** + * Return the MIME type for the given extension. + * @param extension A file extension without the leading '.' + * @return The MIME type for the given extension or null iff there is none. + */ + public String getMimeTypeFromExtension(String extension) { + return MimeUtils.guessMimeTypeFromExtension(extension); + } + + // Static method called by jni. + private static String mimeTypeFromExtension(String extension) { + return MimeUtils.guessMimeTypeFromExtension(extension); + } + + /** + * Return true if the given extension has a registered MIME type. + * @param extension A file extension without the leading '.' + * @return True iff there is an extension entry in the map. + */ + public boolean hasExtension(String extension) { + return MimeUtils.hasExtension(extension); + } + + /** + * Return the registered extension for the given MIME type. Note that some + * MIME types map to multiple extensions. This call will return the most + * common extension for the given MIME type. + * @param mimeType A MIME type (i.e. text/plain) + * @return The extension for the given MIME type or null iff there is none. + */ + public String getExtensionFromMimeType(String mimeType) { + return MimeUtils.guessExtensionFromMimeType(mimeType); + } + + /** + * If the given MIME type is null, or one of the "generic" types (text/plain + * or application/octet-stream) map it to a type that Android can deal with. + * If the given type is not generic, return it unchanged. + * + * @param mimeType MIME type provided by the server. + * @param url URL of the data being loaded. + * @param contentDisposition Content-disposition header given by the server. + * @return The MIME type that should be used for this data. + */ + /* package */ String remapGenericMimeType(String mimeType, String url, + String contentDisposition) { + // If we have one of "generic" MIME types, try to deduce + // the right MIME type from the file extension (if any): + if ("text/plain".equals(mimeType) || + "application/octet-stream".equals(mimeType)) { + + // for attachment, use the filename in the Content-Disposition + // to guess the mimetype + String filename = null; + if (contentDisposition != null) { + filename = URLUtil.parseContentDisposition(contentDisposition); + } + if (filename != null) { + url = filename; + } + String extension = getFileExtensionFromUrl(url); + String newMimeType = getMimeTypeFromExtension(extension); + if (newMimeType != null) { + mimeType = newMimeType; + } + } else if ("text/vnd.wap.wml".equals(mimeType)) { + // As we don't support wml, render it as plain text + mimeType = "text/plain"; + } else { + // It seems that xhtml+xml and vnd.wap.xhtml+xml mime + // subtypes are used interchangeably. So treat them the same. + if ("application/vnd.wap.xhtml+xml".equals(mimeType)) { + mimeType = "application/xhtml+xml"; + } + } + return mimeType; + } + + /** + * Get the singleton instance of MimeTypeMap. + * @return The singleton instance of the MIME-type map. + */ + public static MimeTypeMap getSingleton() { + return sMimeTypeMap; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/android/webkit/URLUtil.java b/AndroidCompat/src/main/java/android/webkit/URLUtil.java new file mode 100644 index 00000000..9fc5b9fd --- /dev/null +++ b/AndroidCompat/src/main/java/android/webkit/URLUtil.java @@ -0,0 +1,364 @@ +/* + * 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 android.webkit; +import android.net.ParseException; +import android.net.Uri; +import android.net.WebAddress; +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +public final class URLUtil { + private static final String LOGTAG = "webkit"; + private static final boolean TRACE = false; + // to refer to bar.png under your package's asset/foo/ directory, use + // "file:///android_asset/foo/bar.png". + static final String ASSET_BASE = "file:///android_asset/"; + // to refer to bar.png under your package's res/drawable/ directory, use + // "file:///android_res/drawable/bar.png". Use "drawable" to refer to + // "drawable-hdpi" directory as well. + static final String RESOURCE_BASE = "file:///android_res/"; + static final String FILE_BASE = "file://"; + static final String PROXY_BASE = "file:///cookieless_proxy/"; + static final String CONTENT_BASE = "content:"; + /** + * Cleans up (if possible) user-entered web addresses + */ + public static String guessUrl(String inUrl) { + String retVal = inUrl; + WebAddress webAddress; + if (TRACE) Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl); + if (inUrl.length() == 0) return inUrl; + if (inUrl.startsWith("about:")) return inUrl; + // Do not try to interpret data scheme URLs + if (inUrl.startsWith("data:")) return inUrl; + // Do not try to interpret file scheme URLs + if (inUrl.startsWith("file:")) return inUrl; + // Do not try to interpret javascript scheme URLs + if (inUrl.startsWith("javascript:")) return inUrl; + // bug 762454: strip period off end of url + if (inUrl.endsWith(".") == true) { + inUrl = inUrl.substring(0, inUrl.length() - 1); + } + try { + webAddress = new WebAddress(inUrl); + } catch (ParseException ex) { + if (TRACE) { + Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl); + } + return retVal; + } + // Check host + if (webAddress.getHost().indexOf('.') == -1) { + // no dot: user probably entered a bare domain. try .com + webAddress.setHost("www." + webAddress.getHost() + ".com"); + } + return webAddress.toString(); + } + public static String composeSearchUrl(String inQuery, String template, + String queryPlaceHolder) { + int placeHolderIndex = template.indexOf(queryPlaceHolder); + if (placeHolderIndex < 0) { + return null; + } + String query; + StringBuilder buffer = new StringBuilder(); + buffer.append(template.substring(0, placeHolderIndex)); + try { + query = java.net.URLEncoder.encode(inQuery, "utf-8"); + buffer.append(query); + } catch (UnsupportedEncodingException ex) { + return null; + } + buffer.append(template.substring( + placeHolderIndex + queryPlaceHolder.length())); + return buffer.toString(); + } + public static byte[] decode(byte[] url) throws IllegalArgumentException { + if (url.length == 0) { + return new byte[0]; + } + // Create a new byte array with the same length to ensure capacity + byte[] tempData = new byte[url.length]; + int tempCount = 0; + for (int i = 0; i < url.length; i++) { + byte b = url[i]; + if (b == '%') { + if (url.length - i > 2) { + b = (byte) (parseHex(url[i + 1]) * 16 + + parseHex(url[i + 2])); + i += 2; + } else { + throw new IllegalArgumentException("Invalid format"); + } + } + tempData[tempCount++] = b; + } + byte[] retData = new byte[tempCount]; + System.arraycopy(tempData, 0, retData, 0, tempCount); + return retData; + } + /** + * @return True iff the url is correctly URL encoded + */ + static boolean verifyURLEncoding(String url) { + int count = url.length(); + if (count == 0) { + return false; + } + int index = url.indexOf('%'); + while (index >= 0 && index < count) { + if (index < count - 2) { + try { + parseHex((byte) url.charAt(++index)); + parseHex((byte) url.charAt(++index)); + } catch (IllegalArgumentException e) { + return false; + } + } else { + return false; + } + index = url.indexOf('%', index + 1); + } + return true; + } + private static int parseHex(byte b) { + if (b >= '0' && b <= '9') return (b - '0'); + if (b >= 'A' && b <= 'F') return (b - 'A' + 10); + if (b >= 'a' && b <= 'f') return (b - 'a' + 10); + throw new IllegalArgumentException("Invalid hex char '" + b + "'"); + } + /** + * @return True iff the url is an asset file. + */ + public static boolean isAssetUrl(String url) { + return (null != url) && url.startsWith(ASSET_BASE); + } + /** + * @return True iff the url is a resource file. + * @hide + */ + public static boolean isResourceUrl(String url) { + return (null != url) && url.startsWith(RESOURCE_BASE); + } + /** + * @return True iff the url is a proxy url to allow cookieless network + * requests from a file url. + * @deprecated Cookieless proxy is no longer supported. + */ + @Deprecated + public static boolean isCookielessProxyUrl(String url) { + return (null != url) && url.startsWith(PROXY_BASE); + } + /** + * @return True iff the url is a local file. + */ + public static boolean isFileUrl(String url) { + return (null != url) && (url.startsWith(FILE_BASE) && + !url.startsWith(ASSET_BASE) && + !url.startsWith(PROXY_BASE)); + } + /** + * @return True iff the url is an about: url. + */ + public static boolean isAboutUrl(String url) { + return (null != url) && url.startsWith("about:"); + } + /** + * @return True iff the url is a data: url. + */ + public static boolean isDataUrl(String url) { + return (null != url) && url.startsWith("data:"); + } + /** + * @return True iff the url is a javascript: url. + */ + public static boolean isJavaScriptUrl(String url) { + return (null != url) && url.startsWith("javascript:"); + } + /** + * @return True iff the url is an http: url. + */ + public static boolean isHttpUrl(String url) { + return (null != url) && + (url.length() > 6) && + url.substring(0, 7).equalsIgnoreCase("http://"); + } + /** + * @return True iff the url is an https: url. + */ + public static boolean isHttpsUrl(String url) { + return (null != url) && + (url.length() > 7) && + url.substring(0, 8).equalsIgnoreCase("https://"); + } + /** + * @return True iff the url is a network url. + */ + public static boolean isNetworkUrl(String url) { + if (url == null || url.length() == 0) { + return false; + } + return isHttpUrl(url) || isHttpsUrl(url); + } + /** + * @return True iff the url is a content: url. + */ + public static boolean isContentUrl(String url) { + return (null != url) && url.startsWith(CONTENT_BASE); + } + /** + * @return True iff the url is valid. + */ + public static boolean isValidUrl(String url) { + if (url == null || url.length() == 0) { + return false; + } + return (isAssetUrl(url) || + isResourceUrl(url) || + isFileUrl(url) || + isAboutUrl(url) || + isHttpUrl(url) || + isHttpsUrl(url) || + isJavaScriptUrl(url) || + isContentUrl(url)); + } + /** + * Strips the url of the anchor. + */ + public static String stripAnchor(String url) { + int anchorIndex = url.indexOf('#'); + if (anchorIndex != -1) { + return url.substring(0, anchorIndex); + } + return url; + } + /** + * Guesses canonical filename that a download would have, using + * the URL and contentDisposition. File extension, if not defined, + * is added based on the mimetype + * @param url Url to the content + * @param contentDisposition Content-Disposition HTTP header or null + * @param mimeType Mime-type of the content or null + * + * @return suggested filename + */ + public static final String guessFileName( + String url, + String contentDisposition, + String mimeType) { + String filename = null; + String extension = null; + // If we couldn't do anything with the hint, move toward the content disposition + if (filename == null && contentDisposition != null) { + filename = parseContentDisposition(contentDisposition); + if (filename != null) { + int index = filename.lastIndexOf('/') + 1; + if (index > 0) { + filename = filename.substring(index); + } + } + } + // If all the other http-related approaches failed, use the plain uri + if (filename == null) { + String decodedUrl = Uri.decode(url); + if (decodedUrl != null) { + int queryIndex = decodedUrl.indexOf('?'); + // If there is a query string strip it, same as desktop browsers + if (queryIndex > 0) { + decodedUrl = decodedUrl.substring(0, queryIndex); + } + if (!decodedUrl.endsWith("/")) { + int index = decodedUrl.lastIndexOf('/') + 1; + if (index > 0) { + filename = decodedUrl.substring(index); + } + } + } + } + // Finally, if couldn't get filename from URI, get a generic filename + if (filename == null) { + filename = "downloadfile"; + } + // Split filename between base and extension + // Add an extension if filename does not have one + int dotIndex = filename.indexOf('.'); + if (dotIndex < 0) { + if (mimeType != null) { + extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + if (extension != null) { + extension = "." + extension; + } + } + if (extension == null) { + if (mimeType != null && mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) { + if (mimeType.equalsIgnoreCase("text/html")) { + extension = ".html"; + } else { + extension = ".txt"; + } + } else { + extension = ".bin"; + } + } + } else { + if (mimeType != null) { + // Compare the last segment of the extension against the mime type. + // If there's a mismatch, discard the entire extension. + int lastDotIndex = filename.lastIndexOf('.'); + String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + filename.substring(lastDotIndex + 1)); + if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) { + extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + if (extension != null) { + extension = "." + extension; + } + } + } + if (extension == null) { + extension = filename.substring(dotIndex); + } + filename = filename.substring(0, dotIndex); + } + return filename + extension; + } + /** Regex used to parse content-disposition headers */ + private static final Pattern CONTENT_DISPOSITION_PATTERN = + Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$", + Pattern.CASE_INSENSITIVE); + /* + * Parse the Content-Disposition HTTP Header. The format of the header + * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html + * This header provides a filename for content that is going to be + * downloaded to the file system. We only support the attachment type. + * Note that RFC 2616 specifies the filename value must be double-quoted. + * Unfortunately some servers do not quote the value so to maintain + * consistent behaviour with other browsers, we allow unquoted values too. + */ + static String parseContentDisposition(String contentDisposition) { + try { + Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); + if (m.find()) { + return m.group(2); + } + } catch (IllegalStateException ex) { + // This function is defined as returning null when it can't parse the header + } + return null; + } +} diff --git a/AndroidCompat/src/main/java/com/android/internal/util/ArrayUtils.java b/AndroidCompat/src/main/java/com/android/internal/util/ArrayUtils.java new file mode 100644 index 00000000..3e4e8862 --- /dev/null +++ b/AndroidCompat/src/main/java/com/android/internal/util/ArrayUtils.java @@ -0,0 +1,471 @@ +/* + * 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 com.android.internal.util; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArraySet; +import libcore.util.EmptyArray; + +import java.lang.reflect.Array; +import java.util.*; +/** + * ArrayUtils contains some methods that you can call to find out + * the most efficient increments by which to grow arrays. + */ +public class ArrayUtils { + private static final int CACHE_SIZE = 73; + private static Object[] sCache = new Object[CACHE_SIZE]; + private ArrayUtils() { /* cannot be instantiated */ } + public static byte[] newUnpaddedByteArray(int minLen) { + return new byte[minLen]; + } + public static char[] newUnpaddedCharArray(int minLen) { + return new char[minLen]; + } + public static int[] newUnpaddedIntArray(int minLen) { + return new int[minLen]; + } + public static boolean[] newUnpaddedBooleanArray(int minLen) { + return new boolean[minLen]; + } + public static long[] newUnpaddedLongArray(int minLen) { + return new long[minLen]; + } + public static float[] newUnpaddedFloatArray(int minLen) { + return new float[minLen]; + } + public static Object[] newUnpaddedObjectArray(int minLen) { + return new Object[minLen]; + } + /** + * Checks if the beginnings of two byte arrays are equal. + * + * @param array1 the first byte array + * @param array2 the second byte array + * @param length the number of bytes to check + * @return true if they're equal, false otherwise + */ + public static boolean equals(byte[] array1, byte[] array2, int length) { + if (length < 0) { + throw new IllegalArgumentException(); + } + if (array1 == array2) { + return true; + } + if (array1 == null || array2 == null || array1.length < length || array2.length < length) { + return false; + } + for (int i = 0; i < length; i++) { + if (array1[i] != array2[i]) { + return false; + } + } + return true; + } + /** + * Returns an empty array of the specified type. The intent is that + * it will return the same empty array every time to avoid reallocation, + * although this is not guaranteed. + */ + @SuppressWarnings("unchecked") + public static T[] emptyArray(Class kind) { + if (kind == Object.class) { + return (T[]) EmptyArray.OBJECT; + } + int bucket = (kind.hashCode() & 0x7FFFFFFF) % CACHE_SIZE; + Object cache = sCache[bucket]; + if (cache == null || cache.getClass().getComponentType() != kind) { + cache = Array.newInstance(kind, 0); + sCache[bucket] = cache; + // Log.e("cache", "new empty " + kind.getName() + " at " + bucket); + } + return (T[]) cache; + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Collection array) { + return array == null || array.isEmpty(); + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable long[] array) { + return array == null || array.length == 0; + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable boolean[] array) { + return array == null || array.length == 0; + } + /** + * Checks that value is present as at least one of the elements of the array. + * @param array the array to check in + * @param value the value to check for + * @return true if the value is present in the array + */ + public static boolean contains(@Nullable T[] array, T value) { + return indexOf(array, value) != -1; + } + /** + * Return first index of {@code value} in {@code array}, or {@code -1} if + * not found. + */ + public static int indexOf(@Nullable T[] array, T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + /** + * Test if all {@code check} items are contained in {@code array}. + */ + public static boolean containsAll(@Nullable T[] array, T[] check) { + if (check == null) return true; + for (T checkItem : check) { + if (!contains(array, checkItem)) { + return false; + } + } + return true; + } + /** + * Test if any {@code check} items are contained in {@code array}. + */ + public static boolean containsAny(@Nullable T[] array, T[] check) { + if (check == null) return false; + for (T checkItem : check) { + if (contains(array, checkItem)) { + return true; + } + } + return false; + } + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static boolean contains(@Nullable long[] array, long value) { + if (array == null) return false; + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(@Nullable long[] array) { + long total = 0; + if (array != null) { + for (long value : array) { + total += value; + } + } + return total; + } + public static int[] convertToIntArray(List list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + @SuppressWarnings("unchecked") + public static @NonNull T[] appendElement(Class kind, @Nullable T[] array, T element) { + final T[] result; + final int end; + if (array != null) { + if (contains(array, element)) return array; + end = array.length; + result = (T[])Array.newInstance(kind, end + 1); + System.arraycopy(array, 0, result, 0, end); + } else { + end = 0; + result = (T[])Array.newInstance(kind, 1); + } + result[end] = element; + return result; + } + /** + * Removes value from given array if present, providing set-like behavior. + */ + @SuppressWarnings("unchecked") + public static @Nullable T[] removeElement(Class kind, @Nullable T[] array, T element) { + if (array != null) { + if (!contains(array, element)) return array; + final int length = array.length; + for (int i = 0; i < length; i++) { + if (Objects.equals(array[i], element)) { + if (length == 1) { + return null; + } + T[] result = (T[])Array.newInstance(kind, length - 1); + System.arraycopy(array, 0, result, 0, i); + System.arraycopy(array, i + 1, result, i, length - i - 1); + return result; + } + } + } + return array; + } + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { + if (cur == null) { + return new int[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + int[] ret = new int[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable int[] removeInt(@Nullable int[] cur, int val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + int[] ret = new int[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable String[] removeString(@Nullable String[] cur, String val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (Objects.equals(cur[i], val)) { + String[] ret = new String[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static @NonNull long[] appendLong(@Nullable long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable long[] removeLong(@Nullable long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + public static @Nullable long[] cloneOrNull(@Nullable long[] array) { + return (array != null) ? array.clone() : null; + } + public static @Nullable ArraySet cloneOrNull(@Nullable ArraySet array) { + return (array != null) ? new ArraySet(array) : null; + } + public static @NonNull ArraySet add(@Nullable ArraySet cur, T val) { + if (cur == null) { + cur = new ArraySet<>(); + } + cur.add(val); + return cur; + } + public static @Nullable ArraySet remove(@Nullable ArraySet cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + public static boolean contains(@Nullable ArraySet cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + public static @NonNull ArrayList add(@Nullable ArrayList cur, T val) { + if (cur == null) { + cur = new ArrayList<>(); + } + cur.add(val); + return cur; + } + public static @Nullable ArrayList remove(@Nullable ArrayList cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + public static boolean contains(@Nullable Collection cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + public static @Nullable T[] trimToSize(@Nullable T[] array, int size) { + if (array == null || size == 0) { + return null; + } else if (array.length == size) { + return array; + } else { + return Arrays.copyOf(array, size); + } + } + /** + * Returns true if the two ArrayLists are equal with respect to the objects they contain. + * The objects must be in the same order and be reference equal (== not .equals()). + */ + public static boolean referenceEquals(ArrayList a, ArrayList b) { + if (a == b) { + return true; + } + final int sizeA = a.size(); + final int sizeB = b.size(); + if (a == null || b == null || sizeA != sizeB) { + return false; + } + boolean diff = false; + for (int i = 0; i < sizeA && !diff; i++) { + diff |= a.get(i) != b.get(i); + } + return !diff; + } + /** + * Removes elements that match the predicate in an efficient way that alters the order of + * elements in the collection. This should only be used if order is not important. + * @param collection The ArrayList from which to remove elements. + * @param predicate The predicate that each element is tested against. + * @return the number of elements removed. + */ + public static int unstableRemoveIf(@Nullable ArrayList collection, + @NonNull java.util.function.Predicate predicate) { + if (collection == null) { + return 0; + } + final int size = collection.size(); + int leftIdx = 0; + int rightIdx = size - 1; + while (leftIdx <= rightIdx) { + // Find the next element to remove moving left to right. + while (leftIdx < size && !predicate.test(collection.get(leftIdx))) { + leftIdx++; + } + // Find the next element to keep moving right to left. + while (rightIdx > leftIdx && predicate.test(collection.get(rightIdx))) { + rightIdx--; + } + if (leftIdx >= rightIdx) { + // Done. + break; + } + Collections.swap(collection, leftIdx, rightIdx); + leftIdx++; + rightIdx--; + } + // leftIdx is now at the end. + for (int i = size - 1; i >= leftIdx; i--) { + collection.remove(i); + } + return size - leftIdx; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/android/internal/util/FastXmlSerializer.java b/AndroidCompat/src/main/java/com/android/internal/util/FastXmlSerializer.java new file mode 100644 index 00000000..f774da95 --- /dev/null +++ b/AndroidCompat/src/main/java/com/android/internal/util/FastXmlSerializer.java @@ -0,0 +1,370 @@ +/* + * 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 com.android.internal.util; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +/** + * This is a quick and dirty implementation of XmlSerializer that isn't horribly + * painfully slow like the normal one. It only does what is needed for the + * specific XML files being written with it. + */ +public class FastXmlSerializer implements XmlSerializer { + private static final String ESCAPE_TABLE[] = new String[] { + "�", "", "", "", "", "", "", "", // 0-7 + "", " ", " ", " ", " ", " ", "", "", // 8-15 + "", "", "", "", "", "", "", "", // 16-23 + "", "", "", "", "", "", "", "", // 24-31 + null, null, """, null, null, null, "&", null, // 32-39 + null, null, null, null, null, null, null, null, // 40-47 + null, null, null, null, null, null, null, null, // 48-55 + null, null, null, null, "<", null, ">", null, // 56-63 + }; + private static final int DEFAULT_BUFFER_LEN = 32*1024; + private static String sSpace = " "; + private final int mBufferLen; + private final char[] mText; + private int mPos; + private Writer mWriter; + private OutputStream mOutputStream; + private CharsetEncoder mCharset; + private ByteBuffer mBytes; + private boolean mIndent = false; + private boolean mInTag; + private int mNesting = 0; + private boolean mLineStart = true; + public FastXmlSerializer() { + this(DEFAULT_BUFFER_LEN); + } + /** + * Allocate a FastXmlSerializer with the given internal output buffer size. If the + * size is zero or negative, then the default buffer size will be used. + * + * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use. + */ + public FastXmlSerializer(int bufferSize) { + mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN; + mText = new char[mBufferLen]; + mBytes = ByteBuffer.allocate(mBufferLen); + } + private void append(char c) throws IOException { + int pos = mPos; + if (pos >= (mBufferLen-1)) { + flush(); + pos = mPos; + } + mText[pos] = c; + mPos = pos+1; + } + private void append(String str, int i, final int length) throws IOException { + if (length > mBufferLen) { + final int end = i + length; + while (i < end) { + int next = i + mBufferLen; + append(str, i, next mBufferLen) { + flush(); + pos = mPos; + } + str.getChars(i, i+length, mText, pos); + mPos = pos + length; + } + private void append(char[] buf, int i, final int length) throws IOException { + if (length > mBufferLen) { + final int end = i + length; + while (i < end) { + int next = i + mBufferLen; + append(buf, i, next mBufferLen) { + flush(); + pos = mPos; + } + System.arraycopy(buf, i, mText, pos, length); + mPos = pos + length; + } + private void append(String str) throws IOException { + append(str, 0, str.length()); + } + private void appendIndent(int indent) throws IOException { + indent *= 4; + if (indent > sSpace.length()) { + indent = sSpace.length(); + } + append(sSpace, 0, indent); + } + private void escapeAndAppendString(final String string) throws IOException { + final int N = string.length(); + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int lastPos = 0; + int pos; + for (pos=0; pos= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(string, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(string, lastPos, pos-lastPos); + } + private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int end = start+len; + int lastPos = start; + int pos; + for (pos=start; pos= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + } + public XmlSerializer attribute(String namespace, String name, String value) throws IOException, + IllegalArgumentException, IllegalStateException { + append(' '); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append("=\""); + escapeAndAppendString(value); + append('"'); + mLineStart = false; + return this; + } + public void cdsect(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void comment(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void docdecl(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { + flush(); + } + public XmlSerializer endTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + mNesting--; + if (mInTag) { + append(" />\n"); + } else { + if (mIndent && mLineStart) { + appendIndent(mNesting); + } + append("\n"); + } + mLineStart = true; + mInTag = false; + return this; + } + public void entityRef(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + private void flushBytes() throws IOException { + int position; + if ((position = mBytes.position()) > 0) { + mBytes.flip(); + mOutputStream.write(mBytes.array(), 0, position); + mBytes.clear(); + } + } + public void flush() throws IOException { + //Log.i("PackageManager", "flush mPos=" + mPos); + if (mPos > 0) { + if (mOutputStream != null) { + CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); + CoderResult result = mCharset.encode(charBuffer, mBytes, true); + while (true) { + if (result.isError()) { + throw new IOException(result.toString()); + } else if (result.isOverflow()) { + flushBytes(); + result = mCharset.encode(charBuffer, mBytes, true); + continue; + } + break; + } + flushBytes(); + mOutputStream.flush(); + } else { + mWriter.write(mText, 0, mPos); + mWriter.flush(); + } + mPos = 0; + } + } + public int getDepth() { + throw new UnsupportedOperationException(); + } + public boolean getFeature(String name) { + throw new UnsupportedOperationException(); + } + public String getName() { + throw new UnsupportedOperationException(); + } + public String getNamespace() { + throw new UnsupportedOperationException(); + } + public String getPrefix(String namespace, boolean generatePrefix) + throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + public Object getProperty(String name) { + throw new UnsupportedOperationException(); + } + public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void processingInstruction(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void setFeature(String name, boolean state) throws IllegalArgumentException, + IllegalStateException { + if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { + mIndent = true; + return; + } + throw new UnsupportedOperationException(); + } + public void setOutput(OutputStream os, String encoding) throws IOException, + IllegalArgumentException, IllegalStateException { + if (os == null) + throw new IllegalArgumentException(); + if (true) { + try { + mCharset = Charset.forName(encoding).newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + } catch (IllegalCharsetNameException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } catch (UnsupportedCharsetException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } + mOutputStream = os; + } else { + setOutput( + encoding == null + ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, encoding)); + } + } + public void setOutput(Writer writer) throws IOException, IllegalArgumentException, + IllegalStateException { + mWriter = writer; + } + public void setPrefix(String prefix, String namespace) throws IOException, + IllegalArgumentException, IllegalStateException { + throw new UnsupportedOperationException(); + } + public void setProperty(String name, Object value) throws IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + public void startDocument(String encoding, Boolean standalone) throws IOException, + IllegalArgumentException, IllegalStateException { + append("\n"); + mLineStart = true; + } + public XmlSerializer startTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">\n"); + } + if (mIndent) { + appendIndent(mNesting); + } + mNesting++; + append('<'); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + mInTag = true; + mLineStart = false; + return this; + } + public XmlSerializer text(char[] buf, int start, int len) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(buf, start, len); + if (mIndent) { + mLineStart = buf[start+len-1] == '\n'; + } + return this; + } + public XmlSerializer text(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(text); + if (mIndent) { + mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n'); + } + return this; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/android/internal/util/Preconditions.java b/AndroidCompat/src/main/java/com/android/internal/util/Preconditions.java new file mode 100644 index 00000000..c4b8f3ce --- /dev/null +++ b/AndroidCompat/src/main/java/com/android/internal/util/Preconditions.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2011 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 com.android.internal.util; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.text.TextUtils; +import java.util.Collection; +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. + */ +public class Preconditions { + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + /** + * Ensures that an expression checking an argument is true. + * + * @param expression the expression to check + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, final Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + /** + * Ensures that an expression checking an argument is true. + * + * @param expression the expression to check + * @param messageTemplate a printf-style message template to use if the check fails; will + * be converted to a string using {@link String#format(String, Object...)} + * @param messageArgs arguments for {@code messageTemplate} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, + final String messageTemplate, + final Object... messageArgs) { + if (!expression) { + throw new IllegalArgumentException(String.format(messageTemplate, messageArgs)); + } + } + /** + * Ensures that an string reference passed as a parameter to the calling + * method is not empty. + * + * @param string an string reference + * @return the string reference that was validated + * @throws IllegalArgumentException if {@code string} is empty + */ + public static @NonNull T checkStringNotEmpty(final T string) { + if (TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(); + } + return string; + } + /** + * Ensures that an string reference passed as a parameter to the calling + * method is not empty. + * + * @param string an string reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the string reference that was validated + * @throws IllegalArgumentException if {@code string} is empty + */ + public static @NonNull T checkStringNotEmpty(final T string, + final Object errorMessage) { + if (TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + return string; + } + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static @NonNull T checkNotNull(final T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static @NonNull T checkNotNull(final T reference, final Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param messageTemplate a printf-style message template to use if the check fails; will + * be converted to a string using {@link String#format(String, Object...)} + * @param messageArgs arguments for {@code messageTemplate} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static @NonNull T checkNotNull(final T reference, + final String messageTemplate, + final Object... messageArgs) { + if (reference == null) { + throw new NullPointerException(String.format(messageTemplate, messageArgs)); + } + return reference; + } + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param message exception message + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(final boolean expression, String message) { + if (!expression) { + throw new IllegalStateException(message); + } + } + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(final boolean expression) { + checkState(expression, null); + } + /** + * Check the requested flags, throwing if any requested flags are outside + * the allowed set. + * + * @return the validated requested flags. + */ + public static int checkFlagsArgument(final int requestedFlags, final int allowedFlags) { + if ((requestedFlags & allowedFlags) != requestedFlags) { + throw new IllegalArgumentException("Requested flags 0x" + + Integer.toHexString(requestedFlags) + ", but only 0x" + + Integer.toHexString(allowedFlags) + " are allowed"); + } + return requestedFlags; + } + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static @IntRange(from = 0) int checkArgumentNonnegative(final int value, + final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + return value; + } + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric int value + * + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static @IntRange(from = 0) int checkArgumentNonnegative(final int value) { + if (value < 0) { + throw new IllegalArgumentException(); + } + return value; + } + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric long value + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static long checkArgumentNonnegative(final long value) { + if (value < 0) { + throw new IllegalArgumentException(); + } + return value; + } + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric long value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static long checkArgumentNonnegative(final long value, final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + return value; + } + /** + * Ensures that that the argument numeric value is positive. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was not positive + */ + public static int checkArgumentPositive(final int value, final String errorMessage) { + if (value <= 0) { + throw new IllegalArgumentException(errorMessage); + } + return value; + } + /** + * Ensures that the argument floating point value is a finite number. + * + *

A finite number is defined to be both representable (that is, not NaN) and + * not infinite (that is neither positive or negative infinity).

+ * + * @param value a floating point value + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not finite + */ + public static float checkArgumentFinite(final float value, final String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (Float.isInfinite(value)) { + throw new IllegalArgumentException(valueName + " must not be infinite"); + } + return value; + } + /** + * Ensures that the argument floating point value is within the inclusive range. + * + *

While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.

+ * + * @param value a floating point value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static float checkArgumentInRange(float value, float lower, float upper, + String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too high)", valueName, lower, upper)); + } + return value; + } + /** + * Ensures that the argument int value is within the inclusive range. + * + * @param value a int value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated int value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static int checkArgumentInRange(int value, int lower, int upper, + String valueName) { + if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too high)", valueName, lower, upper)); + } + return value; + } + /** + * Ensures that the argument long value is within the inclusive range. + * + * @param value a long value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated long value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static long checkArgumentInRange(long value, long lower, long upper, + String valueName) { + if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too high)", valueName, lower, upper)); + } + return value; + } + /** + * Ensures that the array is not {@code null}, and none of its elements are {@code null}. + * + * @param value an array of boxed objects + * @param valueName the name of the argument to use if the check fails + * + * @return the validated array + * + * @throws NullPointerException if the {@code value} or any of its elements were {@code null} + */ + public static T[] checkArrayElementsNotNull(final T[] value, final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + for (int i = 0; i < value.length; ++i) { + if (value[i] == null) { + throw new NullPointerException( + String.format("%s[%d] must not be null", valueName, i)); + } + } + return value; + } + /** + * Ensures that the {@link Collection} is not {@code null}, and none of its elements are + * {@code null}. + * + * @param value a {@link Collection} of boxed objects + * @param valueName the name of the argument to use if the check fails + * + * @return the validated {@link Collection} + * + * @throws NullPointerException if the {@code value} or any of its elements were {@code null} + */ + public static @NonNull , T> C checkCollectionElementsNotNull( + final C value, final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + long ctr = 0; + for (T elem : value) { + if (elem == null) { + throw new NullPointerException( + String.format("%s[%d] must not be null", valueName, ctr)); + } + ++ctr; + } + return value; + } + /** + * Ensures that the {@link Collection} is not {@code null}, and contains at least one element. + * + * @param value a {@link Collection} of boxed elements. + * @param valueName the name of the argument to use if the check fails. + * @return the validated {@link Collection} + * + * @throws NullPointerException if the {@code value} was {@code null} + * @throws IllegalArgumentException if the {@code value} was empty + */ + public static Collection checkCollectionNotEmpty(final Collection value, + final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + if (value.isEmpty()) { + throw new IllegalArgumentException(valueName + " is empty"); + } + return value; + } + /** + * Ensures that all elements in the argument floating point array are within the inclusive range + * + *

While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.

+ * + * @param value a floating point array of values + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if any of the elements in {@code value} were out of range + * @throws NullPointerException if the {@code value} was {@code null} + */ + public static float[] checkArrayElementsInRange(float[] value, float lower, float upper, + String valueName) { + checkNotNull(value, valueName + " must not be null"); + for (int i = 0; i < value.length; ++i) { + float v = value[i]; + if (Float.isNaN(v)) { + throw new IllegalArgumentException(valueName + "[" + i + "] must not be NaN"); + } else if (v < lower) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too low)", + valueName, i, lower, upper)); + } else if (v > upper) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too high)", + valueName, i, lower, upper)); + } + } + return value; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/android/internal/util/XmlUtils.java b/AndroidCompat/src/main/java/com/android/internal/util/XmlUtils.java new file mode 100644 index 00000000..54a44eaf --- /dev/null +++ b/AndroidCompat/src/main/java/com/android/internal/util/XmlUtils.java @@ -0,0 +1,1609 @@ +/* + * 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 com.android.internal.util; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.CompressFormat; +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Base64; +import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ProtocolException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +/** {@hide} */ +public class XmlUtils { + private static final String STRING_ARRAY_SEPARATOR = ":"; + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + public static final int + convertValueToList(CharSequence value, String[] options, int defaultValue) + { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + return defaultValue; + } + public static final boolean + convertValueToBoolean(CharSequence value, boolean defaultValue) + { + boolean result = false; + if (null == value) + return defaultValue; + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + return result; + } + public static final int + convertValueToInt(CharSequence charSeq, int defaultValue) + { + if (null == charSeq) + return defaultValue; + String nm = charSeq.toString(); + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + char c = nm.charAt(index + 1); + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) + { + index++; + base = 16; + } + return Integer.parseInt(nm.substring(index), base) * sign; + } + public static int convertValueToUnsignedInt(String value, int defaultValue) { + if (null == value) { + return defaultValue; + } + return parseUnsignedIntAttribute(value); + } + public static int parseUnsignedIntAttribute(CharSequence charSeq) { + String value = charSeq.toString(); + long bits; + int index = 0; + int len = value.length(); + int base = 10; + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + char c = value.charAt(index + 1); + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + return (int) Long.parseLong(value.substring(index), base); + } + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, OutputStream out) + throws XmlPullParserException, java.io.IOException { + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeMapXml(val, null, serializer); + serializer.endDocument(); + } + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, OutputStream out) + throws XmlPullParserException, java.io.IOException + { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + writeMapXml(val, name, out, null); + } + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @param callback Method to call when an Object type is not recognized. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + writeMapXml(val, out, callback); + out.endTag(null, "map"); + } + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). This method presumes that the start tag and + * name attribute have already been written and does not write an end tag. + * + * @param val The map to be flattened. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { + return; + } + Set s = val.entrySet(); + Iterator i = s.iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + writeValueXml(e.getValue(), (String)e.getKey(), out, callback); + } + } + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + int N = val.size(); + int i=0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + out.endTag(null, "list"); + } + + public static final void writeSetXml(Set val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "set"); + if (name != null) { + out.attribute(null, "name", name); + } + + for (Object v : val) { + writeValueXml(v, null, out); + } + + out.endTag(null, "set"); + } + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + */ + public static final void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + StringBuilder sb = new StringBuilder(val.length*2); + for (int i=0; i> 4) & 0x0f; + sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h))); + h = b & 0x0f; + sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h))); + } + out.text(sb.toString()); + out.endTag(null, "byte-array"); + } + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + for (int i=0; i readMapXml(InputStream in) + throws XmlPullParserException, java.io.IOException + { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + return (HashMap) readValueXml(parser, new String[1]); + } + /** + * Read an ArrayList from an InputStream containing XML. The stream can + * previously have been written by writeListXml(). + * + * @param in The InputStream from which to read. + * + * @return ArrayList The resulting list. + * + * @see #readMapXml + * @see #readValueXml + * @see #readThisListXml + * @see #writeListXml + */ + public static final ArrayList readListXml(InputStream in) + throws XmlPullParserException, java.io.IOException + { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + return (ArrayList)readValueXml(parser, new String[1]); + } + + + /** + * Read a HashSet from an InputStream containing XML. The stream can + * previously have been written by writeSetXml(). + * + * @param in The InputStream from which to read. + * + * @return HashSet The resulting set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readValueXml + * @see #readThisSetXml + * @see #writeSetXml + */ + public static final HashSet readSetXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashSet) readValueXml(parser, new String[1]); + } + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned after the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + */ + public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisMapXml(parser, endTag, name, null); + } + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned after the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + * @hide + */ + public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException + { + HashMap map = new HashMap(); + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name, callback, false); + map.put(name[0], val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Like {@link #readThisMapXml}, but returns an ArrayMap instead of HashMap. + * @hide + */ + public static final ArrayMap readThisArrayMapXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException + { + ArrayMap map = new ArrayMap<>(); + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name, callback, true); + map.put(name[0], val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisListXml(parser, endTag, name, null, false); + } + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + private static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback, boolean arrayMap) + throws XmlPullParserException, java.io.IOException { + ArrayList list = new ArrayList(); + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name, callback, arrayMap); + list.add(val); + //System.out.println("Adding to list: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + return readThisSetXml(parser, endTag, name, null, false); + } + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + * @hide + */ + private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name, + ReadMapCallback callback, boolean arrayMap) + throws XmlPullParserException, java.io.IOException { + HashSet set = new HashSet(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name, callback, arrayMap); + set.add(val); + //System.out.println("Adding to set: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return set; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read a byte[] object from an XmlPullParser. The XML data could + * previously have been generated by writeByteArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated byte[]. + * + * @see #writeByteArrayXml + */ + public static final byte[] readThisByteArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + byte[] array = new byte[num]; + int eventType = parser.getEventType(); + do { + if (eventType == parser.TEXT) { + if (num > 0) { + String values = parser.getText(); + if (values == null || values.length() != num * 2) { + throw new XmlPullParserException( + "Invalid value found in byte-array: " + values); + } + // This is ugly, but keeping it to mirror the logic in #writeByteArrayXml. + for (int i = 0; i < num; i ++) { + char nibbleHighChar = values.charAt(2 * i); + char nibbleLowChar = values.charAt(2 * i + 1); + int nibbleHigh = nibbleHighChar > 'a' ? (nibbleHighChar - 'a' + 10) + : (nibbleHighChar - '0'); + int nibbleLow = nibbleLowChar > 'a' ? (nibbleLowChar - 'a' + 10) + : (nibbleLowChar - '0'); + array[i] = (byte) ((nibbleHigh & 0x0F) << 4 | (nibbleLow & 0x0F)); + } + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated int[]. + * + * @see #readListXml + */ + public static final int[] readThisIntArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in int-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in int-array"); + } + parser.next(); + int[] array = new int[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read a long[] object from an XmlPullParser. The XML data could + * previously have been generated by writeLongArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated long[]. + * + * @see #readListXml + */ + public static final long[] readThisLongArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in long-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in long-array"); + } + parser.next(); + long[] array = new long[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Long.parseLong(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + /** + * Read a double[] object from an XmlPullParser. The XML data could + * previously have been generated by writeDoubleArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "double-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated double[]. + * + * @see #readListXml + */ + public static final double[] readThisDoubleArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in double-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in double-array"); + } + parser.next(); + double[] array = new double[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Double.parseDouble(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + /** + * Read a String[] object from an XmlPullParser. The XML data could + * previously have been generated by writeStringArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "string-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated String[]. + * + * @see #readListXml + */ + public static final String[] readThisStringArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in string-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in string-array"); + } + parser.next(); + String[] array = new String[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = parser.getAttributeValue(null, "value"); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + /** + * Read a boolean[] object from an XmlPullParser. The XML data could + * previously have been generated by writeBooleanArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "string-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated boolean[]. + * + * @see #readListXml + */ + public static final boolean[] readThisBooleanArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in string-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in string-array"); + } + parser.next(); + boolean[] array = new boolean[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Boolean.parseBoolean(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned at the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * + * @return Object The newly generated value object. + * + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static final Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException + { + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + return readThisValueXml(parser, name, null, false); + } else if (eventType == parser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Unexpected end of document"); + } + private static final Object readThisValueXml(XmlPullParser parser, String[] name, + ReadMapCallback callback, boolean arrayMap) + throws XmlPullParserException, java.io.IOException { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName); + Object res; + if (tagName.equals("null")) { + res = null; + } else if (tagName.equals("string")) { + String value = ""; + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + value); + return value; + } + throw new XmlPullParserException( + "Unexpected end tag in : " + parser.getName()); + } else if (eventType == parser.TEXT) { + value += parser.getText(); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in : " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in "); + } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) { + // all work already done by readThisPrimitiveValueXml + } else if (tagName.equals("byte-array")) { + res = readThisByteArrayXml(parser, "byte-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("int-array")) { + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("long-array")) { + res = readThisLongArrayXml(parser, "long-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("double-array")) { + res = readThisDoubleArrayXml(parser, "double-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("string-array")) { + res = readThisStringArrayXml(parser, "string-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("boolean-array")) { + res = readThisBooleanArrayXml(parser, "boolean-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("map")) { + parser.next(); + res = arrayMap + ? readThisArrayMapXml(parser, "map", name, callback) + : readThisMapXml(parser, "map", name, callback); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("list")) { + parser.next(); + res = readThisListXml(parser, "list", name, callback, arrayMap); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("set")) { + parser.next(); + res = readThisSetXml(parser, "set", name, callback, arrayMap); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (callback != null) { + res = callback.readThisUnknownObjectXml(parser, tagName); + name[0] = valueName; + return res; + } else { + throw new XmlPullParserException("Unknown tag: " + tagName); + } + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } + throw new XmlPullParserException( + "Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in <" + tagName + ">"); + } + private static final Object readThisPrimitiveValueXml(XmlPullParser parser, String tagName) + throws XmlPullParserException, java.io.IOException + { + try { + if (tagName.equals("int")) { + return Integer.parseInt(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("long")) { + return Long.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("float")) { + return new Float(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("double")) { + return new Double(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("boolean")) { + return Boolean.valueOf(parser.getAttributeValue(null, "value")); + } else { + return null; + } + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in <" + tagName + ">"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in <" + tagName + ">"); + } + } + public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + } + public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) + throws IOException, XmlPullParserException { + for (;;) { + int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT + || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { + return false; + } + if (type == XmlPullParser.START_TAG + && parser.getDepth() == outerDepth + 1) { + return true; + } + } + } + public static int readIntAttribute(XmlPullParser in, String name, int defaultValue) { + final String value = in.getAttributeValue(null, name); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + public static int readIntAttribute(XmlPullParser in, String name) throws IOException { + final String value = in.getAttributeValue(null, name); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ProtocolException("problem parsing " + name + "=" + value + " as int"); + } + } + public static void writeIntAttribute(XmlSerializer out, String name, int value) + throws IOException { + out.attribute(null, name, Integer.toString(value)); + } + public static long readLongAttribute(XmlPullParser in, String name, long defaultValue) { + final String value = in.getAttributeValue(null, name); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + public static long readLongAttribute(XmlPullParser in, String name) throws IOException { + final String value = in.getAttributeValue(null, name); + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ProtocolException("problem parsing " + name + "=" + value + " as long"); + } + } + public static void writeLongAttribute(XmlSerializer out, String name, long value) + throws IOException { + out.attribute(null, name, Long.toString(value)); + } + public static float readFloatAttribute(XmlPullParser in, String name) throws IOException { + final String value = in.getAttributeValue(null, name); + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + throw new ProtocolException("problem parsing " + name + "=" + value + " as long"); + } + } + public static void writeFloatAttribute(XmlSerializer out, String name, float value) + throws IOException { + out.attribute(null, name, Float.toString(value)); + } + public static boolean readBooleanAttribute(XmlPullParser in, String name) { + final String value = in.getAttributeValue(null, name); + return Boolean.parseBoolean(value); + } + public static boolean readBooleanAttribute(XmlPullParser in, String name, + boolean defaultValue) { + final String value = in.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Boolean.parseBoolean(value); + } + } + public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value) + throws IOException { + out.attribute(null, name, Boolean.toString(value)); + } + public static Uri readUriAttribute(XmlPullParser in, String name) { + final String value = in.getAttributeValue(null, name); + return (value != null) ? Uri.parse(value) : null; + } + public static void writeUriAttribute(XmlSerializer out, String name, Uri value) + throws IOException { + if (value != null) { + out.attribute(null, name, value.toString()); + } + } + public static String readStringAttribute(XmlPullParser in, String name) { + return in.getAttributeValue(null, name); + } + public static void writeStringAttribute(XmlSerializer out, String name, CharSequence value) + throws IOException { + if (value != null) { + out.attribute(null, name, value.toString()); + } + } + public static byte[] readByteArrayAttribute(XmlPullParser in, String name) { + final String value = in.getAttributeValue(null, name); + if (value != null) { + return Base64.decode(value, Base64.DEFAULT); + } else { + return null; + } + } + public static void writeByteArrayAttribute(XmlSerializer out, String name, byte[] value) + throws IOException { + if (value != null) { + out.attribute(null, name, Base64.encodeToString(value, Base64.DEFAULT)); + } + } + public static Bitmap readBitmapAttribute(XmlPullParser in, String name) { + final byte[] value = readByteArrayAttribute(in, name); + if (value != null) { + return BitmapFactory.decodeByteArray(value, 0, value.length); + } else { + return null; + } + } + @Deprecated + public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value) + throws IOException { + if (value != null) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + value.compress(CompressFormat.PNG, 90, os); + writeByteArrayAttribute(out, name, os.toByteArray()); + } + } + /** @hide */ + public interface WriteMapCallback { + /** + * Called from writeMapXml when an Object type is not recognized. The implementer + * must write out the entire element including start and end tags. + * + * @param v The object to be written out + * @param name The mapping key for v. Must be written into the "name" attribute of the + * start tag. + * @param out The XML output stream. + * @throws XmlPullParserException on unrecognized Object type. + * @throws IOException on XmlSerializer serialization errors. + * @hide + */ + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException; + } + /** @hide */ + public interface ReadMapCallback { + /** + * Called from readThisMapXml when a START_TAG is not recognized. The input stream + * is positioned within the start tag so that attributes can be read using in.getAttribute. + * + * @param in the XML input stream + * @param tag the START_TAG that was not recognized. + * @return the Object parsed from the stream which will be put into the map. + * @throws XmlPullParserException if the START_TAG is not recognized. + * @throws IOException on XmlPullParser serialization errors. + * @hide + */ + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/f2prateek/package-info.java b/AndroidCompat/src/main/java/com/f2prateek/package-info.java new file mode 100644 index 00000000..d15ae402 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/package-info.java @@ -0,0 +1,2 @@ +package com.f2prateek; +//TODO Consider if we can change this package into an Android dependency \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/BooleanAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/BooleanAdapter.java new file mode 100644 index 00000000..d8226948 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/BooleanAdapter.java @@ -0,0 +1,34 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class BooleanAdapter implements Preference.Adapter { + static final BooleanAdapter INSTANCE = new BooleanAdapter(); + + @Override public Boolean get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getBoolean(key, false); + } + + @Override public void set(@NonNull String key, @NonNull Boolean value, + @NonNull SharedPreferences.Editor editor) { + editor.putBoolean(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/EnumAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/EnumAdapter.java new file mode 100644 index 00000000..58bd419c --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/EnumAdapter.java @@ -0,0 +1,40 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class EnumAdapter> implements Preference.Adapter { + private final Class enumClass; + + EnumAdapter(Class enumClass) { + this.enumClass = enumClass; + } + + @Override public T get(@NonNull String key, @NonNull SharedPreferences preferences) { + String value = preferences.getString(key, null); + assert value != null; // Not called unless key is present. + return Enum.valueOf(enumClass, value); + } + + @Override + public void set(@NonNull String key, @NonNull T value, @NonNull SharedPreferences.Editor editor) { + editor.putString(key, value.name()); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/FloatAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/FloatAdapter.java new file mode 100644 index 00000000..9a749f19 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/FloatAdapter.java @@ -0,0 +1,34 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class FloatAdapter implements Preference.Adapter { + static final FloatAdapter INSTANCE = new FloatAdapter(); + + @Override public Float get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getFloat(key, 0f); + } + + @Override public void set(@NonNull String key, @NonNull Float value, + @NonNull SharedPreferences.Editor editor) { + editor.putFloat(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/IntegerAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/IntegerAdapter.java new file mode 100644 index 00000000..efda9d99 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/IntegerAdapter.java @@ -0,0 +1,34 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class IntegerAdapter implements Preference.Adapter { + static final IntegerAdapter INSTANCE = new IntegerAdapter(); + + @Override public Integer get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getInt(key, 0); + } + + @Override public void set(@NonNull String key, @NonNull Integer value, + @NonNull SharedPreferences.Editor editor) { + editor.putInt(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/LongAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/LongAdapter.java new file mode 100644 index 00000000..7c5d452f --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/LongAdapter.java @@ -0,0 +1,34 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class LongAdapter implements Preference.Adapter { + static final LongAdapter INSTANCE = new LongAdapter(); + + @Override public Long get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getLong(key, 0L); + } + + @Override public void set(@NonNull String key, @NonNull Long value, + @NonNull SharedPreferences.Editor editor) { + editor.putLong(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preconditions.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preconditions.java new file mode 100644 index 00000000..b4441fc9 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preconditions.java @@ -0,0 +1,30 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file may have been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +final class Preconditions { + static void checkNotNull(Object o, String message) { + if (o == null) { + throw new NullPointerException(message); + } + } + + private Preconditions() { + throw new AssertionError("No instances"); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preference.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preference.java new file mode 100644 index 00000000..cfdafb63 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/Preference.java @@ -0,0 +1,127 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file has been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import rx.Observable; +import rx.functions.Action1; + +/** A preference of type {@link T}. Instances can be created from {@link RxSharedPreferences}. */ +public final class Preference { + /** Stores and retrieves instances of {@code T} in {@link SharedPreferences}. */ + public interface Adapter { + /** Retrieve the value for {@code key} from {@code preferences}. */ + T get(@NonNull String key, @NonNull SharedPreferences preferences); + + /** + * Store non-null {@code value} for {@code key} in {@code editor}. + *

+ * Note: Implementations must not call {@code commit()} or {@code apply()} on + * {@code editor}. + */ + void set(@NonNull String key, @NonNull T value, @NonNull SharedPreferences.Editor editor); + } + + private final SharedPreferences preferences; + private final String key; + private final T defaultValue; + private final Adapter adapter; + private final Observable values; + + Preference(SharedPreferences preferences, final String key, T defaultValue, Adapter adapter, + Observable keyChanges) { + this.preferences = preferences; + this.key = key; + this.defaultValue = defaultValue; + this.adapter = adapter; + this.values = keyChanges + .filter(key::equals) + .startWith("") // Dummy value to trigger initial load. + .onBackpressureLatest() + .map(ignored -> get()); + } + + /** The key for which this preference will store and retrieve values. */ + @NonNull + public String key() { + return key; + } + + /** The value used if none is stored. May be {@code null}. */ + @Nullable + public T defaultValue() { + return defaultValue; + } + + /** + * Retrieve the current value for this preference. Returns {@link #defaultValue()} if no value is + * set. + */ + @Nullable + public T get() { + if (!preferences.contains(key)) { + return defaultValue; + } + return adapter.get(key, preferences); + } + + /** + * Change this preference's stored value to {@code value}. A value of {@code null} will delete the + * preference. + */ + public void set(@Nullable T value) { + SharedPreferences.Editor editor = preferences.edit(); + if (value == null) { + editor.remove(key); + } else { + adapter.set(key, value, editor); + } + editor.apply(); + } + + /** Returns true if this preference has a stored value. */ + public boolean isSet() { + return preferences.contains(key); + } + + /** Delete the stored value for this preference, if any. */ + public void delete() { + set(null); + } + + /** + * Observe changes to this preference. The current value or {@link #defaultValue()} will be + * emitted on first subscribe. + */ + @CheckResult @NonNull + public Observable asObservable() { + return values; + } + + /** + * An action which stores a new value for this preference. Passing {@code null} will delete the + * preference. + */ + @CheckResult @NonNull + public Action1 asAction() { + return (Action1) this::set; + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/RxSharedPreferences.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/RxSharedPreferences.java new file mode 100644 index 00000000..824e801c --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/RxSharedPreferences.java @@ -0,0 +1,178 @@ +/* +Copyright 2014 Prateek Srivastava + +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. + +This file has been modified after being copied from it's original source. + */ +package com.f2prateek.rx.preferences; + +import android.annotation.TargetApi; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.util.Collections; +import java.util.Set; +import rx.Observable; +import rx.subscriptions.Subscriptions; + +import static android.os.Build.VERSION_CODES.HONEYCOMB; +import static com.f2prateek.rx.preferences.Preconditions.checkNotNull; + +/** A factory for reactive {@link Preference} objects. */ +public final class RxSharedPreferences { + private static final Float DEFAULT_FLOAT = 0f; + private static final Integer DEFAULT_INTEGER = 0; + private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE; + private static final Long DEFAULT_LONG = 0L; + + /** Create an instance of {@link RxSharedPreferences} for {@code preferences}. */ + @CheckResult @NonNull + public static RxSharedPreferences create(@NonNull SharedPreferences preferences) { + checkNotNull(preferences, "preferences == null"); + return new RxSharedPreferences(preferences); + } + + private final SharedPreferences preferences; + private final Observable keyChanges; + + private RxSharedPreferences(final SharedPreferences preferences) { + this.preferences = preferences; + this.keyChanges = Observable.create((Observable.OnSubscribe) subscriber -> { + final OnSharedPreferenceChangeListener listener = (preferences1, key) -> subscriber.onNext(key); + + preferences.registerOnSharedPreferenceChangeListener(listener); + + subscriber.add(Subscriptions.create(() -> preferences.unregisterOnSharedPreferenceChangeListener(listener))); + }).share(); + } + + /** Create a boolean preference for {@code key}. Default is {@code false}. */ + @CheckResult @NonNull + public Preference getBoolean(@NonNull String key) { + return getBoolean(key, DEFAULT_BOOLEAN); + } + + /** Create a boolean preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public Preference getBoolean(@NonNull String key, @Nullable Boolean defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, BooleanAdapter.INSTANCE, keyChanges); + } + + /** Create an enum preference for {@code key}. Default is {@code null}. */ + @CheckResult @NonNull + public > Preference getEnum(@NonNull String key, + @NonNull Class enumClass) { + return getEnum(key, null, enumClass); + } + + /** Create an enum preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public > Preference getEnum(@NonNull String key, @Nullable T defaultValue, + @NonNull Class enumClass) { + checkNotNull(key, "key == null"); + checkNotNull(enumClass, "enumClass == null"); + Preference.Adapter adapter = new EnumAdapter<>(enumClass); + return new Preference<>(preferences, key, defaultValue, adapter, keyChanges); + } + + /** Create a float preference for {@code key}. Default is {@code 0}. */ + @CheckResult @NonNull + public Preference getFloat(@NonNull String key) { + return getFloat(key, DEFAULT_FLOAT); + } + + /** Create a float preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public Preference getFloat(@NonNull String key, @Nullable Float defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, FloatAdapter.INSTANCE, keyChanges); + } + + /** Create an integer preference for {@code key}. Default is {@code 0}. */ + @CheckResult @NonNull + public Preference getInteger(@NonNull String key) { + //noinspection UnnecessaryBoxing + return getInteger(key, DEFAULT_INTEGER); + } + + /** Create an integer preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public Preference getInteger(@NonNull String key, @Nullable Integer defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, IntegerAdapter.INSTANCE, keyChanges); + } + + /** Create a long preference for {@code key}. Default is {@code 0}. */ + @CheckResult @NonNull + public Preference getLong(@NonNull String key) { + //noinspection UnnecessaryBoxing + return getLong(key, DEFAULT_LONG); + } + + /** Create a long preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public Preference getLong(@NonNull String key, @Nullable Long defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, LongAdapter.INSTANCE, keyChanges); + } + + /** Create a preference of type {@code T} for {@code key}. Default is {@code null}. */ + @CheckResult @NonNull + public Preference getObject(@NonNull String key, @NonNull Preference.Adapter adapter) { + return getObject(key, null, adapter); + } + + /** + * Create a preference for type {@code T} for {@code key} with a default of {@code defaultValue}. + */ + @CheckResult @NonNull + public Preference getObject(@NonNull String key, @Nullable T defaultValue, + @NonNull Preference.Adapter adapter) { + checkNotNull(key, "key == null"); + checkNotNull(adapter, "adapter == null"); + return new Preference<>(preferences, key, defaultValue, adapter, keyChanges); + } + + /** Create a string preference for {@code key}. Default is {@code null}. */ + @CheckResult @NonNull + public Preference getString(@NonNull String key) { + return getString(key, null); + } + + /** Create a string preference for {@code key} with a default of {@code defaultValue}. */ + @CheckResult @NonNull + public Preference getString(@NonNull String key, @Nullable String defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, StringAdapter.INSTANCE, keyChanges); + } + + /** Create a string set preference for {@code key}. Default is an empty set. */ + @TargetApi(HONEYCOMB) + @CheckResult @NonNull + public Preference> getStringSet(@NonNull String key) { + return getStringSet(key, Collections.emptySet()); + } + + /** Create a string set preference for {@code key} with a default of {@code defaultValue}. */ + @TargetApi(HONEYCOMB) + @CheckResult @NonNull + public Preference> getStringSet(@NonNull String key, + @NonNull Set defaultValue) { + checkNotNull(key, "key == null"); + return new Preference<>(preferences, key, defaultValue, StringSetAdapter.INSTANCE, keyChanges); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringAdapter.java new file mode 100644 index 00000000..e95747a7 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringAdapter.java @@ -0,0 +1,17 @@ +package com.f2prateek.rx.preferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +final class StringAdapter implements Preference.Adapter { + static final StringAdapter INSTANCE = new StringAdapter(); + + @Override public String get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getString(key, null); + } + + @Override public void set(@NonNull String key, @NonNull String value, + @NonNull SharedPreferences.Editor editor) { + editor.putString(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringSetAdapter.java b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringSetAdapter.java new file mode 100644 index 00000000..3ebab280 --- /dev/null +++ b/AndroidCompat/src/main/java/com/f2prateek/rx/preferences/StringSetAdapter.java @@ -0,0 +1,22 @@ +package com.f2prateek.rx.preferences; + +import android.annotation.TargetApi; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import java.util.Set; + +import static android.os.Build.VERSION_CODES.HONEYCOMB; + +@TargetApi(HONEYCOMB) +final class StringSetAdapter implements Preference.Adapter> { + static final StringSetAdapter INSTANCE = new StringSetAdapter(); + + @Override public Set get(@NonNull String key, @NonNull SharedPreferences preferences) { + return preferences.getStringSet(key, null); + } + + @Override public void set(@NonNull String key, @NonNull Set value, + @NonNull SharedPreferences.Editor editor) { + editor.putStringSet(key, value); + } +} diff --git a/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/Connectivity.kt b/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/Connectivity.kt new file mode 100644 index 00000000..725c6869 --- /dev/null +++ b/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/Connectivity.kt @@ -0,0 +1,7 @@ +package com.github.pwittchen.reactivenetwork.library + +import android.net.NetworkInfo + +class Connectivity { + val state = NetworkInfo.State.CONNECTED +} diff --git a/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/ReactiveNetwork.kt b/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/ReactiveNetwork.kt new file mode 100644 index 00000000..a278a844 --- /dev/null +++ b/AndroidCompat/src/main/java/com/github/pwittchen/reactivenetwork/library/ReactiveNetwork.kt @@ -0,0 +1,14 @@ +package com.github.pwittchen.reactivenetwork.library + +import android.content.Context +import rx.Observable + +/** + * Created by nulldev on 12/29/16. + */ + +class ReactiveNetwork { + companion object { + fun observeNetworkConnectivity(context: Context) = Observable.just(Connectivity())!! + } +} diff --git a/AndroidCompat/src/main/java/com/squareup/duktape/Duktape.java b/AndroidCompat/src/main/java/com/squareup/duktape/Duktape.java new file mode 100644 index 00000000..18f1a341 --- /dev/null +++ b/AndroidCompat/src/main/java/com/squareup/duktape/Duktape.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 Square, Inc. + * + * 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 com.squareup.duktape; + +import kotlin.NotImplementedError; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.io.Closeable; + +/** A simple EMCAScript (Javascript) interpreter. */ +public final class Duktape implements Closeable, AutoCloseable { + + private ScriptEngineManager factory = new ScriptEngineManager(); + private ScriptEngine engine = factory.getEngineByName("JavaScript"); + + /** + * Create a new interpreter instance. Calls to this method must matched with + * calls to {@link #close()} on the returned instance to avoid leaking native memory. + */ + public static Duktape create() { + return new Duktape(); + } + + private Duktape() {} + + /** + * Evaluate {@code script} and return a result. {@code fileName} will be used in error + * reporting. Note that the result must be one of the supported Java types or the call will + * return null. + * + * @throws DuktapeException if there is an error evaluating the script. + */ + public synchronized Object evaluate(String script, String fileName) { + throw new NotImplementedError("Not implemented!"); + } + + /** + * Evaluate {@code script} and return a result. Note that the result must be one of the + * supported Java types or the call will return null. + * + * @throws DuktapeException if there is an error evaluating the script. + */ + public synchronized Object evaluate(String script) { + try { + return engine.eval(script); + } catch (ScriptException e) { + throw new DuktapeException(e.getMessage()); + } + } + + /** + * Provides {@code object} to JavaScript as a global object called {@code name}. {@code type} + * defines the interface implemented by {@code object} that will be accessible to JavaScript. + * {@code type} must be an interface that does not extend any other interfaces, and cannot define + * any overloaded methods. + *

Methods of the interface may return {@code void} or any of the following supported argument + * types: {@code boolean}, {@link Boolean}, {@code int}, {@link Integer}, {@code double}, + * {@link Double}, {@link String}. + */ + public synchronized void set(String name, Class type, T object) { + throw new NotImplementedError("Not implemented!"); + } + + /** + * Attaches to a global JavaScript object called {@code name} that implements {@code type}. + * {@code type} defines the interface implemented in JavaScript that will be accessible to Java. + * {@code type} must be an interface that does not extend any other interfaces, and cannot define + * any overloaded methods. + *

Methods of the interface may return {@code void} or any of the following supported argument + * types: {@code boolean}, {@link Boolean}, {@code int}, {@link Integer}, {@code double}, + * {@link Double}, {@link String}. + */ + public synchronized T get(final String name, final Class type) { + throw new NotImplementedError("Not implemented!"); + } + + /** + * Release the native resources associated with this object. You must call this + * method for each instance to avoid leaking native memory. + */ + @Override + public synchronized void close() {} +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/com/squareup/duktape/DuktapeException.java b/AndroidCompat/src/main/java/com/squareup/duktape/DuktapeException.java new file mode 100644 index 00000000..1639ddbe --- /dev/null +++ b/AndroidCompat/src/main/java/com/squareup/duktape/DuktapeException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Square, Inc. + * + * 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 com.squareup.duktape; + +import android.support.annotation.Keep; + +import java.util.regex.Pattern; + +// Called from native code. +@SuppressWarnings("unused") +// Instruct ProGuard not to strip this type. +@Keep +public final class DuktapeException extends RuntimeException { + + /** + * Duktape stack trace strings have multiple lines of the format " at func (file.ext:line)". + * "func" is optional, but we'll omit frames without a function, since it means the frame is in + * native code. + */ + private static final Pattern STACK_TRACE_PATTERN = null; + + /** Java StackTraceElements require a class name. We don't have one in JS, so use this. */ + private static final String STACK_TRACE_CLASS_NAME = "JavaScript"; + + public DuktapeException(String detailMessage) { + super(detailMessage); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/dalvik/system/BaseDexClassLoader.java b/AndroidCompat/src/main/java/dalvik/system/BaseDexClassLoader.java new file mode 100644 index 00000000..b2a48414 --- /dev/null +++ b/AndroidCompat/src/main/java/dalvik/system/BaseDexClassLoader.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011 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 dalvik.system; + +import org.jetbrains.annotations.Nullable; +import xyz.nulldev.androidcompat.pm.PackageController; +import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Enumeration; + +/** + * Base class for common functionality between various dex-based + * {@link ClassLoader} implementations. + */ +public class BaseDexClassLoader extends ClassLoader { + private PackageController controller = KodeinGlobalHelper.instance(PackageController.class); + + private final URLClassLoader realClassloader; + + /** originally specified path (just used for {@code toString()}) */ + private final String originalPath; + + /** + * Constructs an instance. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; may be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public BaseDexClassLoader(String dexPath, File optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(parent); + this.originalPath = dexPath; + + URL[] urls = Arrays.stream(dexPath.split(File.pathSeparator)).map(s -> { + try { + File file = new File(s); + + if(s.endsWith(".jar")) + return file.toURI().toURL(); + + File jar = controller.findJarFromApk(file); + + if(jar == null || !jar.exists()) + throw new IllegalStateException("Could not find APK jar!"); + + return jar.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("APK JAR is invalid!"); + } + }).toArray(URL[]::new); + + realClassloader = new URLClassLoader(urls, parent); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return realClassloader.loadClass(name); + } + + @Nullable + @Override + public URL getResource(String name) { + return realClassloader.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return realClassloader.getResources(name); + } + + public static boolean registerAsParallelCapable() { + return ClassLoader.registerAsParallelCapable(); + } + + public static URL getSystemResource(String name) { + return ClassLoader.getSystemResource(name); + } + + public static Enumeration getSystemResources(String name) throws IOException { + return ClassLoader.getSystemResources(name); + } + + public static InputStream getSystemResourceAsStream(String name) { + return ClassLoader.getSystemResourceAsStream(name); + } + + public static ClassLoader getSystemClassLoader() { + return ClassLoader.getSystemClassLoader(); + } + + @Override + public void setDefaultAssertionStatus(boolean enabled) { + realClassloader.setDefaultAssertionStatus(enabled); + } + + @Override + public void setPackageAssertionStatus(String packageName, boolean enabled) { + realClassloader.setPackageAssertionStatus(packageName, enabled); + } + + @Override + public void setClassAssertionStatus(String className, boolean enabled) { + realClassloader.setClassAssertionStatus(className, enabled); + } + + @Override + public void clearAssertionStatus() { + realClassloader.clearAssertionStatus(); + } + + + + @Override + public String toString() { + return getClass().getName() + "[" + originalPath + "]"; + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/dalvik/system/CloseGuard.java b/AndroidCompat/src/main/java/dalvik/system/CloseGuard.java new file mode 100644 index 00000000..37f977b3 --- /dev/null +++ b/AndroidCompat/src/main/java/dalvik/system/CloseGuard.java @@ -0,0 +1,275 @@ +/* + * 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 dalvik.system; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * CloseGuard is a mechanism for flagging implicit finalizer cleanup of + * resources that should have been cleaned up by explicit close + * methods (aka "explicit termination methods" in Effective Java). + *

+ * A simple example:

   {@code
+ *   class Foo {
+ *
+ *       private final CloseGuard guard = CloseGuard.get();
+ *
+ *       ...
+ *
+ *       public Foo() {
+ *           ...;
+ *           guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }
+ * + * In usage where the resource to be explicitly cleaned up are + * allocated after object construction, CloseGuard protection can + * be deferred. For example:
   {@code
+ *   class Bar {
+ *
+ *       private final CloseGuard guard = CloseGuard.get();
+ *
+ *       ...
+ *
+ *       public Bar() {
+ *           ...;
+ *       }
+ *
+ *       public void connect() {
+ *          ...;
+ *          guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }
+ * + * When used in a constructor calls to {@code open} should occur at + * the end of the constructor since an exception that would cause + * abrupt termination of the constructor will mean that the user will + * not have a reference to the object to cleanup explicitly. When used + * in a method, the call to {@code open} should occur just after + * resource acquisition. + * + * @hide + */ +public final class CloseGuard { + /** + * Instance used when CloseGuard is disabled to avoid allocation. + */ + private static final CloseGuard NOOP = new CloseGuard(); + /** + * Enabled by default so we can catch issues early in VM startup. + * Note, however, that Android disables this early in its startup, + * but enables it with DropBoxing for system apps on debug builds. + */ + private static volatile boolean ENABLED = true; + /** + * Hook for customizing how CloseGuard issues are reported. + */ + private static volatile Reporter REPORTER = new DefaultReporter(); + /** + * The default {@link Tracker}. + */ + private static final DefaultTracker DEFAULT_TRACKER = new DefaultTracker(); + /** + * Hook for customizing how CloseGuard issues are tracked. + */ + private static volatile Tracker currentTracker = DEFAULT_TRACKER; + /** + * Returns a CloseGuard instance. If CloseGuard is enabled, {@code + * #open(String)} can be used to set up the instance to warn on + * failure to close. If CloseGuard is disabled, a non-null no-op + * instance is returned. + */ + public static CloseGuard get() { + if (!ENABLED) { + return NOOP; + } + return new CloseGuard(); + } + /** + * Used to enable or disable CloseGuard. Note that CloseGuard only + * warns if it is enabled for both allocation and finalization. + */ + public static void setEnabled(boolean enabled) { + ENABLED = enabled; + } + /** + * True if CloseGuard mechanism is enabled. + */ + public static boolean isEnabled() { + return ENABLED; + } + /** + * Used to replace default Reporter used to warn of CloseGuard + * violations. Must be non-null. + */ + public static void setReporter(Reporter reporter) { + if (reporter == null) { + throw new NullPointerException("reporter == null"); + } + REPORTER = reporter; + } + /** + * Returns non-null CloseGuard.Reporter. + */ + public static Reporter getReporter() { + return REPORTER; + } + /** + * Sets the {@link Tracker} that is notified when resources are allocated and released. + * + *

This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + * + * @throws NullPointerException if tracker is null + */ + public static void setTracker(Tracker tracker) { + if (tracker == null) { + throw new NullPointerException("tracker == null"); + } + currentTracker = tracker; + } + /** + * Returns {@link #setTracker(Tracker) last Tracker that was set}, or otherwise a default + * Tracker that does nothing. + * + *

This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + */ + public static Tracker getTracker() { + return currentTracker; + } + private CloseGuard() {} + /** + * If CloseGuard is enabled, {@code open} initializes the instance + * with a warning that the caller should have explicitly called the + * {@code closer} method instead of relying on finalization. + * + * @param closer non-null name of explicit termination method + * @throws NullPointerException if closer is null, regardless of + * whether or not CloseGuard is enabled + */ + public void open(String closer) { + // always perform the check for valid API usage... + if (closer == null) { + throw new NullPointerException("closer == null"); + } + // ...but avoid allocating an allocationSite if disabled + if (this == NOOP || !ENABLED) { + return; + } + String message = "Explicit termination method '" + closer + "' not called"; + allocationSite = new Throwable(message); + currentTracker.open(allocationSite); + } + private Throwable allocationSite; + /** + * Marks this CloseGuard instance as closed to avoid warnings on + * finalization. + */ + public void close() { + currentTracker.close(allocationSite); + allocationSite = null; + } + /** + * If CloseGuard is enabled, logs a warning if the caller did not + * properly cleanup by calling an explicit close method + * before finalization. If CloseGuard is disabled, no action is + * performed. + */ + public void warnIfOpen() { + if (allocationSite == null || !ENABLED) { + return; + } + String message = + ("A resource was acquired at attached stack trace but never released. " + + "See java.io.Closeable for information on avoiding resource leaks."); + REPORTER.report(message, allocationSite); + } + /** + * Interface to allow customization of tracking behaviour. + * + *

This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so + * MUST NOT be used for any other purposes. + */ + public interface Tracker { + void open(Throwable allocationSite); + void close(Throwable allocationSite); + } + /** + * Default tracker which does nothing special and simply leaves it up to the GC to detect a + * leak. + */ + private static final class DefaultTracker implements Tracker { + @Override + public void open(Throwable allocationSite) { + } + @Override + public void close(Throwable allocationSite) { + } + } + /** + * Interface to allow customization of reporting behavior. + */ + public interface Reporter { + void report (String message, Throwable allocationSite); + } + /** + * Default Reporter which reports CloseGuard violations to the log. + */ + private static final class DefaultReporter implements Reporter { + private Logger logger = LoggerFactory.getLogger(DefaultReporter.class); + @Override public void report (String message, Throwable allocationSite) { + logger.warn(message, allocationSite); + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/dalvik/system/PathClassLoader.java b/AndroidCompat/src/main/java/dalvik/system/PathClassLoader.java new file mode 100644 index 00000000..817c04a1 --- /dev/null +++ b/AndroidCompat/src/main/java/dalvik/system/PathClassLoader.java @@ -0,0 +1,64 @@ +/* + * 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 dalvik.system; +/** + * Provides a simple {@link ClassLoader} implementation that operates on a list + * of files and directories in the local file system, but does not attempt to + * load classes from the network. Android uses this class for its system class + * loader and for its application class loader(s). + */ +public class PathClassLoader extends BaseDexClassLoader { + /** + * Creates a {@code PathClassLoader} that operates on a given list of files + * and directories. This method is equivalent to calling + * {@link #PathClassLoader(String, String, ClassLoader)} with a + * {@code null} value for the second argument (see description there). + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param parent the parent class loader + */ + public PathClassLoader(String dexPath, ClassLoader parent) { + super(dexPath, null, null, parent); + } + /** + * Creates a {@code PathClassLoader} that operates on two given + * lists of files and directories. The entries of the first list + * should be one of the following: + * + *

    + *
  • JAR/ZIP/APK files, possibly containing a "classes.dex" file as + * well as arbitrary resources. + *
  • Raw ".dex" files (not inside a zip file). + *
+ * + * The entries of the second list should be directories containing + * native library files. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public PathClassLoader(String dexPath, String libraryPath, + ClassLoader parent) { + super(dexPath, null, libraryPath, parent); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.kt b/AndroidCompat/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.kt new file mode 100644 index 00000000..4ca6d194 --- /dev/null +++ b/AndroidCompat/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.kt @@ -0,0 +1,8 @@ +package io.requery.android.database.sqlite + +import android.arch.persistence.db.SupportSQLiteOpenHelper +import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory + +class RequerySQLiteOpenHelperFactory { + fun create(configuration: SupportSQLiteOpenHelper.Configuration) = FrameworkSQLiteOpenHelperFactory().create(configuration) +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/kotlinx/coroutines/experimental/android/HandlerContext.kt b/AndroidCompat/src/main/java/kotlinx/coroutines/experimental/android/HandlerContext.kt new file mode 100644 index 00000000..22289e41 --- /dev/null +++ b/AndroidCompat/src/main/java/kotlinx/coroutines/experimental/android/HandlerContext.kt @@ -0,0 +1,5 @@ +package kotlinx.coroutines.experimental.android + +import kotlinx.coroutines.GlobalScope + +val UI = GlobalScope.coroutineContext \ No newline at end of file diff --git a/AndroidCompat/src/main/java/libcore/net/MimeUtils.java b/AndroidCompat/src/main/java/libcore/net/MimeUtils.java new file mode 100644 index 00000000..c0d916e8 --- /dev/null +++ b/AndroidCompat/src/main/java/libcore/net/MimeUtils.java @@ -0,0 +1,448 @@ +/* + * 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 libcore.net; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +/** + * Utilities for dealing with MIME types. + * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap. + */ +public final class MimeUtils { + private static final Map mimeTypeToExtensionMap = new HashMap(); + private static final Map extensionToMimeTypeMap = new HashMap(); + static { + // The following table is based on /etc/mime.types data minus + // chemical/* MIME types and MIME types that don't map to any + // file extensions. We also exclude top-level domain names to + // deal with cases like: + // + // mail.google.com/a/google.com + // + // and "active" MIME types (due to potential security issues). + // Note that this list is _not_ in alphabetical order and must not be sorted. + // The "most popular" extension must come first, so that it's the one returned + // by guessExtensionFromMimeType. + add("application/andrew-inset", "ez"); + add("application/dsptype", "tsp"); + add("application/epub+zip", "epub"); + add("application/hta", "hta"); + add("application/mac-binhex40", "hqx"); + add("application/mathematica", "nb"); + add("application/msaccess", "mdb"); + add("application/oda", "oda"); + add("application/ogg", "ogg"); + add("application/ogg", "oga"); + add("application/pdf", "pdf"); + add("application/pgp-keys", "key"); + add("application/pgp-signature", "pgp"); + add("application/pics-rules", "prf"); + add("application/pkix-cert", "cer"); + add("application/rar", "rar"); + add("application/rdf+xml", "rdf"); + add("application/rss+xml", "rss"); + add("application/zip", "zip"); + add("application/vnd.android.package-archive", "apk"); + add("application/vnd.cinderella", "cdy"); + add("application/vnd.ms-pki.stl", "stl"); + add("application/vnd.oasis.opendocument.database", "odb"); + add("application/vnd.oasis.opendocument.formula", "odf"); + add("application/vnd.oasis.opendocument.graphics", "odg"); + add("application/vnd.oasis.opendocument.graphics-template", "otg"); + add("application/vnd.oasis.opendocument.image", "odi"); + add("application/vnd.oasis.opendocument.presentation", "odp"); + add("application/vnd.oasis.opendocument.presentation-template", "otp"); + add("application/vnd.oasis.opendocument.spreadsheet", "ods"); + add("application/vnd.oasis.opendocument.spreadsheet-template", "ots"); + add("application/vnd.oasis.opendocument.text", "odt"); + add("application/vnd.oasis.opendocument.text-master", "odm"); + add("application/vnd.oasis.opendocument.text-template", "ott"); + add("application/vnd.oasis.opendocument.text-web", "oth"); + add("application/vnd.google-earth.kml+xml", "kml"); + add("application/vnd.google-earth.kmz", "kmz"); + add("application/msword", "doc"); + add("application/msword", "dot"); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx"); + add("application/vnd.ms-excel", "xls"); + add("application/vnd.ms-excel", "xlt"); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx"); + add("application/vnd.ms-powerpoint", "ppt"); + add("application/vnd.ms-powerpoint", "pot"); + add("application/vnd.ms-powerpoint", "pps"); + add("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"); + add("application/vnd.openxmlformats-officedocument.presentationml.template", "potx"); + add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx"); + add("application/vnd.rim.cod", "cod"); + add("application/vnd.smaf", "mmf"); + add("application/vnd.stardivision.calc", "sdc"); + add("application/vnd.stardivision.draw", "sda"); + add("application/vnd.stardivision.impress", "sdd"); + add("application/vnd.stardivision.impress", "sdp"); + add("application/vnd.stardivision.math", "smf"); + add("application/vnd.stardivision.writer", "sdw"); + add("application/vnd.stardivision.writer", "vor"); + add("application/vnd.stardivision.writer-global", "sgl"); + add("application/vnd.sun.xml.calc", "sxc"); + add("application/vnd.sun.xml.calc.template", "stc"); + add("application/vnd.sun.xml.draw", "sxd"); + add("application/vnd.sun.xml.draw.template", "std"); + add("application/vnd.sun.xml.impress", "sxi"); + add("application/vnd.sun.xml.impress.template", "sti"); + add("application/vnd.sun.xml.math", "sxm"); + add("application/vnd.sun.xml.writer", "sxw"); + add("application/vnd.sun.xml.writer.global", "sxg"); + add("application/vnd.sun.xml.writer.template", "stw"); + add("application/vnd.visio", "vsd"); + add("application/x-abiword", "abw"); + add("application/x-apple-diskimage", "dmg"); + add("application/x-bcpio", "bcpio"); + add("application/x-bittorrent", "torrent"); + add("application/x-cdf", "cdf"); + add("application/x-cdlink", "vcd"); + add("application/x-chess-pgn", "pgn"); + add("application/x-cpio", "cpio"); + add("application/x-debian-package", "deb"); + add("application/x-debian-package", "udeb"); + add("application/x-director", "dcr"); + add("application/x-director", "dir"); + add("application/x-director", "dxr"); + add("application/x-dms", "dms"); + add("application/x-doom", "wad"); + add("application/x-dvi", "dvi"); + add("application/x-font", "pfa"); + add("application/x-font", "pfb"); + add("application/x-font", "gsf"); + add("application/x-font", "pcf"); + add("application/x-font", "pcf.Z"); + add("application/x-freemind", "mm"); + // application/futuresplash isn't IANA, so application/x-futuresplash should come first. + add("application/x-futuresplash", "spl"); + add("application/futuresplash", "spl"); + add("application/x-gnumeric", "gnumeric"); + add("application/x-go-sgf", "sgf"); + add("application/x-graphing-calculator", "gcf"); + add("application/x-gtar", "tgz"); + add("application/x-gtar", "gtar"); + add("application/x-gtar", "taz"); + add("application/x-hdf", "hdf"); + add("application/x-hwp", "hwp"); // http://b/18788282. + add("application/x-ica", "ica"); + add("application/x-internet-signup", "ins"); + add("application/x-internet-signup", "isp"); + add("application/x-iphone", "iii"); + add("application/x-iso9660-image", "iso"); + add("application/x-jmol", "jmz"); + add("application/x-kchart", "chrt"); + add("application/x-killustrator", "kil"); + add("application/x-koan", "skp"); + add("application/x-koan", "skd"); + add("application/x-koan", "skt"); + add("application/x-koan", "skm"); + add("application/x-kpresenter", "kpr"); + add("application/x-kpresenter", "kpt"); + add("application/x-kspread", "ksp"); + add("application/x-kword", "kwd"); + add("application/x-kword", "kwt"); + add("application/x-latex", "latex"); + add("application/x-lha", "lha"); + add("application/x-lzh", "lzh"); + add("application/x-lzx", "lzx"); + add("application/x-maker", "frm"); + add("application/x-maker", "maker"); + add("application/x-maker", "frame"); + add("application/x-maker", "fb"); + add("application/x-maker", "book"); + add("application/x-maker", "fbdoc"); + add("application/x-mif", "mif"); + add("application/x-ms-wmd", "wmd"); + add("application/x-ms-wmz", "wmz"); + add("application/x-msi", "msi"); + add("application/x-ns-proxy-autoconfig", "pac"); + add("application/x-nwc", "nwc"); + add("application/x-object", "o"); + add("application/x-oz-application", "oza"); + add("application/x-pem-file", "pem"); + add("application/x-pkcs12", "p12"); + add("application/x-pkcs12", "pfx"); + add("application/x-pkcs7-certreqresp", "p7r"); + add("application/x-pkcs7-crl", "crl"); + add("application/x-quicktimeplayer", "qtl"); + add("application/x-shar", "shar"); + add("application/x-shockwave-flash", "swf"); + add("application/x-stuffit", "sit"); + add("application/x-sv4cpio", "sv4cpio"); + add("application/x-sv4crc", "sv4crc"); + add("application/x-tar", "tar"); + add("application/x-texinfo", "texinfo"); + add("application/x-texinfo", "texi"); + add("application/x-troff", "t"); + add("application/x-troff", "roff"); + add("application/x-troff-man", "man"); + add("application/x-ustar", "ustar"); + add("application/x-wais-source", "src"); + add("application/x-wingz", "wz"); + add("application/x-webarchive", "webarchive"); + add("application/x-webarchive-xml", "webarchivexml"); + add("application/x-x509-ca-cert", "crt"); + add("application/x-x509-user-cert", "crt"); + add("application/x-x509-server-cert", "crt"); + add("application/x-xcf", "xcf"); + add("application/x-xfig", "fig"); + add("application/xhtml+xml", "xhtml"); + // Video mime types for 3GPP first so they'll be default for guessMimeTypeFromExtension + // See RFC 3839 for 3GPP and RFC 4393 for 3GPP2 + add("video/3gpp", "3gpp"); + add("video/3gpp", "3gp"); + add("video/3gpp2", "3gpp2"); + add("video/3gpp2", "3g2"); + add("audio/3gpp", "3gpp"); + add("audio/aac", "aac"); + add("audio/aac-adts", "aac"); + add("audio/amr", "amr"); + add("audio/amr-wb", "awb"); + add("audio/basic", "snd"); + add("audio/flac", "flac"); + add("application/x-flac", "flac"); + add("audio/imelody", "imy"); + add("audio/midi", "mid"); + add("audio/midi", "midi"); + add("audio/midi", "ota"); + add("audio/midi", "kar"); + add("audio/midi", "rtttl"); + add("audio/midi", "xmf"); + add("audio/mobile-xmf", "mxmf"); + // add ".mp3" first so it will be the default for guessExtensionFromMimeType + add("audio/mpeg", "mp3"); + add("audio/mpeg", "mpga"); + add("audio/mpeg", "mpega"); + add("audio/mpeg", "mp2"); + add("audio/mpeg", "m4a"); + add("audio/mpegurl", "m3u"); + add("audio/prs.sid", "sid"); + add("audio/x-aiff", "aif"); + add("audio/x-aiff", "aiff"); + add("audio/x-aiff", "aifc"); + add("audio/x-gsm", "gsm"); + add("audio/x-matroska", "mka"); + add("audio/x-mpegurl", "m3u"); + add("audio/x-ms-wma", "wma"); + add("audio/x-ms-wax", "wax"); + add("audio/x-pn-realaudio", "ra"); + add("audio/x-pn-realaudio", "rm"); + add("audio/x-pn-realaudio", "ram"); + add("audio/x-realaudio", "ra"); + add("audio/x-scpls", "pls"); + add("audio/x-sd2", "sd2"); + add("audio/x-wav", "wav"); + // image/bmp isn't IANA, so image/x-ms-bmp should come first. + add("image/x-ms-bmp", "bmp"); + add("image/bmp", "bmp"); + add("image/gif", "gif"); + // image/ico isn't IANA, so image/x-icon should come first. + add("image/x-icon", "ico"); + add("image/ico", "cur"); + add("image/ico", "ico"); + add("image/ief", "ief"); + // add ".jpg" first so it will be the default for guessExtensionFromMimeType + add("image/jpeg", "jpg"); + add("image/jpeg", "jpeg"); + add("image/jpeg", "jpe"); + add("image/pcx", "pcx"); + add("image/png", "png"); + add("image/svg+xml", "svg"); + add("image/svg+xml", "svgz"); + add("image/tiff", "tiff"); + add("image/tiff", "tif"); + add("image/vnd.djvu", "djvu"); + add("image/vnd.djvu", "djv"); + add("image/vnd.wap.wbmp", "wbmp"); + add("image/webp", "webp"); + add("image/x-adobe-dng", "dng"); + add("image/x-canon-cr2", "cr2"); + add("image/x-cmu-raster", "ras"); + add("image/x-coreldraw", "cdr"); + add("image/x-coreldrawpattern", "pat"); + add("image/x-coreldrawtemplate", "cdt"); + add("image/x-corelphotopaint", "cpt"); + add("image/x-fuji-raf", "raf"); + add("image/x-jg", "art"); + add("image/x-jng", "jng"); + add("image/x-nikon-nef", "nef"); + add("image/x-nikon-nrw", "nrw"); + add("image/x-olympus-orf", "orf"); + add("image/x-panasonic-rw2", "rw2"); + add("image/x-pentax-pef", "pef"); + add("image/x-photoshop", "psd"); + add("image/x-portable-anymap", "pnm"); + add("image/x-portable-bitmap", "pbm"); + add("image/x-portable-graymap", "pgm"); + add("image/x-portable-pixmap", "ppm"); + add("image/x-samsung-srw", "srw"); + add("image/x-sony-arw", "arw"); + add("image/x-rgb", "rgb"); + add("image/x-xbitmap", "xbm"); + add("image/x-xpixmap", "xpm"); + add("image/x-xwindowdump", "xwd"); + add("model/iges", "igs"); + add("model/iges", "iges"); + add("model/mesh", "msh"); + add("model/mesh", "mesh"); + add("model/mesh", "silo"); + add("text/calendar", "ics"); + add("text/calendar", "icz"); + add("text/comma-separated-values", "csv"); + add("text/css", "css"); + add("text/html", "htm"); + add("text/html", "html"); + add("text/h323", "323"); + add("text/iuls", "uls"); + add("text/mathml", "mml"); + // add ".txt" first so it will be the default for guessExtensionFromMimeType + add("text/plain", "txt"); + add("text/plain", "asc"); + add("text/plain", "text"); + add("text/plain", "diff"); + add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint + add("text/richtext", "rtx"); + add("text/rtf", "rtf"); + add("text/text", "phps"); + add("text/tab-separated-values", "tsv"); + add("text/xml", "xml"); + add("text/x-bibtex", "bib"); + add("text/x-boo", "boo"); + add("text/x-c++hdr", "hpp"); + add("text/x-c++hdr", "h++"); + add("text/x-c++hdr", "hxx"); + add("text/x-c++hdr", "hh"); + add("text/x-c++src", "cpp"); + add("text/x-c++src", "c++"); + add("text/x-c++src", "cc"); + add("text/x-c++src", "cxx"); + add("text/x-chdr", "h"); + add("text/x-component", "htc"); + add("text/x-csh", "csh"); + add("text/x-csrc", "c"); + add("text/x-dsrc", "d"); + add("text/x-haskell", "hs"); + add("text/x-java", "java"); + add("text/x-literate-haskell", "lhs"); + add("text/x-moc", "moc"); + add("text/x-pascal", "p"); + add("text/x-pascal", "pas"); + add("text/x-pcs-gcd", "gcd"); + add("text/x-setext", "etx"); + add("text/x-tcl", "tcl"); + add("text/x-tex", "tex"); + add("text/x-tex", "ltx"); + add("text/x-tex", "sty"); + add("text/x-tex", "cls"); + add("text/x-vcalendar", "vcs"); + add("text/x-vcard", "vcf"); + add("video/avi", "avi"); + add("video/dl", "dl"); + add("video/dv", "dif"); + add("video/dv", "dv"); + add("video/fli", "fli"); + add("video/m4v", "m4v"); + add("video/mp2ts", "ts"); + add("video/mpeg", "mpeg"); + add("video/mpeg", "mpg"); + add("video/mpeg", "mpe"); + add("video/mp4", "mp4"); + add("video/mpeg", "VOB"); + add("video/quicktime", "qt"); + add("video/quicktime", "mov"); + add("video/vnd.mpegurl", "mxu"); + add("video/webm", "webm"); + add("video/x-la-asf", "lsf"); + add("video/x-la-asf", "lsx"); + add("video/x-matroska", "mkv"); + add("video/x-mng", "mng"); + add("video/x-ms-asf", "asf"); + add("video/x-ms-asf", "asx"); + add("video/x-ms-wm", "wm"); + add("video/x-ms-wmv", "wmv"); + add("video/x-ms-wmx", "wmx"); + add("video/x-ms-wvx", "wvx"); + add("video/x-sgi-movie", "movie"); + add("video/x-webex", "wrf"); + add("x-conference/x-cooltalk", "ice"); + add("x-epoc/x-sisx-app", "sisx"); + } + private static void add(String mimeType, String extension) { + // If we have an existing x -> y mapping, we do not want to + // override it with another mapping x -> y2. + // If a mime type maps to several extensions + // the first extension added is considered the most popular + // so we do not want to overwrite it later. + if (!mimeTypeToExtensionMap.containsKey(mimeType)) { + mimeTypeToExtensionMap.put(mimeType, extension); + } + if (!extensionToMimeTypeMap.containsKey(extension)) { + extensionToMimeTypeMap.put(extension, mimeType); + } + } + private MimeUtils() { + } + /** + * Returns true if the given case insensitive MIME type has an entry in the map. + * @param mimeType A MIME type (i.e. text/plain) + * @return True if a extension has been registered for + * the given case insensitive MIME type. + */ + public static boolean hasMimeType(String mimeType) { + return (guessExtensionFromMimeType(mimeType) != null); + } + /** + * Returns the MIME type for the given case insensitive file extension. + * @param extension A file extension without the leading '.' + * @return The MIME type has been registered for + * the given case insensitive file extension or null if there is none. + */ + public static String guessMimeTypeFromExtension(String extension) { + if (extension == null || extension.isEmpty()) { + return null; + } + extension = extension.toLowerCase(Locale.US); + return extensionToMimeTypeMap.get(extension); + } + /** + * Returns true if the given case insensitive extension has a registered MIME type. + * @param extension A file extension without the leading '.' + * @return True if a MIME type has been registered for + * the given case insensitive file extension. + */ + public static boolean hasExtension(String extension) { + return (guessMimeTypeFromExtension(extension) != null); + } + /** + * Returns the registered extension for the given case insensitive MIME type. Note that some + * MIME types map to multiple extensions. This call will return the most + * common extension for the given MIME type. + * @param mimeType A MIME type (i.e. text/plain) + * @return The extension has been registered for + * the given case insensitive MIME type or null if there is none. + */ + public static String guessExtensionFromMimeType(String mimeType) { + if (mimeType == null || mimeType.isEmpty()) { + return null; + } + mimeType = mimeType.toLowerCase(Locale.US); + return mimeTypeToExtensionMap.get(mimeType); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/libcore/net/UriCodec.java b/AndroidCompat/src/main/java/libcore/net/UriCodec.java new file mode 100644 index 00000000..cefbc37c --- /dev/null +++ b/AndroidCompat/src/main/java/libcore/net/UriCodec.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + * [Modified for TachiWeb] + */ +package libcore.net; +import java.io.ByteArrayOutputStream; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Encodes and decodes {@code application/x-www-form-urlencoded} content. + * Subclasses define exactly which characters are legal. + * + *

By default, UTF-8 is used to encode escaped characters. A single input + * character like "\u0080" may be encoded to multiple octets like %C2%80. + */ +public abstract class UriCodec { + /** + * Returns true if {@code c} does not need to be escaped. + */ + protected abstract boolean isRetained(char c); + /** + * Throws if {@code s} is invalid according to this encoder. + */ + public final String validate(String uri, int start, int end, String name) + throws URISyntaxException { + for (int i = start; i < end; ) { + char ch = uri.charAt(i); + if ((ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || isRetained(ch)) { + i++; + } else if (ch == '%') { + if (i + 2 >= end) { + throw new URISyntaxException(uri, "Incomplete % sequence in " + name, i); + } + int d1 = hexToInt(uri.charAt(i + 1)); + int d2 = hexToInt(uri.charAt(i + 2)); + if (d1 == -1 || d2 == -1) { + throw new URISyntaxException(uri, "Invalid % sequence: " + + uri.substring(i, i + 3) + " in " + name, i); + } + i += 3; + } else { + throw new URISyntaxException(uri, "Illegal character in " + name, i); + } + } + return uri.substring(start, end); + } + /** + * Throws if {@code s} contains characters that are not letters, digits or + * in {@code legal}. + */ + public static void validateSimple(String s, String legal) + throws URISyntaxException { + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (!((ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || legal.indexOf(ch) > -1)) { + throw new URISyntaxException(s, "Illegal character", i); + } + } + } + /** + * Encodes {@code s} and appends the result to {@code builder}. + * + * @param isPartiallyEncoded true to fix input that has already been + * partially or fully encoded. For example, input of "hello%20world" is + * unchanged with isPartiallyEncoded=true but would be double-escaped to + * "hello%2520world" otherwise. + */ + private void appendEncoded(StringBuilder builder, String s, Charset charset, + boolean isPartiallyEncoded) { + if (s == null) { + throw new NullPointerException(); + } + int escapeStart = -1; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || isRetained(c) + || (c == '%' && isPartiallyEncoded)) { + if (escapeStart != -1) { + appendHex(builder, s.substring(escapeStart, i), charset); + escapeStart = -1; + } + if (c == '%' && isPartiallyEncoded) { + // this is an encoded 3-character sequence like "%20" + builder.append(s, i, i + 3); + i += 2; + } else if (c == ' ') { + builder.append('+'); + } else { + builder.append(c); + } + } else if (escapeStart == -1) { + escapeStart = i; + } + } + if (escapeStart != -1) { + appendHex(builder, s.substring(escapeStart, s.length()), charset); + } + } + public final String encode(String s, Charset charset) { + // Guess a bit larger for encoded form + StringBuilder builder = new StringBuilder(s.length() + 16); + appendEncoded(builder, s, charset, false); + return builder.toString(); + } + public final void appendEncoded(StringBuilder builder, String s) { + appendEncoded(builder, s, StandardCharsets.UTF_8, false); + } + public final void appendPartiallyEncoded(StringBuilder builder, String s) { + appendEncoded(builder, s, StandardCharsets.UTF_8, true); + } + /** + * @param convertPlus true to convert '+' to ' '. + * @param throwOnFailure true to throw an IllegalArgumentException on + * invalid escape sequences; false to replace them with the replacement + * character (U+fffd). + */ + public static String decode(String s, boolean convertPlus, Charset charset, + boolean throwOnFailure) { + if (s.indexOf('%') == -1 && (!convertPlus || s.indexOf('+') == -1)) { + return s; + } + StringBuilder result = new StringBuilder(s.length()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (int i = 0; i < s.length();) { + char c = s.charAt(i); + if (c == '%') { + do { + int d1, d2; + if (i + 2 < s.length() + && (d1 = hexToInt(s.charAt(i + 1))) != -1 + && (d2 = hexToInt(s.charAt(i + 2))) != -1) { + out.write((byte) ((d1 << 4) + d2)); + } else if (throwOnFailure) { + throw new IllegalArgumentException("Invalid % sequence at " + i + ": " + s); + } else { + byte[] replacement = "\ufffd".getBytes(charset); + out.write(replacement, 0, replacement.length); + } + i += 3; + } while (i < s.length() && s.charAt(i) == '%'); + result.append(new String(out.toByteArray(), charset)); + out.reset(); + } else { + if (convertPlus && c == '+') { + c = ' '; + } + result.append(c); + i++; + } + } + return result.toString(); + } + /** + * Like {@link Character#digit}, but without support for non-ASCII + * characters. + */ + private static int hexToInt(char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('a' <= c && c <= 'f') { + return 10 + (c - 'a'); + } else if ('A' <= c && c <= 'F') { + return 10 + (c - 'A'); + } else { + return -1; + } + } + public static String decode(String s) { + return decode(s, false, StandardCharsets.UTF_8, true); + } + private static void appendHex(StringBuilder builder, String s, Charset charset) { + for (byte b : s.getBytes(charset)) { + appendHex(builder, b); + } + } + private static void appendHex(StringBuilder sb, byte b) { + sb.append('%'); + sb.append(String.format("0x%x", b)); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/libcore/util/EmptyArray.java b/AndroidCompat/src/main/java/libcore/util/EmptyArray.java new file mode 100644 index 00000000..4b73d065 --- /dev/null +++ b/AndroidCompat/src/main/java/libcore/util/EmptyArray.java @@ -0,0 +1,34 @@ +/* + * 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 libcore.util; +public final class EmptyArray { + private EmptyArray() {} + public static final boolean[] BOOLEAN = new boolean[0]; + public static final byte[] BYTE = new byte[0]; + public static final char[] CHAR = new char[0]; + public static final double[] DOUBLE = new double[0]; + public static final float[] FLOAT = new float[0]; + public static final int[] INT = new int[0]; + public static final long[] LONG = new long[0]; + public static final Class[] CLASS = new Class[0]; + public static final Object[] OBJECT = new Object[0]; + public static final String[] STRING = new String[0]; + public static final Throwable[] THROWABLE = new Throwable[0]; + public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0]; + public static final java.lang.reflect.Type[] TYPE = new java.lang.reflect.Type[0]; + public static final java.lang.reflect.TypeVariable[] TYPE_VARIABLE = + new java.lang.reflect.TypeVariable[0]; +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/org/json/JSON.java b/AndroidCompat/src/main/java/org/json/JSON.java new file mode 100644 index 00000000..ac304a80 --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSON.java @@ -0,0 +1,116 @@ +/* + * 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 org.json; + +class JSON { + /** + * Returns the input if it is a JSON-permissible value; throws otherwise. + */ + static double checkDouble(double d) throws JSONException { + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw new JSONException("Forbidden numeric value: " + d); + } + return d; + } + + static Boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + String stringValue = (String) value; + if ("true".equalsIgnoreCase(stringValue)) { + return true; + } else if ("false".equalsIgnoreCase(stringValue)) { + return false; + } + } + return null; + } + + static Double toDouble(Object value) { + if (value instanceof Double) { + return (Double) value; + } else if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof String) { + try { + return Double.valueOf((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static Integer toInteger(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof String) { + try { + return (int) Double.parseDouble((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static Long toLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else if (value instanceof String) { + try { + return (long) Double.parseDouble((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static String toString(Object value) { + if (value instanceof String) { + return (String) value; + } else if (value != null) { + return String.valueOf(value); + } + return null; + } + + public static JSONException typeMismatch(Object indexOrName, Object actual, + String requiredType) throws JSONException { + if (actual == null) { + throw new JSONException("Value at " + indexOrName + " is null."); + } else { + throw new JSONException("Value " + actual + " at " + indexOrName + + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + } + + public static JSONException typeMismatch(Object actual, String requiredType) + throws JSONException { + if (actual == null) { + throw new JSONException("Value is null."); + } else { + throw new JSONException("Value " + actual + + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + } +} diff --git a/AndroidCompat/src/main/java/org/json/JSONArray.java b/AndroidCompat/src/main/java/org/json/JSONArray.java new file mode 100644 index 00000000..30a5fb07 --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSONArray.java @@ -0,0 +1,629 @@ +/* + * 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 org.json; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * A dense indexed sequence of values. Values may be any mix of + * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings, + * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}. + * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() + * infinities}, or of any type not listed here. + * + *

{@code JSONArray} has the same type coercion behavior and + * optional/mandatory accessors as {@link JSONObject}. See that class' + * documentation for details. + * + *

Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value {@link + * JSONObject#NULL}. In particular, {@code get} fails if the requested index + * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}. + * + *

Instances of this class are not thread safe. Although this class is + * nonfinal, it was not designed for inheritance and should not be subclassed. + * In particular, self-use by overridable methods is not specified. See + * Effective Java Item 17, "Design and Document or inheritance or else + * prohibit it" for further information. + */ +public class JSONArray { + + private final List values; + + /** + * Creates a {@code JSONArray} with no values. + */ + public JSONArray() { + values = new ArrayList(); + } + + /** + * Creates a new {@code JSONArray} by copying all values from the given + * collection. + * + * @param copyFrom a collection whose values are of supported types. + * Unsupported values are not permitted and will yield an array in an + * inconsistent state. + */ + /* Accept a raw type for API compatibility */ + public JSONArray(Collection copyFrom) { + this(); + if (copyFrom != null) { + for (Iterator it = copyFrom.iterator(); it.hasNext(); ) { + put(JSONObject.wrap(it.next())); + } + } + } + + /** + * Creates a new {@code JSONArray} with values from the next array in the + * tokener. + * + * @param readFrom a tokener whose nextValue() method will yield a + * {@code JSONArray}. + * @throws JSONException if the parse fails or doesn't yield a + * {@code JSONArray}. + */ + public JSONArray(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just + * parse to temporary JSONArray and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONArray) { + values = ((JSONArray) object).values; + } else { + throw JSON.typeMismatch(object, "JSONArray"); + } + } + + /** + * Creates a new {@code JSONArray} with values from the JSON string. + * + * @param json a JSON-encoded string containing an array. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONArray}. + */ + public JSONArray(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONArray} with values from the given primitive array. + */ + public JSONArray(Object array) throws JSONException { + if (!array.getClass().isArray()) { + throw new JSONException("Not a primitive array: " + array.getClass()); + } + final int length = Array.getLength(array); + values = new ArrayList(length); + for (int i = 0; i < length; ++i) { + put(JSONObject.wrap(Array.get(array, i))); + } + } + + /** + * Returns the number of values in this array. + */ + public int length() { + return values.size(); + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JSONArray put(boolean value) { + values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + */ + public JSONArray put(double value) throws JSONException { + values.add(JSON.checkDouble(value)); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JSONArray put(int value) { + values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JSONArray put(long value) { + values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May + * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. Unsupported values are not permitted and will cause the + * array to be in an inconsistent state. + * @return this array. + */ + public JSONArray put(Object value) { + values.add(value); + return this; + } + + /** + * Same as {@link #put}, with added validity checks. + */ + void checkedPut(Object value) throws JSONException { + if (value instanceof Number) { + JSON.checkDouble(((Number) value).doubleValue()); + } + + put(value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * + * @return this array. + */ + public JSONArray put(int index, boolean value) throws JSONException { + return put(index, (Boolean) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + */ + public JSONArray put(int index, double value) throws JSONException { + return put(index, (Double) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * + * @return this array. + */ + public JSONArray put(int index, int value) throws JSONException { + return put(index, (Integer) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * + * @return this array. + */ + public JSONArray put(int index, long value) throws JSONException { + return put(index, (Long) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May + * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + * @return this array. + */ + public JSONArray put(int index, Object value) throws JSONException { + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + while (values.size() <= index) { + values.add(null); + } + values.set(index, value); + return this; + } + + /** + * Returns true if this array has no value at {@code index}, or if its value + * is the {@code null} reference or {@link JSONObject#NULL}. + */ + public boolean isNull(int index) { + Object value = opt(index); + return value == null || value == JSONObject.NULL; + } + + /** + * Returns the value at {@code index}. + * + * @throws JSONException if this array has no value at {@code index}, or if + * that value is the {@code null} reference. This method returns + * normally if the value is {@code JSONObject#NULL}. + */ + public Object get(int index) throws JSONException { + try { + Object value = values.get(index); + if (value == null) { + throw new JSONException("Value at " + index + " is null."); + } + return value; + } catch (IndexOutOfBoundsException e) { + throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")", e); + } + } + + /** + * Returns the value at {@code index}, or null if the array has no value + * at {@code index}. + */ + public Object opt(int index) { + if (index < 0 || index >= values.size()) { + return null; + } + return values.get(index); + } + + /** + * Removes and returns the value at {@code index}, or null if the array has no value + * at {@code index}. + */ + public Object remove(int index) { + if (index < 0 || index >= values.size()) { + return null; + } + return values.remove(index); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. + * + * @throws JSONException if the value at {@code index} doesn't exist or + * cannot be coerced to a boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "boolean"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. Returns false otherwise. + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. Returns {@code fallback} otherwise. + */ + public boolean optBoolean(int index, boolean fallback) { + Object object = opt(index); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can + * be coerced to a double. + * + * @throws JSONException if the value at {@code index} doesn't exist or + * cannot be coerced to a double. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "double"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can + * be coerced to a double. Returns {@code NaN} otherwise. + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * Returns the value at {@code index} if it exists and is a double or can + * be coerced to a double. Returns {@code fallback} otherwise. + */ + public double optDouble(int index, double fallback) { + Object object = opt(index); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is an int or + * can be coerced to an int. + * + * @throws JSONException if the value at {@code index} doesn't exist or + * cannot be coerced to a int. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "int"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is an int or + * can be coerced to an int. Returns 0 otherwise. + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * Returns the value at {@code index} if it exists and is an int or + * can be coerced to an int. Returns {@code fallback} otherwise. + */ + public int optInt(int index, int fallback) { + Object object = opt(index); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a long or + * can be coerced to a long. + * + * @throws JSONException if the value at {@code index} doesn't exist or + * cannot be coerced to a long. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "long"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a long or + * can be coerced to a long. Returns 0 otherwise. + */ + public long optLong(int index) { + return optLong(index, 0L); + } + + /** + * Returns the value at {@code index} if it exists and is a long or + * can be coerced to a long. Returns {@code fallback} otherwise. + */ + public long optLong(int index, long fallback) { + Object object = opt(index); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. + * + * @throws JSONException if no such value exists. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "String"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. Returns the empty string if no such value exists. + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. Returns {@code fallback} if no such value exists. + */ + public String optString(int index, String fallback) { + Object object = opt(index); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. + * + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONArray}. + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } else { + throw JSON.typeMismatch(index, object, "JSONArray"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + */ + public JSONArray optJSONArray(int index) { + Object object = opt(index); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. + * + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONObject}. + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } else { + throw JSON.typeMismatch(index, object, "JSONObject"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + */ + public JSONObject optJSONObject(int index) { + Object object = opt(index); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns a new object whose values are the values in this array, and whose + * names are the values in {@code names}. Names and values are paired up by + * index from 0 through to the shorter array's length. Names that are not + * strings will be coerced to strings. This method returns null if either + * array is empty. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + JSONObject result = new JSONObject(); + int length = Math.min(names.length(), values.size()); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(name, opt(i)); + } + return result; + } + + /** + * Returns a new string by alternating this array's values with {@code + * separator}. This array's string values are quoted and have their special + * characters escaped. For example, the array containing the strings '12" + * pizza', 'taco' and 'soda' joined on '+' returns this: + *
"12\" pizza"+"taco"+"soda"
+ */ + public String join(String separator) throws JSONException { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + for (int i = 0, size = values.size(); i < size; i++) { + if (i > 0) { + stringer.out.append(separator); + } + stringer.value(values.get(i)); + } + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.out.toString(); + } + + /** + * Encodes this array as a compact JSON string, such as: + *
[94043,90210]
+ */ + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } catch (JSONException e) { + return null; + } + } + + /** + * Encodes this array as a human readable JSON string for debugging, such + * as: + *
+     * [
+     *     94043,
+     *     90210
+     * ]
+ * + * @param indentSpaces the number of spaces to indent for each level of + * nesting. + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.array(); + for (Object value : values) { + stringer.value(value); + } + stringer.endArray(); + } + + @Override + public boolean equals(Object o) { + return o instanceof JSONArray && ((JSONArray) o).values.equals(values); + } + + @Override + public int hashCode() { + // diverge from the original, which doesn't implement hashCode + return values.hashCode(); + } +} diff --git a/AndroidCompat/src/main/java/org/json/JSONException.java b/AndroidCompat/src/main/java/org/json/JSONException.java new file mode 100644 index 00000000..981d426a --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSONException.java @@ -0,0 +1,58 @@ +/* + * 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 org.json; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Thrown to indicate a problem with the JSON API. Such problems include: + *
    + *
  • Attempts to parse or construct malformed documents + *
  • Use of null as a name + *
  • Use of numeric types not available to JSON, such as {@link + * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + *
  • Lookups using an out of range index or nonexistent name + *
  • Type mismatches on lookups + *
+ * + *

Although this is a checked exception, it is rarely recoverable. Most + * callers should simply wrap this exception in an unchecked exception and + * rethrow: + *

  public JSONArray toJSONObject() {
+ *     try {
+ *         JSONObject result = new JSONObject();
+ *         ...
+ *     } catch (JSONException e) {
+ *         throw new RuntimeException(e);
+ *     }
+ * }
+ */ +public class JSONException extends RuntimeException { + + public JSONException(String s) { + super(s); + } + + public JSONException(String message, Throwable cause) { + super(message, cause); + } + + public JSONException(Throwable cause) { + super(cause); + } + +} diff --git a/AndroidCompat/src/main/java/org/json/JSONObject.java b/AndroidCompat/src/main/java/org/json/JSONObject.java new file mode 100644 index 00000000..bb025d44 --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSONObject.java @@ -0,0 +1,854 @@ +/* + * 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 org.json; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.*; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * A modifiable set of name/value mappings. Names are unique, non-null strings. + * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray + * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}. + * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link + * Double#isInfinite() infinities}, or of any type not listed here. + * + *

This class can coerce values to another type when requested. + *

+ * + *

This class can look up both mandatory and optional values: + *

    + *
  • Use getType() to retrieve a mandatory value. This + * fails with a {@code JSONException} if the requested name has no value + * or if the value cannot be coerced to the requested type. + *
  • Use optType() to retrieve an optional value. This + * returns a system- or user-supplied default if the requested name has no + * value or if the value cannot be coerced to the requested type. + *
+ * + *

Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value {@link + * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the + * named entry from the object but {@code put(name, JSONObject.NULL)} stores an + * entry whose value is {@code JSONObject.NULL}. + * + *

Instances of this class are not thread safe. Although this class is + * nonfinal, it was not designed for inheritance and should not be subclassed. + * In particular, self-use by overrideable methods is not specified. See + * Effective Java Item 17, "Design and Document or inheritance or else + * prohibit it" for further information. + */ +public class JSONObject { + + /** + * A sentinel value used to explicitly define a name with no value. Unlike + * {@code null}, names with this value: + *

    + *
  • show up in the {@link #names} array + *
  • show up in the {@link #keys} iterator + *
  • return {@code true} for {@link #has(String)} + *
  • do not throw on {@link #get(String)} + *
  • are included in the encoded JSON string. + *
+ * + *

This value violates the general contract of {@link Object#equals} by + * returning true when compared to {@code null}. Its {@link #toString} + * method returns "null". + */ + @NonNull + public static final Object NULL = new Object() { + @Override + public boolean equals(Object o) { + return o == this || o == null; // API specifies this broken equals implementation + } + + // at least make the broken equals(null) consistent with Objects.hashCode(null). + @Override + public int hashCode() { + return Objects.hashCode(null); + } + + @Override + public String toString() { + return "null"; + } + }; + private static final Double NEGATIVE_ZERO = -0d; + private final LinkedHashMap nameValuePairs; + + /** + * Creates a {@code JSONObject} with no name/value mappings. + */ + public JSONObject() { + nameValuePairs = new LinkedHashMap(); + } + + /** + * Creates a new {@code JSONObject} by copying all name/value mappings from + * the given map. + * + * @param copyFrom a map whose keys are of type {@link String} and whose + * values are of supported types. + * @throws NullPointerException if any of the map's keys are null. + */ + /* (accept a raw type for API compatibility) */ + public JSONObject(@NonNull Map copyFrom) { + this(); + Map contentsTyped = (Map) copyFrom; + for (Map.Entry entry : contentsTyped.entrySet()) { + /* + * Deviate from the original by checking that keys are non-null and + * of the proper type. (We still defer validating the values). + */ + String key = (String) entry.getKey(); + if (key == null) { + throw new NullPointerException("key == null"); + } + nameValuePairs.put(key, wrap(entry.getValue())); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the next + * object in the tokener. + * + * @param readFrom a tokener whose nextValue() method will yield a + * {@code JSONObject}. + * @throws JSONException if the parse fails or doesn't yield a + * {@code JSONObject}. + */ + public JSONObject(@NonNull JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just + * parse to temporary JSONObject and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONObject) { + this.nameValuePairs = ((JSONObject) object).nameValuePairs; + } else { + throw JSON.typeMismatch(object, "JSONObject"); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the JSON + * string. + * + * @param json a JSON-encoded string containing an object. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONObject}. + */ + public JSONObject(@NonNull String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONObject} by copying mappings for the listed names + * from the given object. Names that aren't present in {@code copyFrom} will + * be skipped. + */ + public JSONObject(@NonNull JSONObject copyFrom, @NonNull String[] names) throws JSONException { + this(); + for (String name : names) { + Object value = copyFrom.opt(name); + if (value != null) { + nameValuePairs.put(name, value); + } + } + } + + /** + * Encodes the number as a JSON string. + * + * @param number a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + */ + @NonNull + public static String numberToString(@NonNull Number number) throws JSONException { + if (number == null) { + throw new JSONException("Number must be non-null"); + } + + double doubleValue = number.doubleValue(); + JSON.checkDouble(doubleValue); + + // the original returns "-0" instead of "-0.0" for negative zero + if (number.equals(NEGATIVE_ZERO)) { + return "-0"; + } + + long longValue = number.longValue(); + if (doubleValue == (double) longValue) { + return Long.toString(longValue); + } + + return number.toString(); + } + + /** + * Encodes {@code data} as a JSON string. This applies quotes and any + * necessary character escaping. + * + * @param data the string to encode. Null will be interpreted as an empty + * string. + */ + @NonNull + public static String quote(@Nullable String data) { + if (data == null) { + return "\"\""; + } + try { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + stringer.value(data); + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.toString(); + } catch (JSONException e) { + throw new AssertionError(); + } + } + + /** + * Wraps the given object if necessary. + * + *

If the object is null or , returns {@link #NULL}. + * If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. + * If the object is {@code NULL}, no wrapping is necessary. + * If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}. + * If the object is a {@code Map}, returns an equivalent {@code JSONObject}. + * If the object is a primitive wrapper type or {@code String}, returns the object. + * Otherwise if the object is from a {@code java} package, returns the result of {@code toString}. + * If wrapping fails, returns null. + */ + @Nullable + public static Object wrap(@Nullable Object o) { + if (o == null) { + return NULL; + } + if (o instanceof JSONArray || o instanceof JSONObject) { + return o; + } + if (o.equals(NULL)) { + return o; + } + try { + if (o instanceof Collection) { + return new JSONArray((Collection) o); + } else if (o.getClass().isArray()) { + return new JSONArray(o); + } + if (o instanceof Map) { + return new JSONObject((Map) o); + } + if (o instanceof Boolean || + o instanceof Byte || + o instanceof Character || + o instanceof Double || + o instanceof Float || + o instanceof Integer || + o instanceof Long || + o instanceof Short || + o instanceof String) { + return o; + } + if (o.getClass().getPackage().getName().startsWith("java.")) { + return o.toString(); + } + } catch (Exception ignored) { + } + return null; + } + + /** + * Returns the number of name/value mappings in this object. + */ + public int length() { + return nameValuePairs.size(); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + @NonNull + public JSONObject put(@NonNull String name, boolean value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + */ + @NonNull + public JSONObject put(@NonNull String name, double value) throws JSONException { + nameValuePairs.put(checkName(name), JSON.checkDouble(value)); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + @NonNull + public JSONObject put(@NonNull String name, int value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + @NonNull + public JSONObject put(@NonNull String name, long value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. If the value is {@code null}, any existing + * mapping for {@code name} is removed. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link #NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + * @return this object. + */ + @NonNull + public JSONObject put(@NonNull String name, @Nullable Object value) throws JSONException { + if (value == null) { + nameValuePairs.remove(name); + return this; + } + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Equivalent to {@code put(name, value)} when both parameters are non-null; + * does nothing otherwise. + */ + @NonNull + public JSONObject putOpt(@Nullable String name, @Nullable Object value) throws JSONException { + if (name == null || value == null) { + return this; + } + return put(name, value); + } + + /** + * Appends {@code value} to the array already mapped to {@code name}. If + * this object has no mapping for {@code name}, this inserts a new mapping. + * If the mapping exists but its value is not an array, the existing + * and new values are inserted in order into a new array which is itself + * mapped to {@code name}. In aggregate, this allows values to be added to a + * mapping one at a time. + * + *

Note that {@code append(String, Object)} provides better semantics. + * In particular, the mapping for {@code name} will always be a + * {@link JSONArray}. Using {@code accumulate} will result in either a + * {@link JSONArray} or a mapping whose type is the type of {@code value} + * depending on the number of calls to it. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link #NULL} or null. May not be {@link + * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + */ + // TODO: Change {@code append) to {@link #append} when append is + // unhidden. + @NonNull + public JSONObject accumulate(@NonNull String name, @Nullable Object value) throws JSONException { + Object current = nameValuePairs.get(checkName(name)); + if (current == null) { + return put(name, value); + } + + if (current instanceof JSONArray) { + JSONArray array = (JSONArray) current; + array.checkedPut(value); + } else { + JSONArray array = new JSONArray(); + array.checkedPut(current); + array.checkedPut(value); + nameValuePairs.put(name, array); + } + return this; + } + + /** + * Appends values to the array mapped to {@code name}. A new {@link JSONArray} + * mapping for {@code name} will be inserted if no mapping exists. If the existing + * mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException} + * will be thrown. + * + * @throws JSONException if {@code name} is {@code null} or if the mapping for + * {@code name} is non-null and is not a {@link JSONArray}. + * @hide + */ + public JSONObject append(String name, Object value) throws JSONException { + Object current = nameValuePairs.get(checkName(name)); + + final JSONArray array; + if (current instanceof JSONArray) { + array = (JSONArray) current; + } else if (current == null) { + JSONArray newArray = new JSONArray(); + nameValuePairs.put(name, newArray); + array = newArray; + } else { + throw new JSONException("Key " + name + " is not a JSONArray"); + } + + array.checkedPut(value); + + return this; + } + + String checkName(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + return name; + } + + /** + * Removes the named mapping if it exists; does nothing otherwise. + * + * @return the value previously mapped by {@code name}, or null if there was + * no such mapping. + */ + @Nullable + public Object remove(@Nullable String name) { + return nameValuePairs.remove(name); + } + + /** + * Returns true if this object has no mapping for {@code name} or if it has + * a mapping whose value is {@link #NULL}. + */ + public boolean isNull(@Nullable String name) { + Object value = nameValuePairs.get(name); + return value == null || value == NULL; + } + + /** + * Returns true if this object has a mapping for {@code name}. The mapping + * may be {@link #NULL}. + */ + public boolean has(@Nullable String name) { + return nameValuePairs.containsKey(name); + } + + /** + * Returns the value mapped by {@code name}, or throws if no such mapping exists. + * + * @throws JSONException if no such mapping exists. + */ + @NonNull + public Object get(@NonNull String name) throws JSONException { + Object result = nameValuePairs.get(name); + if (result == null) { + throw new JSONException("No value for " + name); + } + return result; + } + + /** + * Returns the value mapped by {@code name}, or null if no such mapping + * exists. + */ + @Nullable + public Object opt(@Nullable String name) { + return nameValuePairs.get(name); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or throws otherwise. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a boolean. + */ + public boolean getBoolean(@NonNull String name) throws JSONException { + Object object = get(name); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "boolean"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or false otherwise. + */ + public boolean optBoolean(@Nullable String name) { + return optBoolean(name, false); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or {@code fallback} otherwise. + */ + public boolean optBoolean(@Nullable String name, boolean fallback) { + Object object = opt(name); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or throws otherwise. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a double. + */ + public double getDouble(@NonNull String name) throws JSONException { + Object object = get(name); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "double"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or {@code NaN} otherwise. + */ + public double optDouble(@Nullable String name) { + return optDouble(name, Double.NaN); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or {@code fallback} otherwise. + */ + public double optDouble(@Nullable String name, double fallback) { + Object object = opt(name); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or throws otherwise. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to an int. + */ + public int getInt(@NonNull String name) throws JSONException { + Object object = get(name); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "int"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or 0 otherwise. + */ + public int optInt(@Nullable String name) { + return optInt(name, 0); + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or {@code fallback} otherwise. + */ + public int optInt(@Nullable String name, int fallback) { + Object object = opt(name); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or throws otherwise. + * Note that JSON represents numbers as doubles, + * so this is lossy; use strings to transfer numbers via JSON. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a long. + */ + public long getLong(@NonNull String name) throws JSONException { + Object object = get(name); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "long"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles, + * so this is lossy; use strings to transfer numbers via JSON. + */ + public long optLong(@Nullable String name) { + return optLong(name, 0L); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or {@code fallback} otherwise. Note that JSON represents + * numbers as doubles, so this is lossy; use strings to transfer + * numbers via JSON. + */ + public long optLong(@Nullable String name, long fallback) { + Object object = opt(name); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or throws if no such mapping exists. + * + * @throws JSONException if no such mapping exists. + */ + @NonNull + public String getString(@NonNull String name) throws JSONException { + Object object = get(name); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "String"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or the empty string if no such mapping exists. + */ + @NonNull + public String optString(@Nullable String name) { + return optString(name, ""); + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or {@code fallback} if no such mapping exists. + */ + @NonNull + public String optString(@Nullable String name, @NonNull String fallback) { + Object object = opt(name); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}, or throws otherwise. + * + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONArray}. + */ + @NonNull + public JSONArray getJSONArray(@NonNull String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONArray) { + return (JSONArray) object; + } else { + throw JSON.typeMismatch(name, object, "JSONArray"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}, or null otherwise. + */ + @Nullable + public JSONArray optJSONArray(@Nullable String name) { + Object object = opt(name); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}, or throws otherwise. + * + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONObject}. + */ + @NonNull + public JSONObject getJSONObject(@NonNull String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONObject) { + return (JSONObject) object; + } else { + throw JSON.typeMismatch(name, object, "JSONObject"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}, or null otherwise. + */ + @Nullable + public JSONObject optJSONObject(@Nullable String name) { + Object object = opt(name); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns an array with the values corresponding to {@code names}. The + * array contains null for names that aren't mapped. This method returns + * null if {@code names} is either null or empty. + */ + @Nullable + public JSONArray toJSONArray(@Nullable JSONArray names) throws JSONException { + JSONArray result = new JSONArray(); + if (names == null) { + return null; + } + int length = names.length(); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(opt(name)); + } + return result; + } + + /** + * Returns an iterator of the {@code String} names in this object. The + * returned iterator supports {@link Iterator#remove() remove}, which will + * remove the corresponding mapping from this object. If this object is + * modified after the iterator is returned, the iterator's behavior is + * undefined. The order of the keys is undefined. + */ + @NonNull + public Iterator keys() { + return nameValuePairs.keySet().iterator(); + } + + /** + * Returns the set of {@code String} names in this object. The returned set + * is a view of the keys in this object. {@link Set#remove(Object)} will remove + * the corresponding mapping from this object and set iterator behaviour + * is undefined if this object is modified after it is returned. + *

+ * See {@link #keys()}. + * + * @hide. + */ + public Set keySet() { + return nameValuePairs.keySet(); + } + + /** + * Returns an array containing the string names in this object. This method + * returns null if this object contains no mappings. + */ + @Nullable + public JSONArray names() { + return nameValuePairs.isEmpty() + ? null + : new JSONArray(new ArrayList(nameValuePairs.keySet())); + } + + /** + * Encodes this object as a compact JSON string, such as: + *

{"query":"Pizza","locations":[94043,90210]}
+ */ + @Override + @NonNull + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } catch (JSONException e) { + return null; + } + } + + /** + * Encodes this object as a human readable JSON string for debugging, such + * as: + *
+     * {
+     *     "query": "Pizza",
+     *     "locations": [
+     *         94043,
+     *         90210
+     *     ]
+     * }
+ * + * @param indentSpaces the number of spaces to indent for each level of + * nesting. + */ + @NonNull + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.object(); + for (Map.Entry entry : nameValuePairs.entrySet()) { + stringer.key(entry.getKey()).value(entry.getValue()); + } + stringer.endObject(); + } +} diff --git a/AndroidCompat/src/main/java/org/json/JSONStringer.java b/AndroidCompat/src/main/java/org/json/JSONStringer.java new file mode 100644 index 00000000..c765b306 --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSONStringer.java @@ -0,0 +1,433 @@ +/* + * 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 org.json; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most + * application developers should use those methods directly and disregard this + * API. For example:
+ * JSONObject object = ...
+ * String json = object.toString();
+ * + *

Stringers only encode well-formed JSON strings. In particular: + *

    + *
  • The stringer must have exactly one top-level array or object. + *
  • Lexical scopes must be balanced: every call to {@link #array} must + * have a matching call to {@link #endArray} and every call to {@link + * #object} must have a matching call to {@link #endObject}. + *
  • Arrays may not contain keys (property names). + *
  • Objects must alternate keys (property names) and values. + *
  • Values are inserted with either literal {@link #value(Object) value} + * calls, or by nesting arrays or objects. + *
+ * Calls that would result in a malformed JSON string will fail with a + * {@link JSONException}. + * + *

This class provides no facility for pretty-printing (ie. indenting) + * output. To encode indented output, use {@link JSONObject#toString(int)} or + * {@link JSONArray#toString(int)}. + * + *

Some implementations of the API support at most 20 levels of nesting. + * Attempts to create more than 20 levels of nesting may fail with a {@link + * JSONException}. + * + *

Each stringer may be used to encode a single top level value. Instances of + * this class are not thread safe. Although this class is nonfinal, it was not + * designed for inheritance and should not be subclassed. In particular, + * self-use by overrideable methods is not specified. See Effective Java + * Item 17, "Design and Document or inheritance or else prohibit it" for further + * information. + */ +public class JSONStringer { + + /** + * The output data, containing at most one top-level array or object. + */ + final StringBuilder out = new StringBuilder(); + /** + * Unlike the original implementation, this stack isn't limited to 20 + * levels of nesting. + */ + private final List stack = new ArrayList(); + /** + * A string containing a full set of spaces for a single level of + * indentation, or null for no pretty printing. + */ + private final String indent; + + public JSONStringer() { + indent = null; + } + + JSONStringer(int indentSpaces) { + char[] indentChars = new char[indentSpaces]; + Arrays.fill(indentChars, ' '); + indent = new String(indentChars); + } + + /** + * Begins encoding a new array. Each call to this method must be paired with + * a call to {@link #endArray}. + * + * @return this stringer. + */ + public JSONStringer array() throws JSONException { + return open(Scope.EMPTY_ARRAY, "["); + } + + /** + * Ends encoding the current array. + * + * @return this stringer. + */ + public JSONStringer endArray() throws JSONException { + return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); + } + + /** + * Begins encoding a new object. Each call to this method must be paired + * with a call to {@link #endObject}. + * + * @return this stringer. + */ + public JSONStringer object() throws JSONException { + return open(Scope.EMPTY_OBJECT, "{"); + } + + /** + * Ends encoding the current object. + * + * @return this stringer. + */ + public JSONStringer endObject() throws JSONException { + return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); + } + + /** + * Enters a new scope by appending any necessary whitespace and the given + * bracket. + */ + JSONStringer open(Scope empty, String openBracket) throws JSONException { + if (stack.isEmpty() && out.length() > 0) { + throw new JSONException("Nesting problem: multiple top-level roots"); + } + beforeValue(); + stack.add(empty); + out.append(openBracket); + return this; + } + + /** + * Closes the current scope by appending any necessary whitespace and the + * given bracket. + */ + JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { + Scope context = peek(); + if (context != nonempty && context != empty) { + throw new JSONException("Nesting problem"); + } + + stack.remove(stack.size() - 1); + if (context == nonempty) { + newline(); + } + out.append(closeBracket); + return this; + } + + /** + * Returns the value on the top of the stack. + */ + private Scope peek() throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + return stack.get(stack.size() - 1); + } + + /** + * Replace the value on the top of the stack with the given value. + */ + private void replaceTop(Scope topOfStack) { + stack.set(stack.size() - 1, topOfStack); + } + + /** + * Encodes {@code value}. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs} + * or {@link Double#isInfinite() infinities}. + * @return this stringer. + */ + public JSONStringer value(Object value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + + if (value instanceof JSONArray) { + ((JSONArray) value).writeTo(this); + return this; + + } else if (value instanceof JSONObject) { + ((JSONObject) value).writeTo(this); + return this; + } + + beforeValue(); + + if (value == null + || value instanceof Boolean + || value == JSONObject.NULL) { + out.append(value); + + } else if (value instanceof Number) { + out.append(JSONObject.numberToString((Number) value)); + + } else { + string(value.toString()); + } + + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @return this stringer. + */ + public JSONStringer value(boolean value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + */ + public JSONStringer value(double value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(JSONObject.numberToString(value)); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @return this stringer. + */ + public JSONStringer value(long value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + private void string(String value) { + out.append("\""); + for (int i = 0, length = value.length(); i < length; i++) { + char c = value.charAt(i); + + /* + * From RFC 4627, "All Unicode characters may be placed within the + * quotation marks except for the characters that must be escaped: + * quotation mark, reverse solidus, and the control characters + * (U+0000 through U+001F)." + */ + switch (c) { + case '"': + case '\\': + case '/': + out.append('\\').append(c); + break; + + case '\t': + out.append("\\t"); + break; + + case '\b': + out.append("\\b"); + break; + + case '\n': + out.append("\\n"); + break; + + case '\r': + out.append("\\r"); + break; + + case '\f': + out.append("\\f"); + break; + + default: + if (c <= 0x1F) { + out.append(String.format("\\u%04x", (int) c)); + } else { + out.append(c); + } + break; + } + + } + out.append("\""); + } + + private void newline() { + if (indent == null) { + return; + } + + out.append("\n"); + for (int i = 0; i < stack.size(); i++) { + out.append(indent); + } + } + + /** + * Encodes the key (property name) to this stringer. + * + * @param name the name of the forthcoming value. May not be null. + * @return this stringer. + */ + public JSONStringer key(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + beforeKey(); + string(name); + return this; + } + + /** + * Inserts any necessary separators and whitespace before a name. Also + * adjusts the stack to expect the key's value. + */ + private void beforeKey() throws JSONException { + Scope context = peek(); + if (context == Scope.NONEMPTY_OBJECT) { // first in object + out.append(','); + } else if (context != Scope.EMPTY_OBJECT) { // not in an object! + throw new JSONException("Nesting problem"); + } + newline(); + replaceTop(Scope.DANGLING_KEY); + } + + /** + * Inserts any necessary separators and whitespace before a literal value, + * inline array, or inline object. Also adjusts the stack to expect either a + * closing bracket or another element. + */ + private void beforeValue() throws JSONException { + if (stack.isEmpty()) { + return; + } + + Scope context = peek(); + if (context == Scope.EMPTY_ARRAY) { // first in array + replaceTop(Scope.NONEMPTY_ARRAY); + newline(); + } else if (context == Scope.NONEMPTY_ARRAY) { // another in array + out.append(','); + newline(); + } else if (context == Scope.DANGLING_KEY) { // value for key + out.append(indent == null ? ":" : ": "); + replaceTop(Scope.NONEMPTY_OBJECT); + } else if (context != Scope.NULL) { + throw new JSONException("Nesting problem"); + } + } + + /** + * Returns the encoded JSON string. + * + *

If invoked with unterminated arrays or unclosed objects, this method's + * return value is undefined. + * + *

Warning: although it contradicts the general contract + * of {@link Object#toString}, this method returns null if the stringer + * contains no data. + */ + @Override + public String toString() { + return out.length() == 0 ? null : out.toString(); + } + + /** + * Lexical scoping elements within this stringer, necessary to insert the + * appropriate separator characters (ie. commas and colons) and to detect + * nesting errors. + */ + enum Scope { + + /** + * An array with no elements requires no separators or newlines before + * it is closed. + */ + EMPTY_ARRAY, + + /** + * A array with at least one value requires a comma and newline before + * the next element. + */ + NONEMPTY_ARRAY, + + /** + * An object with no keys or values requires no separators or newlines + * before it is closed. + */ + EMPTY_OBJECT, + + /** + * An object whose most recent element is a key. The next element must + * be a value. + */ + DANGLING_KEY, + + /** + * An object with at least one name/value pair requires a comma and + * newline before the next element. + */ + NONEMPTY_OBJECT, + + /** + * A special bracketless array needed by JSONStringer.join() and + * JSONObject.quote() only. Not used for JSON encoding. + */ + NULL, + } +} diff --git a/AndroidCompat/src/main/java/org/json/JSONTokener.java b/AndroidCompat/src/main/java/org/json/JSONTokener.java new file mode 100644 index 00000000..e863cbda --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/JSONTokener.java @@ -0,0 +1,613 @@ +/* + * 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 org.json; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Parses a JSON (RFC 4627) + * encoded string into the corresponding object. Most clients of + * this class will use only need the {@link #JSONTokener(String) constructor} + * and {@link #nextValue} method. Example usage:

+ * String json = "{"
+ *         + "  \"query\": \"Pizza\", "
+ *         + "  \"locations\": [ 94043, 90210 ] "
+ *         + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ * + *

For best interoperability and performance use JSON that complies with + * RFC 4627, such as that generated by {@link JSONStringer}. For legacy reasons + * this parser is lenient, so a successful parse does not indicate that the + * input string was valid JSON. All of the following syntax errors will be + * ignored: + *

    + *
  • End of line comments starting with {@code //} or {@code #} and ending + * with a newline character. + *
  • C-style comments starting with {@code /*} and ending with + * {@code *}{@code /}. Such comments may not be nested. + *
  • Strings that are unquoted or {@code 'single quoted'}. + *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. + *
  • Octal integers prefixed with {@code 0}. + *
  • Array elements separated by {@code ;}. + *
  • Unnecessary array separators. These are interpreted as if null was the + * omitted value. + *
  • Key-value pairs separated by {@code =} or {@code =>}. + *
  • Key-value pairs separated by {@code ;}. + *
+ * + *

Each tokener may be used to parse a single JSON string. Instances of this + * class are not thread safe. Although this class is nonfinal, it was not + * designed for inheritance and should not be subclassed. In particular, + * self-use by overrideable methods is not specified. See Effective Java + * Item 17, "Design and Document or inheritance or else prohibit it" for further + * information. + */ +public class JSONTokener { + + /** + * The input JSON. + */ + private final String in; + + /** + * The index of the next character to be returned by {@link #next}. When + * the input is exhausted, this equals the input's length. + */ + private int pos; + + /** + * @param in JSON encoded string. Null is not permitted and will yield a + * tokener that throws {@code NullPointerExceptions} when methods are + * called. + */ + public JSONTokener(String in) { + // consume an optional byte order mark (BOM) if it exists + if (in != null && in.startsWith("\ufeff")) { + in = in.substring(1); + } + this.in = in; + } + + /** + * Returns the integer [0..15] value for the given hex character, or -1 + * for non-hex input. + * + * @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other + * character will yield a -1 result. + */ + public static int dehexchar(char hex) { + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + return hex - 'A' + 10; + } else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } else { + return -1; + } + } + + /** + * Returns the next value from the input. + * + * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double or {@link JSONObject#NULL}. + * @throws JSONException if the input is malformed. + */ + public Object nextValue() throws JSONException { + int c = nextCleanInternal(); + switch (c) { + case -1: + throw syntaxError("End of input"); + + case '{': + return readObject(); + + case '[': + return readArray(); + + case '\'': + case '"': + return nextString((char) c); + + default: + pos--; + return readLiteral(); + } + } + + private int nextCleanInternal() throws JSONException { + while (pos < in.length()) { + int c = in.charAt(pos++); + switch (c) { + case '\t': + case ' ': + case '\n': + case '\r': + continue; + + case '/': + if (pos == in.length()) { + return c; + } + + char peek = in.charAt(pos); + switch (peek) { + case '*': + // skip a /* c-style comment */ + pos++; + int commentEnd = in.indexOf("*/", pos); + if (commentEnd == -1) { + throw syntaxError("Unterminated comment"); + } + pos = commentEnd + 2; + continue; + + case '/': + // skip a // end-of-line comment + pos++; + skipToEndOfLine(); + continue; + + default: + return c; + } + + case '#': + /* + * Skip a # hash end-of-line comment. The JSON RFC doesn't + * specify this behavior, but it's required to parse + * existing documents. See http://b/2571423. + */ + skipToEndOfLine(); + continue; + + default: + return c; + } + } + + return -1; + } + + /** + * Advances the position until after the next newline character. If the line + * is terminated by "\r\n", the '\n' must be consumed as whitespace by the + * caller. + */ + private void skipToEndOfLine() { + for (; pos < in.length(); pos++) { + char c = in.charAt(pos); + if (c == '\r' || c == '\n') { + pos++; + break; + } + } + } + + /** + * Returns the string up to but not including {@code quote}, unescaping any + * character escape sequences encountered along the way. The opening quote + * should have already been read. This consumes the closing quote, but does + * not include it in the returned string. + * + * @param quote either ' or ". + */ + public String nextString(char quote) throws JSONException { + /* + * For strings that are free of escape sequences, we can just extract + * the result as a substring of the input. But if we encounter an escape + * sequence, we need to use a StringBuilder to compose the result. + */ + StringBuilder builder = null; + + /* the index of the first character not yet appended to the builder. */ + int start = pos; + + while (pos < in.length()) { + int c = in.charAt(pos++); + if (c == quote) { + if (builder == null) { + // a new string avoids leaking memory + return in.substring(start, pos - 1); + } else { + builder.append(in, start, pos - 1); + return builder.toString(); + } + } + + if (c == '\\') { + if (pos == in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(in, start, pos - 1); + builder.append(readEscapeCharacter()); + start = pos; + } + } + + throw syntaxError("Unterminated string"); + } + + /** + * Unescapes the character identified by the character or characters that + * immediately follow a backslash. The backslash '\' should have already + * been read. This supports both unicode escapes "u000A" and two-character + * escapes "\n". + */ + private char readEscapeCharacter() throws JSONException { + char escaped = in.charAt(pos++); + switch (escaped) { + case 'u': + if (pos + 4 > in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + String hex = in.substring(pos, pos + 4); + pos += 4; + try { + return (char) Integer.parseInt(hex, 16); + } catch (NumberFormatException nfe) { + throw syntaxError("Invalid escape sequence: " + hex); + } + + case 't': + return '\t'; + + case 'b': + return '\b'; + + case 'n': + return '\n'; + + case 'r': + return '\r'; + + case 'f': + return '\f'; + + case '\'': + case '"': + case '\\': + default: + return escaped; + } + } + + /** + * Reads a null, boolean, numeric or unquoted string literal value. Numeric + * values will be returned as an Integer, Long, or Double, in that order of + * preference. + */ + private Object readLiteral() throws JSONException { + String literal = nextToInternal("{}[]/\\:,=;# \t\f"); + + if (literal.length() == 0) { + throw syntaxError("Expected literal value"); + } else if ("null".equalsIgnoreCase(literal)) { + return JSONObject.NULL; + } else if ("true".equalsIgnoreCase(literal)) { + return Boolean.TRUE; + } else if ("false".equalsIgnoreCase(literal)) { + return Boolean.FALSE; + } + + /* try to parse as an integral type... */ + if (literal.indexOf('.') == -1) { + int base = 10; + String number = literal; + if (number.startsWith("0x") || number.startsWith("0X")) { + number = number.substring(2); + base = 16; + } else if (number.startsWith("0") && number.length() > 1) { + number = number.substring(1); + base = 8; + } + try { + long longValue = Long.parseLong(number, base); + if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { + return (int) longValue; + } else { + return longValue; + } + } catch (NumberFormatException e) { + /* + * This only happens for integral numbers greater than + * Long.MAX_VALUE, numbers in exponential form (5e-10) and + * unquoted strings. Fall through to try floating point. + */ + } + } + + /* ...next try to parse as a floating point... */ + try { + return Double.valueOf(literal); + } catch (NumberFormatException ignored) { + } + + /* ... finally give up. We have an unquoted string */ + return literal; // a new string avoids leaking memory + } + + /** + * Returns the string up to but not including any of the given characters or + * a newline character. This does not consume the excluded character. + */ + private String nextToInternal(String excluded) { + int start = pos; + for (; pos < in.length(); pos++) { + char c = in.charAt(pos); + if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { + return in.substring(start, pos); + } + } + return in.substring(start); + } + + /** + * Reads a sequence of key/value pairs and the trailing closing brace '}' of + * an object. The opening brace '{' should have already been read. + */ + private JSONObject readObject() throws JSONException { + JSONObject result = new JSONObject(); + + /* Peek to see if this is the empty object. */ + int first = nextCleanInternal(); + if (first == '}') { + return result; + } else if (first != -1) { + pos--; + } + + while (true) { + Object name = nextValue(); + if (!(name instanceof String)) { + if (name == null) { + throw syntaxError("Names cannot be null"); + } else { + throw syntaxError("Names must be strings, but " + name + + " is of type " + name.getClass().getName()); + } + } + + /* + * Expect the name/value separator to be either a colon ':', an + * equals sign '=', or an arrow "=>". The last two are bogus but we + * include them because that's what the original implementation did. + */ + int separator = nextCleanInternal(); + if (separator != ':' && separator != '=') { + throw syntaxError("Expected ':' after " + name); + } + if (pos < in.length() && in.charAt(pos) == '>') { + pos++; + } + + result.put((String) name, nextValue()); + + switch (nextCleanInternal()) { + case '}': + return result; + case ';': + case ',': + continue; + default: + throw syntaxError("Unterminated object"); + } + } + } + + /** + * Reads a sequence of values and the trailing closing brace ']' of an + * array. The opening brace '[' should have already been read. Note that + * "[]" yields an empty array, but "[,]" returns a two-element array + * equivalent to "[null,null]". + */ + private JSONArray readArray() throws JSONException { + JSONArray result = new JSONArray(); + + /* to cover input that ends with ",]". */ + boolean hasTrailingSeparator = false; + + while (true) { + switch (nextCleanInternal()) { + case -1: + throw syntaxError("Unterminated array"); + case ']': + if (hasTrailingSeparator) { + result.put(null); + } + return result; + case ',': + case ';': + /* A separator without a value first means "null". */ + result.put(null); + hasTrailingSeparator = true; + continue; + default: + pos--; + } + + result.put(nextValue()); + + switch (nextCleanInternal()) { + case ']': + return result; + case ',': + case ';': + hasTrailingSeparator = true; + continue; + default: + throw syntaxError("Unterminated array"); + } + } + } + + /** + * Returns an exception containing the given message plus the current + * position and the entire input string. + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this); + } + + /* + * Legacy APIs. + * + * None of the methods below are on the critical path of parsing JSON + * documents. They exist only because they were exposed by the original + * implementation and may be used by some clients. + */ + + /** + * Returns the current position and the entire input string. + */ + @Override + public String toString() { + // consistent with the original implementation + return " at character " + pos + " of " + in; + } + + /** + * Returns true until the input has been exhausted. + */ + public boolean more() { + return pos < in.length(); + } + + /** + * Returns the next available character, or the null character '\0' if all + * input has been exhausted. The return value of this method is ambiguous + * for JSON strings that contain the character '\0'. + */ + public char next() { + return pos < in.length() ? in.charAt(pos++) : '\0'; + } + + /** + * Returns the next available character if it equals {@code c}. Otherwise an + * exception is thrown. + */ + public char next(char c) throws JSONException { + char result = next(); + if (result != c) { + throw syntaxError("Expected " + c + " but was " + result); + } + return result; + } + + /** + * Returns the next character that is not whitespace and does not belong to + * a comment. If the input is exhausted before such a character can be + * found, the null character '\0' is returned. The return value of this + * method is ambiguous for JSON strings that contain the character '\0'. + */ + public char nextClean() throws JSONException { + int nextCleanInt = nextCleanInternal(); + return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; + } + + /** + * Returns the next {@code length} characters of the input. + * + *

The returned string shares its backing character array with this + * tokener's input string. If a reference to the returned string may be held + * indefinitely, you should use {@code new String(result)} to copy it first + * to avoid memory leaks. + * + * @throws JSONException if the remaining input is not long enough to + * satisfy this request. + */ + public String next(int length) throws JSONException { + if (pos + length > in.length()) { + throw syntaxError(length + " is out of bounds"); + } + String result = in.substring(pos, pos + length); + pos += length; + return result; + } + + /** + * Returns the {@link String#trim trimmed} string holding the characters up + * to but not including the first of: + *

    + *
  • any character in {@code excluded} + *
  • a newline character '\n' + *
  • a carriage return '\r' + *
+ * + *

The returned string shares its backing character array with this + * tokener's input string. If a reference to the returned string may be held + * indefinitely, you should use {@code new String(result)} to copy it first + * to avoid memory leaks. + * + * @return a possibly-empty string + */ + public String nextTo(String excluded) { + if (excluded == null) { + throw new NullPointerException("excluded == null"); + } + return nextToInternal(excluded).trim(); + } + + /** + * Equivalent to {@code nextTo(String.valueOf(excluded))}. + */ + public String nextTo(char excluded) { + return nextToInternal(String.valueOf(excluded)).trim(); + } + + /** + * Advances past all input up to and including the next occurrence of + * {@code thru}. If the remaining input doesn't contain {@code thru}, the + * input is exhausted. + */ + public void skipPast(String thru) { + int thruStart = in.indexOf(thru, pos); + pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); + } + + /** + * Advances past all input up to but not including the next occurrence of + * {@code to}. If the remaining input doesn't contain {@code to}, the input + * is unchanged. + */ + public char skipTo(char to) { + int index = in.indexOf(to, pos); + if (index != -1) { + pos = index; + return to; + } else { + return '\0'; + } + } + + /** + * Unreads the most recent character of input. If no input characters have + * been read, the input is unchanged. + */ + public void back() { + if (--pos == -1) { + pos = 0; + } + } +} diff --git a/AndroidCompat/src/main/java/org/json/package-info.java b/AndroidCompat/src/main/java/org/json/package-info.java new file mode 100644 index 00000000..d38e7382 --- /dev/null +++ b/AndroidCompat/src/main/java/org/json/package-info.java @@ -0,0 +1,4 @@ +/** + * Package copied and modified from: https://android.googlesource.com/platform/libcore/+/master/json + */ +package org.json; \ No newline at end of file diff --git a/AndroidCompat/src/main/java/rx.android.schedulers/AndroidSchedulers.kt b/AndroidCompat/src/main/java/rx.android.schedulers/AndroidSchedulers.kt new file mode 100644 index 00000000..1512a66e --- /dev/null +++ b/AndroidCompat/src/main/java/rx.android.schedulers/AndroidSchedulers.kt @@ -0,0 +1,16 @@ +package rx.android.schedulers + +import rx.internal.schedulers.ImmediateScheduler + +class AndroidSchedulers { + companion object { + val mainThreadScheduler by lazy { + ImmediateScheduler.INSTANCE!! + } + + /** + * Simulated main thread scheduler + */ + fun mainThread() = mainThreadScheduler + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt new file mode 100644 index 00000000..35c1c038 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt @@ -0,0 +1,17 @@ +package xyz.nulldev.androidcompat + +import android.app.Application +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance +import xyz.nulldev.androidcompat.androidimpl.CustomContext + +class AndroidCompat { + + val context: CustomContext by DI.global.instance() + + fun startApp(application: Application) { + application.attach(context) + application.onCreate() + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt new file mode 100644 index 00000000..43572fec --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt @@ -0,0 +1,30 @@ +package xyz.nulldev.androidcompat + +import org.kodein.di.DI +import org.kodein.di.conf.global +import xyz.nulldev.androidcompat.bytecode.ModApplier +import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule +import xyz.nulldev.androidcompat.config.FilesConfigModule +import xyz.nulldev.androidcompat.config.SystemConfigModule +import xyz.nulldev.ts.config.GlobalConfigManager + +/** + * Initializes the Android compatibility module + */ +class AndroidCompatInitializer { + + val modApplier by lazy { ModApplier() } + + fun init() { + modApplier.apply() + + DI.global.addImport(AndroidCompatModule().create()) + + //Register config modules + GlobalConfigManager.registerModules( + FilesConfigModule.register(GlobalConfigManager.config), + ApplicationInfoConfigModule.register(GlobalConfigManager.config), + SystemConfigModule.register(GlobalConfigManager.config) + ) + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt new file mode 100644 index 00000000..7283a9de --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt @@ -0,0 +1,39 @@ +package xyz.nulldev.androidcompat + +import android.content.Context +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.conf.global +import org.kodein.di.instance +import org.kodein.di.singleton +import xyz.nulldev.androidcompat.androidimpl.CustomContext +import xyz.nulldev.androidcompat.androidimpl.FakePackageManager +import xyz.nulldev.androidcompat.info.ApplicationInfoImpl +import xyz.nulldev.androidcompat.io.AndroidFiles +import xyz.nulldev.androidcompat.pm.PackageController +import xyz.nulldev.androidcompat.service.ServiceSupport + +/** + * AndroidCompatModule + */ + +class AndroidCompatModule { + fun create() = DI.Module("AndroidCompat") { + bind() with singleton { AndroidFiles() } + + bind() with singleton { ApplicationInfoImpl() } + + bind() with singleton { ServiceSupport() } + + bind() with singleton { FakePackageManager() } + + bind() with singleton { PackageController() } + + //Context + bind() with singleton { CustomContext() } + bind() with singleton { + val context: Context by DI.global.instance() + context + } + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java new file mode 100644 index 00000000..cdbb3ef0 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -0,0 +1,738 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.androidimpl; + +import android.content.*; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.os.*; +import android.view.Display; +import android.view.DisplayAdjustments; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.kodein.di.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; +import xyz.nulldev.androidcompat.io.AndroidFiles; +import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences; +import xyz.nulldev.androidcompat.service.ServiceSupport; +import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; + +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Custom context implementation. + * + * TODO Deal with packagemanager for extension sources + */ +public class CustomContext extends Context implements DIAware { + private DI kodein; + public CustomContext() { + this(KodeinGlobalHelper.kodein()); + } + + public CustomContext(DI kodein) { + this.kodein = kodein; + + //Init configs + androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class, getDi()); + applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class, getDi()); + serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class, getDi()); + fakePackageManager = KodeinGlobalHelper.instance(FakePackageManager.class, getDi()); + } + + @NotNull + @Override + public DI getDi() { + return kodein; + } + + private AndroidFiles androidFiles; + private ApplicationInfoImpl applicationInfo; + private ServiceSupport serviceSupport; + private FakePackageManager fakePackageManager; + + private Logger logger = LoggerFactory.getLogger(CustomContext.class); + + private Map serviceMap = new HashMap<>(); + { + serviceMap.put(Context.CONNECTIVITY_SERVICE, ConnectivityManager.INSTANCE); + serviceMap.put(Context.POWER_SERVICE, PowerManager.INSTANCE); + } + + @Override + public AssetManager getAssets() { + return null; + } + + @Override + public Resources getResources() { + return null; + } + + @Override + public PackageManager getPackageManager() { + return fakePackageManager; + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + @Override + public Looper getMainLooper() { + return null; + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public void setTheme(int i) { + + } + + @Override + public Resources.Theme getTheme() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return this.getClass().getClassLoader(); + } + + @Override + public String getPackageName() { + return null; + } + + @Override + public String getBasePackageName() { + return null; + } + + @Override + public String getOpPackageName() { + return null; + } + + @Override + public ApplicationInfo getApplicationInfo() { + return applicationInfo; + } + + @Override + public String getPackageResourcePath() { + return null; + } + + @Override + public String getPackageCodePath() { + return null; + } + + /** Fake shared prefs! **/ + private Map prefs = new HashMap<>(); //Cache + + private File sharedPrefsFileFromString(String s) { + return new File(androidFiles.getPrefsDir(), s + ".json"); + } + + @Override + public synchronized SharedPreferences getSharedPreferences(String s, int i) { + SharedPreferences preferences = prefs.get(s); + //Create new shared preferences if one does not exist + if(preferences == null) { + preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); + prefs.put(s, preferences); + } + return preferences; + } + + public SharedPreferences getSharedPreferences(File file, int mode) { + return new JsonSharedPreferences(file); + } + + @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + return false; + } + + @Override + public boolean deleteSharedPreferences(String name) { + prefs.remove(name); + return sharedPrefsFileFromString(name).delete(); + } + + @Override + public FileInputStream openFileInput(String s) throws FileNotFoundException { + return null; + } + + @Override + public FileOutputStream openFileOutput(String s, int i) throws FileNotFoundException { + return null; + } + + @Override + public boolean deleteFile(String s) { + return false; + } + + @Override + public File getFileStreamPath(String s) { + return null; + } + + @Override + public File getSharedPreferencesPath(String name) { + return null; + } + + @Override + public File getDataDir() { + return androidFiles.getDataDir(); + } + + @Override + public File getFilesDir() { + return androidFiles.getFilesDir(); + } + + @Override + public File getNoBackupFilesDir() { + return androidFiles.getNoBackupFilesDir(); + } + + @Override + public File getExternalFilesDir(String s) { + return androidFiles.getExternalFilesDirs().get(0); + } + + @Override + public File[] getExternalFilesDirs(String s) { + List files = androidFiles.getExternalFilesDirs(); + return files.toArray(new File[files.size()]); + } + + @Override + public File getObbDir() { + return androidFiles.getObbDirs().get(0); + } + + @Override + public File[] getObbDirs() { + List files = androidFiles.getObbDirs(); + return files.toArray(new File[files.size()]); + } + + @Override + public File getCacheDir() { + return androidFiles.getCacheDir(); + } + + @Override + public File getCodeCacheDir() { + return androidFiles.getCodeCacheDir(); + } + + @Override + public File getExternalCacheDir() { + return androidFiles.getExternalCacheDirs().get(0); + } + + @Override + public File[] getExternalCacheDirs() { + List files = androidFiles.getExternalCacheDirs(); + return files.toArray(new File[files.size()]); + } + + @Override + public File[] getExternalMediaDirs() { + List files = androidFiles.getExternalMediaDirs(); + return files.toArray(new File[files.size()]); + } + + @Override + public String[] fileList() { + return new String[0]; + } + + @Override + public File getDir(String s, int i) { + return null; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory) { + return openOrCreateDatabase(s, i, cursorFactory, null); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory, DatabaseErrorHandler databaseErrorHandler) { + return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(s).getAbsolutePath(), cursorFactory, databaseErrorHandler); + } + + @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + return false; + } + + @Override + public boolean deleteDatabase(String s) { + return false; + } + + @Override + public File getDatabasePath(String s) { + return new File(new File(androidFiles.getRootDir(), "databases"), s); + } + + @Override + public String[] databaseList() { + return new String[0]; + } + + @Override + public Drawable getWallpaper() { + return null; + } + + @Override + public Drawable peekWallpaper() { + return null; + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + return 0; + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + return 0; + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + + } + + @Override + public void setWallpaper(InputStream inputStream) throws IOException { + + } + + @Override + public void clearWallpaper() throws IOException { + + } + + @Override + public void startActivity(Intent intent) { + + } + + @Override + public void startActivity(Intent intent, Bundle bundle) { + + } + + @Override + public void startActivities(Intent[] intents) { + + } + + @Override + public void startActivities(Intent[] intents, Bundle bundle) { + + } + + @Override + public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2) throws IntentSender.SendIntentException { + + } + + @Override + public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2, Bundle bundle) throws IntentSender.SendIntentException { + + } + + @Override + public void sendBroadcast(Intent intent) { + + } + + @Override + public void sendBroadcast(Intent intent, String s) { + + } + + @Override + public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) { + + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { + + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission, int appOp) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String s) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String s, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s1, Bundle bundle) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle userHandle) { + + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String s) { + + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp) { + + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle userHandle, String s, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s1, Bundle bundle) { + + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendStickyBroadcast(Intent intent) { + + } + + @Override + public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s, Bundle bundle) { + + } + + @Override + public void removeStickyBroadcast(Intent intent) { + + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle userHandle) { + + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle userHandle, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s, Bundle bundle) { + + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle userHandle) { + + } + + @Override + public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter) { + return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, String s, Handler handler) { + return null; + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { + return null; + } + + public void unregisterReceiver(BroadcastReceiver broadcastReceiver) { + + } + + @Override + public ComponentName startService(Intent intent) { + serviceSupport.startService(this, intent); + return intent.getComponent(); + } + + @Override + public boolean stopService(Intent intent) { + serviceSupport.stopService(this, intent); + return true; + } + + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { + logger.warn("An attempt was made to start the service: '{}' as another user! Since multiple user services are currently not supported, the service will be started as the current user!", service.getComponent().getClassName()); + return startService(service); + } + + @Override + public boolean stopServiceAsUser(Intent service, UserHandle user) { + logger.warn("An attempt was made to stop the service: '{}' as another user! Since multiple user services are currently not supported, the service will be stopped as the current user!", service.getComponent().getClassName()); + return stopService(service); + } + + @Override + public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) { + return false; + } + + @Override + public void unbindService(ServiceConnection serviceConnection) { + + } + + @Override + public boolean startInstrumentation(ComponentName componentName, String s, Bundle bundle) { + return false; + } + + @Override + public Object getSystemService(String s) { + return serviceMap.get(s); + } + + @Override + public String getSystemServiceName(Class aClass) { + return null; + } + + @Override + public int checkPermission(String s, int i, int i1) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkCallingPermission(String s) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkCallingOrSelfPermission(String s) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkSelfPermission(String s) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public void enforcePermission(String s, int i, int i1, String s1) { + + } + + @Override + public void enforceCallingPermission(String s, String s1) { + + } + + @Override + public void enforceCallingOrSelfPermission(String s, String s1) { + + } + + @Override + public void grantUriPermission(String s, Uri uri, int i) { + + } + + @Override + public void revokeUriPermission(Uri uri, int i) { + + } + + @Override + public int checkUriPermission(Uri uri, int i, int i1, int i2) { + return 0; + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + return 0; + } + + @Override + public int checkCallingUriPermission(Uri uri, int i) { + return 0; + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int i) { + return 0; + } + + @Override + public int checkUriPermission(Uri uri, String s, String s1, int i, int i1, int i2) { + return 0; + } + + @Override + public void enforceUriPermission(Uri uri, int i, int i1, int i2, String s) { + + } + + @Override + public void enforceCallingUriPermission(Uri uri, int i, String s) { + + } + + @Override + public void enforceCallingOrSelfUriPermission(Uri uri, int i, String s) { + + } + + @Override + public void enforceUriPermission(Uri uri, String s, String s1, int i, int i1, int i2, String s2) { + + } + + @Override + public Context createPackageContext(String s, int i) throws PackageManager.NameNotFoundException { + return null; + } + + @Override + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws PackageManager.NameNotFoundException { + return null; + } + + @Override + public Context createApplicationContext(ApplicationInfo application, int flags) throws PackageManager.NameNotFoundException { + return null; + } + + @Override + public int getUserId() { + return 0; + } + + @Override + public Context createConfigurationContext(Configuration configuration) { + return null; + } + + @Override + public Context createDisplayContext(Display display) { + return null; + } + + @Override + public Context createDeviceProtectedStorageContext() { + return null; + } + + @Override + public Context createCredentialProtectedStorageContext() { + return null; + } + + @Override + public DisplayAdjustments getDisplayAdjustments(int displayId) { + return null; + } + + @Override + public Display getDisplay() { + return null; + } + + @Override + public boolean isDeviceProtectedStorage() { + return false; + } + + @Override + public boolean isCredentialProtectedStorage() { + return false; + } + + @NotNull + @Override + public DIContext getDiContext() { + return getDi().getDiContext(); + } + + @Nullable + @Override + public DITrigger getDiTrigger() { + return null; + } +} + + diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/FakePackageManager.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/FakePackageManager.java new file mode 100644 index 00000000..1f07a0a9 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/FakePackageManager.java @@ -0,0 +1,849 @@ +package xyz.nulldev.androidcompat.androidimpl; + +import android.app.PackageInstallObserver; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.*; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import kotlin.NotImplementedError; +import xyz.nulldev.androidcompat.pm.InstalledPackage; +import xyz.nulldev.androidcompat.pm.PackageController; +import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class FakePackageManager extends PackageManager { + private PackageController controller = KodeinGlobalHelper.instance(PackageController.class); + + @Override + public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { + InstalledPackage installedPackage = controller.findPackage(packageName); + + if(installedPackage == null) throw new NameNotFoundException(); + + return installedPackage.getInfo(); + } + + @Override + public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) throws NameNotFoundException { + return getPackageInfo(packageName, userId); + } + + @Override + public String[] currentToCanonicalPackageNames(String[] names) { + throw new NotImplementedError(); + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] names) { + throw new NotImplementedError(); + } + + @Override + public Intent getLaunchIntentForPackage(String packageName) { + throw new NotImplementedError(); + } + + @Override + public Intent getLeanbackLaunchIntentForPackage(String packageName) { + throw new NotImplementedError(); + } + + @Override + public int[] getPackageGids(String packageName) throws NameNotFoundException { + return new int[0]; + } + + @Override + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { + return new int[0]; + } + + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(String packageName, int flags, int userId) throws NameNotFoundException { + return 0; + } + + @Override + public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public List queryPermissionsByGroup(String group, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public boolean isPermissionReviewModeEnabled() { + return false; + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public List getAllPermissionGroups(int flags) { + throw new NotImplementedError(); + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException { + return getPackageInfo(packageName, flags).applicationInfo; + } + + @Override + public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId) throws NameNotFoundException { + return getPackageInfoAsUser(packageName, flags, userId).applicationInfo; + } + + @Override + public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public ServiceInfo getServiceInfo(ComponentName component, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + @Override + public ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException { + throw new NotImplementedError(); + } + + //TODO Return loaded extensions + @Override + public List getInstalledPackages(int flags) { + return controller.listInstalled().stream().map(InstalledPackage::getInfo).collect(Collectors.toList()); + } + + @Override + public List getPackagesHoldingPermissions(String[] permissions, int flags) { + throw new NotImplementedError(); + } + + @Override + public List getInstalledPackagesAsUser(int flags, int userId) { + throw new NotImplementedError(); + } + + @Override + public int checkPermission(String permName, String pkgName) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public boolean isPermissionRevokedByPolicy(String permName, String pkgName) { + return false; + } + + @Override + public String getPermissionControllerPackageName() { + return null; + } + + @Override + public boolean addPermission(PermissionInfo info) { + return true; + } + + @Override + public boolean addPermissionAsync(PermissionInfo info) { + return true; + } + + @Override + public void removePermission(String name) { + } + + @Override + public void grantRuntimePermission(String packageName, String permissionName, UserHandle user) { + } + + @Override + public void revokeRuntimePermission(String packageName, String permissionName, UserHandle user) { + } + + @Override + public int getPermissionFlags(String permissionName, String packageName, UserHandle user) { + return 0; + } + + @Override + public void updatePermissionFlags(String permissionName, String packageName, int flagMask, int flagValues, UserHandle user) { + throw new NotImplementedError(); + } + + @Override + public boolean shouldShowRequestPermissionRationale(String permission) { + return true; + } + + @Override + public int checkSignatures(String pkg1, String pkg2) { + return 0; + } + + @Override + public int checkSignatures(int uid1, int uid2) { + return 0; + } + + @Override + public String[] getPackagesForUid(int uid) { + return new String[0]; + } + + @Override + public String getNameForUid(int uid) { + return null; + } + + @Override + public String[] getNamesForUids(int[] uids) { + return new String[0]; + } + + @Override + public int getUidForSharedUser(String sharedUserName) throws NameNotFoundException { + return 0; + } + + @Override + public List getInstalledApplications(int flags) { + return getInstalledPackages(flags).stream().map((it) -> it.applicationInfo).collect(Collectors.toList()); + } + + @Override + public List getInstalledApplicationsAsUser(int flags, int userId) { + return getInstalledApplications(flags); + } + + @Override + public List getInstantApps() { + return new ArrayList<>(); + } + + @Override + public Drawable getInstantAppIcon(String packageName) { + throw new NotImplementedError(); + } + + @Override + public boolean isInstantApp() { + return false; + } + + @Override + public boolean isInstantApp(String packageName) { + return false; + } + + @Override + public int getInstantAppCookieMaxBytes() { + return 0; + } + + @Override + public int getInstantAppCookieMaxSize() { + return 0; + } + + @Override + public byte[] getInstantAppCookie() { + return new byte[0]; + } + + @Override + public void clearInstantAppCookie() { + + } + + @Override + public void updateInstantAppCookie(byte[] cookie) { + + } + + @Override + public boolean setInstantAppCookie(byte[] cookie) { + return false; + } + + @Override + public String[] getSystemSharedLibraryNames() { + return new String[0]; + } + + @Override + public List getSharedLibraries(int flags) { + return null; + } + + @Override + public List getSharedLibrariesAsUser(int flags, int userId) { + return null; + } + + @Override + public String getServicesSystemSharedLibraryPackageName() { + return null; + } + + @Override + public String getSharedSystemSharedLibraryPackageName() { + return null; + } + + @Override + public ChangedPackages getChangedPackages(int sequenceNumber) { + return null; + } + + @Override + public FeatureInfo[] getSystemAvailableFeatures() { + return new FeatureInfo[0]; + } + + @Override + public boolean hasSystemFeature(String name) { + return false; + } + + @Override + public boolean hasSystemFeature(String name, int version) { + return false; + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + return null; + } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + return null; + } + + @Override + public List queryIntentActivities(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { + return null; + } + + @Override + public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) { + return null; + } + + @Override + public List queryBroadcastReceivers(Intent intent, int flags) { + return null; + } + + @Override + public List queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) { + return null; + } + + @Override + public ResolveInfo resolveService(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentServices(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentServicesAsUser(Intent intent, int flags, int userId) { + return null; + } + + @Override + public List queryIntentContentProvidersAsUser(Intent intent, int flags, int userId) { + return null; + } + + @Override + public List queryIntentContentProviders(Intent intent, int flags) { + return null; + } + + @Override + public ProviderInfo resolveContentProvider(String name, int flags) { + return null; + } + + @Override + public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) { + return null; + } + + @Override + public List queryContentProviders(String processName, int uid, int flags) { + return null; + } + + @Override + public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) throws NameNotFoundException { + return null; + } + + @Override + public List queryInstrumentation(String targetPackage, int flags) { + return null; + } + + @Override + public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public Drawable getActivityIcon(ComponentName activityName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getActivityIcon(Intent intent) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getActivityBanner(ComponentName activityName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getActivityBanner(Intent intent) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getDefaultActivityIcon() { + return null; + } + + @Override + public Drawable getApplicationIcon(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationIcon(String packageName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getApplicationBanner(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationBanner(String packageName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getActivityLogo(ComponentName activityName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getActivityLogo(Intent intent) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getApplicationLogo(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationLogo(String packageName) throws NameNotFoundException { + return null; + } + + @Override + public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) { + return null; + } + + @Override + public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) { + return null; + } + + @Override + public Drawable getUserBadgeForDensity(UserHandle user, int density) { + return null; + } + + @Override + public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) { + return null; + } + + @Override + public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { + return null; + } + + @Override + public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public CharSequence getApplicationLabel(ApplicationInfo info) { + return info.nonLocalizedLabel; + } + + @Override + public Resources getResourcesForActivity(ComponentName activityName) throws NameNotFoundException { + return null; + } + + @Override + public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException { + return null; + } + + @Override + public Resources getResourcesForApplication(String appPackageName) throws NameNotFoundException { + return null; + } + + @Override + public Resources getResourcesForApplicationAsUser(String appPackageName, int userId) throws NameNotFoundException { + return null; + } + + @Override + public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName) { + + } + + @Override + public int installExistingPackage(String packageName) throws NameNotFoundException { + return 0; + } + + @Override + public int installExistingPackage(String packageName, int installReason) throws NameNotFoundException { + return 0; + } + + @Override + public int installExistingPackageAsUser(String packageName, int userId) throws NameNotFoundException { + return 0; + } + + @Override + public void verifyPendingInstall(int id, int verificationCode) { + + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) { + + } + + @Override + public void verifyIntentFilter(int verificationId, int verificationCode, List failedDomains) { + + } + + @Override + public int getIntentVerificationStatusAsUser(String packageName, int userId) { + return 0; + } + + @Override + public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) { + return false; + } + + @Override + public List getIntentFilterVerifications(String packageName) { + return null; + } + + @Override + public List getAllIntentFilters(String packageName) { + return null; + } + + @Override + public String getDefaultBrowserPackageNameAsUser(int userId) { + return null; + } + + @Override + public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) { + return false; + } + + @Override + public void setInstallerPackageName(String targetPackage, String installerPackageName) { + + } + + @Override + public void setUpdateAvailable(String packageName, boolean updateAvaialble) { + + } + + @Override + public String getInstallerPackageName(String packageName) { + return null; + } + + @Override + public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) { + + } + + @Override + public void addPackageToPreferred(String packageName) { + + } + + @Override + public void removePackageFromPreferred(String packageName) { + + } + + @Override + public List getPreferredPackages(int flags) { + return null; + } + + @Override + public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + + } + + @Override + public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + + } + + @Override + public int getPreferredActivities(List outFilters, List outActivities, String packageName) { + return 0; + } + + @Override + public ComponentName getHomeActivities(List outActivities) { + return null; + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { + + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName) { + return 0; + } + + @Override + public void setApplicationEnabledSetting(String packageName, int newState, int flags) { + + } + + @Override + public int getApplicationEnabledSetting(String packageName) { + return 0; + } + + @Override + public void flushPackageRestrictionsAsUser(int userId) { + + } + + @Override + public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, UserHandle userHandle) { + return false; + } + + @Override + public boolean getApplicationHiddenSettingAsUser(String packageName, UserHandle userHandle) { + return false; + } + + @Override + public boolean isSafeMode() { + return false; + } + + @Override + public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + + } + + @Override + public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + + } + + @Override + public KeySet getKeySetByAlias(String packageName, String alias) { + return null; + } + + @Override + public KeySet getSigningKeySet(String packageName) { + return null; + } + + @Override + public boolean isSignedBy(String packageName, KeySet ks) { + return false; + } + + @Override + public boolean isSignedByExactly(String packageName, KeySet ks) { + return false; + } + + @Override + public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, int userId) { + return new String[0]; + } + + @Override + public boolean isPackageSuspendedForUser(String packageName, int userId) { + return false; + } + + @Override + public int getMoveStatus(int moveId) { + return 0; + } + + @Override + public void registerMoveCallback(MoveCallback callback, Handler handler) { + + } + + @Override + public void unregisterMoveCallback(MoveCallback callback) { + + } + + @Override + public boolean isUpgrade() { + return false; + } + + @Override + public PackageInstaller getPackageInstaller() { + return null; + } + + @Override + public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId, int flags) { + + } + + @Override + public void clearCrossProfileIntentFilters(int sourceUserId) { + + } + + @Override + public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { + return null; + } + + @Override + public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { + return null; + } + + @Override + public boolean isPackageAvailable(String packageName) { + return false; + } + + @Override + public int getInstallReason(String packageName, UserHandle user) { + return 0; + } + + @Override + public boolean canRequestPackageInstalls() { + return false; + } + + @Override + public ComponentName getInstantAppResolverSettingsComponent() { + return null; + } + + @Override + public ComponentName getInstantAppInstallerComponent() { + return null; + } + + @Override + public String getInstantAppAndroidId(String packageName, UserHandle user) { + return null; + } + + @Override + public void registerDexModule(String dexModulePath, DexModuleRegisterCallback callback) { + + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/bytecode/ModApplier.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/bytecode/ModApplier.kt new file mode 100644 index 00000000..535146cf --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/bytecode/ModApplier.kt @@ -0,0 +1,22 @@ +package xyz.nulldev.androidcompat.bytecode + +import javassist.CtClass +import mu.KotlinLogging + +/** + * Applies Javassist modifications + */ + +class ModApplier { + + val logger = KotlinLogging.logger {} + + fun apply() { + logger.info { "Applying Javassist mods..." } + val modifiedClasses = mutableListOf() + + modifiedClasses.forEach { + it.toClass() + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt new file mode 100644 index 00000000..932b2b9a --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt @@ -0,0 +1,18 @@ +package xyz.nulldev.androidcompat.config + +import com.typesafe.config.Config +import xyz.nulldev.ts.config.ConfigModule + +/** + * Application info config. + */ + +class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) { + val packageName = config.getString("packageName")!! + val debug = config.getBoolean("debug") + + companion object { + fun register(config: Config) + = ApplicationInfoConfigModule(config.getConfig("android.app")) + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt new file mode 100644 index 00000000..c2ee7b71 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt @@ -0,0 +1,33 @@ +package xyz.nulldev.androidcompat.config + +import com.typesafe.config.Config +import xyz.nulldev.ts.config.ConfigModule + +/** + * Files configuration modules. Specifies where to store the Android files. + */ + +class FilesConfigModule(config: Config) : ConfigModule(config) { + val dataDir = config.getString("dataDir")!! + val filesDir = config.getString("filesDir")!! + val noBackupFilesDir = config.getString("noBackupFilesDir")!! + val externalFilesDirs: MutableList = config.getStringList("externalFilesDirs")!! + val obbDirs: MutableList = config.getStringList("obbDirs")!! + val cacheDir = config.getString("cacheDir")!! + val codeCacheDir = config.getString("codeCacheDir")!! + val externalCacheDirs: MutableList = config.getStringList("externalCacheDirs")!! + val externalMediaDirs: MutableList = config.getStringList("externalMediaDirs")!! + val rootDir = config.getString("rootDir")!! + val externalStorageDir = config.getString("externalStorageDir")!! + val downloadCacheDir = config.getString("downloadCacheDir")!! + val databasesDir = config.getString("databasesDir")!! + + val prefsDir = config.getString("prefsDir")!! + + val packageDir = config.getString("packageDir")!! + + companion object { + fun register(config: Config) + = FilesConfigModule(config.getConfig("android.files")) + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt new file mode 100644 index 00000000..a8373050 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt @@ -0,0 +1,21 @@ +package xyz.nulldev.androidcompat.config + +import com.typesafe.config.Config +import xyz.nulldev.ts.config.ConfigModule + +class SystemConfigModule(val config: Config) : ConfigModule(config) { + val isDebuggable = config.getBoolean("isDebuggable") + + val propertyPrefix = "properties." + + fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!! + fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property") + fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property") + fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property") + fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property") + + companion object { + fun register(config: Config) + = SystemConfigModule(config.getConfig("android.system")) + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt new file mode 100644 index 00000000..1bd14751 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt @@ -0,0 +1,841 @@ +package xyz.nulldev.androidcompat.db + +import java.io.InputStream +import java.io.Reader +import java.math.BigDecimal +import java.net.URL +import java.sql.* +import java.sql.Array +import java.sql.Date +import java.util.* + +class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { + + private val cachedContent = mutableListOf() + private val columnCache = mutableMapOf() + private var lastReturnWasNull = false + private var cursor = 0 + var resultSetLength = 0 + + val parentMetadata = parent.metaData + val columnCount = parentMetadata.columnCount + val columnLabels = (1 .. columnCount).map { + parentMetadata.getColumnLabel(it) + }.toTypedArray() + + init { + val columnCount = columnCount + + // TODO + // Profiling reveals that this is a bottleneck (average ms for this call is: 48ms) + // How can we optimize this? + // We need to fill the cache as the set is loaded + + //Fill cache + while(parent.next()) { + cachedContent += ResultSetEntry().apply { + for(i in 1 .. columnCount) + data += parent.getObject(i) + } + resultSetLength++ + } + } + + private fun notImplemented(): Nothing { + throw UnsupportedOperationException("This class currently does not support this operation!") + } + + private fun cursorValid(): Boolean { + return isAfterLast || isBeforeFirst + } + + private fun internalMove(row: Int) { + if(cursor < 0) cursor = 0 + else if(cursor > resultSetLength + 1) cursor = resultSetLength + 1 + else cursor = row + } + + private fun obj(column: Int): Any? { + val obj = cachedContent[cursor - 1].data[column - 1] + lastReturnWasNull = obj == null + return obj + } + + private fun obj(column: String?): Any? { + return obj(cachedFindColumn(column)) + } + + private fun cachedFindColumn(column: String?) + = columnCache.getOrPut(column!!, { + findColumn(column) + }) + + override fun getNClob(columnIndex: Int): NClob { + return obj(columnIndex) as NClob + } + + override fun getNClob(columnLabel: String?): NClob { + return obj(columnLabel) as NClob + } + + override fun updateNString(columnIndex: Int, nString: String?) { + notImplemented() + } + + override fun updateNString(columnLabel: String?, nString: String?) { + notImplemented() + } + + override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Int) { + notImplemented() + } + + override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Int) { + notImplemented() + } + + override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Long) { + notImplemented() + } + + override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Long) { + notImplemented() + } + + override fun updateBinaryStream(columnIndex: Int, x: InputStream?) { + notImplemented() + } + + override fun updateBinaryStream(columnLabel: String?, x: InputStream?) { + notImplemented() + } + + override fun updateTimestamp(columnIndex: Int, x: Timestamp?) { + notImplemented() + } + + override fun updateTimestamp(columnLabel: String?, x: Timestamp?) { + notImplemented() + } + + override fun updateNCharacterStream(columnIndex: Int, x: Reader?, length: Long) { + notImplemented() + } + + override fun updateNCharacterStream(columnLabel: String?, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateNCharacterStream(columnIndex: Int, x: Reader?) { + notImplemented() + } + + override fun updateNCharacterStream(columnLabel: String?, reader: Reader?) { + notImplemented() + } + + override fun updateInt(columnIndex: Int, x: Int) { + notImplemented() + } + + override fun updateInt(columnLabel: String?, x: Int) { + notImplemented() + } + + override fun moveToInsertRow() { + notImplemented() + } + + override fun getDate(columnIndex: Int): Date { + //TODO Maybe? + notImplemented() + } + + override fun getDate(columnLabel: String?): Date { + //TODO Maybe? + notImplemented() + } + + override fun getDate(columnIndex: Int, cal: Calendar?): Date { + //TODO Maybe? + notImplemented() + } + + override fun getDate(columnLabel: String?, cal: Calendar?): Date { + //TODO Maybe? + notImplemented() + } + + override fun beforeFirst() { + //TODO Maybe? + notImplemented() + } + + override fun updateFloat(columnIndex: Int, x: Float) { + notImplemented() + } + + override fun updateFloat(columnLabel: String?, x: Float) { + notImplemented() + } + + override fun getBoolean(columnIndex: Int): Boolean { + return obj(columnIndex) as Boolean + } + + override fun getBoolean(columnLabel: String?): Boolean { + return obj(columnLabel) as Boolean + } + + override fun isFirst(): Boolean { + return cursor - 1 < resultSetLength + } + + override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal { + //TODO Maybe? + notImplemented() + } + + override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal { + //TODO Maybe? + notImplemented() + } + + override fun getBigDecimal(columnIndex: Int): BigDecimal { + return obj(columnIndex) as BigDecimal + } + + override fun getBigDecimal(columnLabel: String?): BigDecimal { + return obj(columnLabel) as BigDecimal + } + + override fun updateBytes(columnIndex: Int, x: ByteArray?) { + notImplemented() + } + + override fun updateBytes(columnLabel: String?, x: ByteArray?) { + notImplemented() + } + + override fun isLast(): Boolean { + return cursor == resultSetLength + } + + override fun insertRow() { + notImplemented() + } + + override fun getTime(columnIndex: Int): Time { + //TODO Maybe? + notImplemented() + } + + override fun getTime(columnLabel: String?): Time { + //TODO Maybe? + notImplemented() + } + + override fun getTime(columnIndex: Int, cal: Calendar?): Time { + //TODO Maybe? + notImplemented() + } + + override fun getTime(columnLabel: String?, cal: Calendar?): Time { + //TODO Maybe? + notImplemented() + } + + override fun rowDeleted() = false + + override fun last(): Boolean { + internalMove(resultSetLength) + return cursorValid() + } + + override fun isAfterLast(): Boolean { + return cursor > resultSetLength + } + + override fun relative(rows: Int): Boolean { + internalMove(cursor + rows) + return cursorValid() + } + + override fun absolute(row: Int): Boolean { + if(row > 0) { + internalMove(row) + } else { + last() + for(i in 1 .. row) + previous() + } + return cursorValid() + } + + override fun getSQLXML(columnIndex: Int): SQLXML? { + //TODO Maybe? + notImplemented() + } + + override fun getSQLXML(columnLabel: String?): SQLXML? { + //TODO Maybe? + notImplemented() + } + + override fun unwrap(iface: Class?): T { + if(thisIsWrapperFor(iface)) + return this as T + else + return parent.unwrap(iface) + } + + override fun next(): Boolean { + internalMove(cursor + 1) + return cursorValid() + } + + override fun getFloat(columnIndex: Int): Float { + return obj(columnIndex) as Float + } + + override fun getFloat(columnLabel: String?): Float { + return obj(columnLabel) as Float + } + + override fun wasNull() = lastReturnWasNull + + override fun getRow(): Int { + return cursor + } + + override fun first(): Boolean { + internalMove(1) + return cursorValid() + } + + override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Int) { + notImplemented() + } + + override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Int) { + notImplemented() + } + + override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Long) { + notImplemented() + } + + override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Long) { + notImplemented() + } + + override fun updateAsciiStream(columnIndex: Int, x: InputStream?) { + notImplemented() + } + + override fun updateAsciiStream(columnLabel: String?, x: InputStream?) { + notImplemented() + } + + override fun getURL(columnIndex: Int): URL { + return obj(columnIndex) as URL + } + + override fun getURL(columnLabel: String?): URL { + return obj(columnLabel) as URL + } + + override fun updateShort(columnIndex: Int, x: Short) { + notImplemented() + } + + override fun updateShort(columnLabel: String?, x: Short) { + notImplemented() + } + + override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE + + override fun updateNClob(columnIndex: Int, nClob: NClob?) { + notImplemented() + } + + override fun updateNClob(columnLabel: String?, nClob: NClob?) { + notImplemented() + } + + override fun updateNClob(columnIndex: Int, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateNClob(columnLabel: String?, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateNClob(columnIndex: Int, reader: Reader?) { + notImplemented() + } + + override fun updateNClob(columnLabel: String?, reader: Reader?) { + notImplemented() + } + + override fun updateRef(columnIndex: Int, x: Ref?) { + notImplemented() + } + + override fun updateRef(columnLabel: String?, x: Ref?) { + notImplemented() + } + + override fun updateObject(columnIndex: Int, x: Any?, scaleOrLength: Int) { + notImplemented() + } + + override fun updateObject(columnIndex: Int, x: Any?) { + notImplemented() + } + + override fun updateObject(columnLabel: String?, x: Any?, scaleOrLength: Int) { + notImplemented() + } + + override fun updateObject(columnLabel: String?, x: Any?) { + notImplemented() + } + + override fun afterLast() { + internalMove(resultSetLength + 1) + } + + override fun updateLong(columnIndex: Int, x: Long) { + notImplemented() + } + + override fun updateLong(columnLabel: String?, x: Long) { + notImplemented() + } + + override fun getBlob(columnIndex: Int): Blob { + //TODO Maybe? + notImplemented() + } + + override fun getBlob(columnLabel: String?): Blob { + //TODO Maybe? + notImplemented() + } + + override fun updateClob(columnIndex: Int, x: Clob?) { + notImplemented() + } + + override fun updateClob(columnLabel: String?, x: Clob?) { + notImplemented() + } + + override fun updateClob(columnIndex: Int, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateClob(columnLabel: String?, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateClob(columnIndex: Int, reader: Reader?) { + notImplemented() + } + + override fun updateClob(columnLabel: String?, reader: Reader?) { + notImplemented() + } + + override fun getByte(columnIndex: Int): Byte { + return obj(columnIndex) as Byte + } + + override fun getByte(columnLabel: String?): Byte { + return obj(columnLabel) as Byte + } + + override fun getString(columnIndex: Int): String? { + return obj(columnIndex) as String? + } + + override fun getString(columnLabel: String?): String? { + return obj(columnLabel) as String? + } + + override fun updateSQLXML(columnIndex: Int, xmlObject: SQLXML?) { + notImplemented() + } + + override fun updateSQLXML(columnLabel: String?, xmlObject: SQLXML?) { + notImplemented() + } + + override fun updateDate(columnIndex: Int, x: Date?) { + notImplemented() + } + + override fun updateDate(columnLabel: String?, x: Date?) { + notImplemented() + } + + override fun getObject(columnIndex: Int): Any? { + return obj(columnIndex) + } + + override fun getObject(columnLabel: String?): Any? { + return obj(columnLabel) + } + + override fun getObject(columnIndex: Int, map: MutableMap>?): Any { + //TODO Maybe? + notImplemented() + } + + override fun getObject(columnLabel: String?, map: MutableMap>?): Any { + //TODO Maybe? + notImplemented() + } + + override fun getObject(columnIndex: Int, type: Class?): T { + return obj(columnIndex) as T + } + + override fun getObject(columnLabel: String?, type: Class?): T { + return obj(columnLabel) as T + } + + override fun previous(): Boolean { + internalMove(cursor - 1) + return cursorValid() + } + + override fun updateDouble(columnIndex: Int, x: Double) { + notImplemented() + } + + override fun updateDouble(columnLabel: String?, x: Double) { + notImplemented() + } + + private fun castToLong(obj: Any?): Long { + if(obj == null) return 0 + else if(obj is Long) return obj + else if(obj is Number) return obj.toLong() + else throw IllegalStateException("Object is not a long!") + } + + override fun getLong(columnIndex: Int): Long { + return castToLong(obj(columnIndex)) + } + + override fun getLong(columnLabel: String?): Long { + return castToLong(obj(columnLabel)) + } + + override fun getClob(columnIndex: Int): Clob { + //TODO Maybe? + notImplemented() + } + + override fun getClob(columnLabel: String?): Clob { + //TODO Maybe? + notImplemented() + } + + override fun updateBlob(columnIndex: Int, x: Blob?) { + notImplemented() + } + + override fun updateBlob(columnLabel: String?, x: Blob?) { + notImplemented() + } + + override fun updateBlob(columnIndex: Int, inputStream: InputStream?, length: Long) { + notImplemented() + } + + override fun updateBlob(columnLabel: String?, inputStream: InputStream?, length: Long) { + notImplemented() + } + + override fun updateBlob(columnIndex: Int, inputStream: InputStream?) { + notImplemented() + } + + override fun updateBlob(columnLabel: String?, inputStream: InputStream?) { + notImplemented() + } + + override fun updateByte(columnIndex: Int, x: Byte) { + notImplemented() + } + + override fun updateByte(columnLabel: String?, x: Byte) { + notImplemented() + } + + override fun updateRow() { + notImplemented() + } + + override fun deleteRow() { + notImplemented() + } + + override fun getNString(columnIndex: Int): String { + return obj(columnIndex) as String + } + + override fun getNString(columnLabel: String?): String { + return obj(columnLabel) as String + } + + override fun getArray(columnIndex: Int): Array { + //TODO Maybe? + notImplemented() + } + + override fun getArray(columnLabel: String?): Array { + //TODO Maybe? + notImplemented() + } + + override fun cancelRowUpdates() { + notImplemented() + } + + override fun updateString(columnIndex: Int, x: String?) { + notImplemented() + } + + override fun updateString(columnLabel: String?, x: String?) { + notImplemented() + } + + override fun setFetchDirection(direction: Int) { + notImplemented() + } + + override fun getCharacterStream(columnIndex: Int): Reader { + return getNCharacterStream(columnIndex) + } + + override fun getCharacterStream(columnLabel: String?): Reader { + return getNCharacterStream(columnLabel) + } + + override fun isBeforeFirst(): Boolean { + return cursor - 1 < resultSetLength + } + + override fun updateBoolean(columnIndex: Int, x: Boolean) { + notImplemented() + } + + override fun updateBoolean(columnLabel: String?, x: Boolean) { + notImplemented() + } + + override fun refreshRow() { + notImplemented() + } + + override fun rowUpdated() = false + + override fun updateBigDecimal(columnIndex: Int, x: BigDecimal?) { + notImplemented() + } + + override fun updateBigDecimal(columnLabel: String?, x: BigDecimal?) { + notImplemented() + } + + override fun getShort(columnIndex: Int): Short { + return obj(columnIndex) as Short + } + + override fun getShort(columnLabel: String?): Short { + return obj(columnLabel) as Short + } + + override fun getAsciiStream(columnIndex: Int): InputStream { + return getBinaryStream(columnIndex) + } + + override fun getAsciiStream(columnLabel: String?): InputStream { + return getBinaryStream(columnLabel) + } + + override fun updateTime(columnIndex: Int, x: Time?) { + notImplemented() + } + + override fun updateTime(columnLabel: String?, x: Time?) { + notImplemented() + } + + override fun getTimestamp(columnIndex: Int): Timestamp { + //TODO Maybe? + notImplemented() + } + + override fun getTimestamp(columnLabel: String?): Timestamp { + //TODO Maybe? + notImplemented() + } + + override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp { + //TODO Maybe? + notImplemented() + } + + override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp { + //TODO Maybe? + notImplemented() + } + + override fun getRef(columnIndex: Int): Ref { + //TODO Maybe? + notImplemented() + } + + override fun getRef(columnLabel: String?): Ref { + //TODO Maybe? + notImplemented() + } + + override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY + + override fun updateRowId(columnIndex: Int, x: RowId?) { + notImplemented() + } + + override fun updateRowId(columnLabel: String?, x: RowId?) { + notImplemented() + } + + override fun getNCharacterStream(columnIndex: Int): Reader { + return getBinaryStream(columnIndex).reader() + } + + override fun getNCharacterStream(columnLabel: String?): Reader { + return getBinaryStream(columnLabel).reader() + } + + override fun updateArray(columnIndex: Int, x: Array?) { + notImplemented() + } + + override fun updateArray(columnLabel: String?, x: Array?) { + notImplemented() + } + + override fun getBytes(columnIndex: Int): ByteArray { + return obj(columnIndex) as ByteArray + } + + override fun getBytes(columnLabel: String?): ByteArray { + return obj(columnLabel) as ByteArray + } + + override fun getDouble(columnIndex: Int): Double { + return obj(columnIndex) as Double + } + + override fun getDouble(columnLabel: String?): Double { + return obj(columnLabel) as Double + } + + override fun getUnicodeStream(columnIndex: Int): InputStream { + return getBinaryStream(columnIndex) + } + + override fun getUnicodeStream(columnLabel: String?): InputStream { + return getBinaryStream(columnLabel) + } + + override fun rowInserted() = false + + private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface) + + override fun isWrapperFor(iface: Class<*>?): Boolean { + return thisIsWrapperFor(iface) || parent.isWrapperFor(iface) + } + + override fun getInt(columnIndex: Int): Int { + return obj(columnIndex) as Int + } + + override fun getInt(columnLabel: String?): Int { + return obj(columnLabel) as Int + } + + override fun updateNull(columnIndex: Int) { + notImplemented() + } + + override fun updateNull(columnLabel: String?) { + notImplemented() + } + + override fun getRowId(columnIndex: Int): RowId { + //TODO Maybe? + notImplemented() + } + + override fun getRowId(columnLabel: String?): RowId { + //TODO Maybe? + notImplemented() + } + + override fun getMetaData(): ResultSetMetaData { + return object : ResultSetMetaData by parentMetadata { + override fun isReadOnly(column: Int) = true + override fun isWritable(column: Int) = false + override fun isDefinitelyWritable(column: Int) = false + override fun getColumnCount() = this@ScrollableResultSet.columnCount + override fun getColumnLabel(column: Int): String { + return columnLabels[column - 1] + } + } + } + + override fun getBinaryStream(columnIndex: Int): InputStream { + return (obj(columnIndex) as ByteArray).inputStream() + } + + override fun getBinaryStream(columnLabel: String?): InputStream { + return (obj(columnLabel) as ByteArray).inputStream() + } + + override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Int) { + notImplemented() + } + + override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Int) { + notImplemented() + } + + override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Long) { + notImplemented() + } + + override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Long) { + notImplemented() + } + + override fun updateCharacterStream(columnIndex: Int, x: Reader?) { + notImplemented() + } + + override fun updateCharacterStream(columnLabel: String?, reader: Reader?) { + notImplemented() + } + + class ResultSetEntry { + val data = mutableListOf() + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/info/ApplicationInfoImpl.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/info/ApplicationInfoImpl.kt new file mode 100644 index 00000000..83c0f1d4 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/info/ApplicationInfoImpl.kt @@ -0,0 +1,22 @@ +package xyz.nulldev.androidcompat.info + +import android.content.pm.ApplicationInfo +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.conf.global +import org.kodein.di.instance +import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule +import xyz.nulldev.ts.config.ConfigManager + +class ApplicationInfoImpl(override val di: DI = DI.global) : ApplicationInfo(), DIAware { + val configManager: ConfigManager by di.instance() + + val appInfoConfig: ApplicationInfoConfigModule + get() = configManager.module() + + val debug: Boolean get() = appInfoConfig.debug + + init { + super.packageName = appInfoConfig.packageName + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt new file mode 100644 index 00000000..ea55b497 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt @@ -0,0 +1,38 @@ +package xyz.nulldev.androidcompat.io + +import xyz.nulldev.androidcompat.config.FilesConfigModule +import xyz.nulldev.ts.config.ConfigManager +import xyz.nulldev.ts.config.GlobalConfigManager +import java.io.File + +/** + * Android file constants. + */ +class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) { + val filesConfig: FilesConfigModule + get() = configManager.module() + + val dataDir: File get() = registerFile(filesConfig.dataDir) + val filesDir: File get() = registerFile(filesConfig.filesDir) + val noBackupFilesDir: File get() = registerFile(filesConfig.noBackupFilesDir) + val externalFilesDirs: List get() = filesConfig.externalFilesDirs.map { registerFile(it) } + val obbDirs: List get() = filesConfig.obbDirs.map { registerFile(it) } + val cacheDir: File get() = registerFile(filesConfig.cacheDir) + val codeCacheDir: File get() = registerFile(filesConfig.codeCacheDir) + val externalCacheDirs: List get() = filesConfig.externalCacheDirs.map { registerFile(it) } + val externalMediaDirs: List get() = filesConfig.externalMediaDirs.map { registerFile(it) } + val rootDir: File get() = registerFile(filesConfig.rootDir) + val externalStorageDir: File get() = registerFile(filesConfig.externalStorageDir) + val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir) + val databasesDir: File get() = registerFile(filesConfig.databasesDir) + + val prefsDir: File get() = registerFile(filesConfig.prefsDir) + + val packagesDir: File get() = registerFile(filesConfig.packageDir) + + fun registerFile(file: String): File { + return File(file).apply { + mkdirs() + } + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java new file mode 100644 index 00000000..9b04c7b0 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java @@ -0,0 +1,385 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.io.sharedprefs; + +import android.content.SharedPreferences; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; + +/** + * JSON implementation of Android's SharedPreferences. + */ +public class JsonSharedPreferences implements SharedPreferences { + + private static final String KEY_TYPE = "t"; + private static final String KEY_VALUE = "v"; + + private Map prefs = new HashMap<>(); //In-memory preference values + private List listeners = new ArrayList<>(); //Change listeners + private File file; //Where the values should be stored + + private Logger logger = LoggerFactory.getLogger(JsonSharedPreferences.class); + + /** + * Create a SharedPreference instance from a file + * @param file The file to load the prefs from, use null to create an empty SharedPreferences instance. + */ + public JsonSharedPreferences(File file) { + this.file = file; + //Load previous values if they exist + if(file != null) { + if(file.exists()) { + try { + loadFromString(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)); + } catch (IOException e) { + logger.error("Failed to read shared prefs from String!", e); + } + } + } + } + + /** + * Load the SharedPreferences from a String. + * @param string The String to load the prefs from. + */ + public synchronized void loadFromString(String string) { + try { + JSONObject jsonObject = new JSONObject(string); + //Changes are need to be applied atomically so we temporarily store all changes in a separate map + Map tempMap = new HashMap<>(); + //Loop through all preference objects in JSON + for (String key : jsonObject.keySet()) { + JSONObject object = jsonObject.getJSONObject(key); + //Load the object's Java type + String typeString = object.getString(KEY_TYPE); + PrefType type = PrefType.valueOf(typeString); + Object res; + //Map the JSON object's value to it's Java value + switch (type) { + case String: + res = object.getString(KEY_VALUE); + break; + case StringSet: + Set set = new HashSet<>(); + JSONArray array = object.getJSONArray(KEY_VALUE); + for (int i = 0; i < array.length(); i++) { + set.add(array.getString(i)); + } + res = set; + break; + case Int: + res = object.getInt(KEY_VALUE); + break; + case Long: + res = object.getLong(KEY_VALUE); + break; + case Float: + res = Float.parseFloat(object.getString(KEY_VALUE)); + break; + case Boolean: + res = object.getBoolean(KEY_VALUE); + break; + default: + case Null: + res = null; + break; + } + //Queue the loaded object for placement into the in-memory preference map + tempMap.put(key, res); + } + //Apply all changes made to the in-memory preference map atomically + prefs = tempMap; + } catch (JSONException e) { + throw new RuntimeException("Error parsing JSON shared preferences!", e); + } + } + + /** + * Serialize the JSON prefs to a String. + * @return The serialized prefs. + */ + public synchronized String saveToString() { + return saveToJSONObject().toString(); + } + + /** + * Serialize the JSON prefs to a JSONObject + * @return The serialized prefs. + */ + public synchronized JSONObject saveToJSONObject() { + JSONObject object = new JSONObject(); + //Loop through every preference in the in-memory preference map + for (Map.Entry entry : prefs.entrySet()) { + JSONObject entryObj = new JSONObject(); + Object value = entry.getValue(); + //Determine the object's type + PrefType type = PrefType.fromObject(value); + if (type == PrefType.Float) { + value = value.toString(); + } else if (type == PrefType.StringSet) { + JSONArray arrayObj = new JSONArray(); + Set casted = (Set) value; + for (String item : casted) { + arrayObj.put(item); + } + value = arrayObj; + } + //Put the preference's type and value into a JSON object + entryObj.put(KEY_TYPE, type.name()); + entryObj.put(KEY_VALUE, value); + object.put(entry.getKey(), entryObj); + } + return object; + } + + @Override + public synchronized Map getAll() { + return new HashMap<>(prefs); + } + + private T fallbackIfNull(T obj, T fallback) { + if (obj == null) { + return fallback; + } + return obj; + } + + @Override + public synchronized String getString(String s, String s1) { + return fallbackIfNull((String) prefs.get(s), s1); + } + + @Override + public synchronized Set getStringSet(String s, Set set) { + return fallbackIfNull((Set) prefs.get(s), set); + } + + @Override + public synchronized int getInt(String s, int i) { + return fallbackIfNull((Integer) prefs.get(s), i); + } + + @Override + public synchronized long getLong(String s, long l) { + return fallbackIfNull((Long) prefs.get(s), l); + } + + @Override + public synchronized float getFloat(String s, float v) { + return fallbackIfNull((Float) prefs.get(s), v); + } + + @Override + public synchronized boolean getBoolean(String s, boolean b) { + return fallbackIfNull((Boolean) prefs.get(s), b); + } + + public synchronized Object get(String s, Object b) { + return fallbackIfNull(prefs.get(s), b); + } + + @Override + public synchronized boolean contains(String s) { + return prefs.containsKey(s); + } + + @Override + public JsonSharedPreferencesEditor edit() { + return new JsonSharedPreferencesEditor(); + } + + @Override + public synchronized void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + if (!listeners.contains(onSharedPreferenceChangeListener)) { + listeners.add(onSharedPreferenceChangeListener); + } + } + + @Override + public synchronized void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + listeners.remove(onSharedPreferenceChangeListener); + } + + public class JsonSharedPreferencesEditor implements Editor { + + Map prefsClone = new HashMap<>(prefs); + + List affectedKeys = new ArrayList<>(); //List of all affected keys to invoke listeners on once changes applied + + private JsonSharedPreferencesEditor() { + } + + private void recordChange(String key) { + if (!affectedKeys.contains(key)) { + affectedKeys.add(key); + } + } + + public synchronized Editor put(String s, Object o) { + PrefType.fromObject(o); // Will throw if 'o' is invalid + + prefsClone.put(s, o); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putString(String s, String s1) { + prefsClone.put(s, s1); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putStringSet(String s, Set set) { + Set clonedSet = new HashSet<>(); + clonedSet.addAll(set); + prefsClone.put(s, clonedSet); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putInt(String s, int i) { + prefsClone.put(s, i); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putLong(String s, long l) { + prefsClone.put(s, l); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putFloat(String s, float v) { + prefsClone.put(s, v); + recordChange(s); + return this; + } + + @Override + public synchronized Editor putBoolean(String s, boolean b) { + prefsClone.put(s, b); + recordChange(s); + return this; + } + + @Override + public synchronized Editor remove(String s) { + prefsClone.remove(s); + recordChange(s); + return this; + } + + @Override + public synchronized Editor clear() { + prefsClone.keySet().forEach(this::recordChange); + prefsClone.clear(); + return this; + } + + @Override + public synchronized boolean commit() { + synchronized (JsonSharedPreferences.this) { + //Backup prefs + Map oldPrefs = prefs; + prefs = prefsClone; + if(file != null) { + //Delete old on-disk copy of preferences + if(file.exists()) { + file.delete(); + } + //Save new preferences to disk + String string = saveToString(); + try (PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)))) { + writer.print(string); + } catch (IOException e) { + logger.error("Failed to save shared prefs!", e); + prefs = oldPrefs; + return false; + } + } + //Invoke preference change listeners + for (String key : affectedKeys) { + for (OnSharedPreferenceChangeListener listener : listeners) { + listener.onSharedPreferenceChanged(JsonSharedPreferences.this, key); + } + } + affectedKeys.clear(); + return true; + } + } + + @Override + public void apply() { + //TODO Threading? + commit(); + } + } + + public File getFile() { + return file; + } + + public void setFile(File file) { + this.file = file; + } + + /** Preference types (for storing what type of object a preference is)**/ + private enum PrefType { + String, + StringSet, + Int, + Long, + Float, + Boolean, + Null; + + public static PrefType fromObject(Object object) { + if (object == null) { + return Null; + } + if (object instanceof String) { + return String; + } else if (object instanceof Set || Set.class.isAssignableFrom(object.getClass())) { + return StringSet; + } else if (object instanceof Integer) { + return Int; + } else if (object instanceof Long) { + return Long; + } else if (object instanceof Float) { + return Float; + } else if (object instanceof Boolean) { + return Boolean; + } + throw new IllegalArgumentException("Could not find type of object: " + object); + } + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt new file mode 100644 index 00000000..ce94d6d9 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt @@ -0,0 +1,93 @@ +package xyz.nulldev.androidcompat.pm + +import android.content.pm.PackageInfo +import android.content.pm.Signature +import android.os.Bundle +import com.android.apksig.ApkVerifier +import com.googlecode.d2j.dex.Dex2jar +import net.dongliu.apk.parser.ApkFile +import net.dongliu.apk.parser.ApkParsers +import org.w3c.dom.Element +import org.w3c.dom.Node +import org.w3c.dom.NodeList +import java.io.File +import javax.imageio.ImageIO +import javax.xml.parsers.DocumentBuilderFactory + + + +data class InstalledPackage(val root: File) { + val apk = File(root, "package.apk") + val jar = File(root, "translated.jar") + val icon = File(root, "icon.png") + + val info: PackageInfo + get() = ApkParsers.getMetaInfo(apk).toPackageInfo(root, apk).also { + val parsed = ApkFile(apk) + val dbFactory = DocumentBuilderFactory.newInstance() + val dBuilder = dbFactory.newDocumentBuilder() + val doc = parsed.manifestXml.byteInputStream().use { + dBuilder.parse(it) + } + + it.applicationInfo.metaData = Bundle().apply { + val appTag = doc.getElementsByTagName("application").item(0) + + appTag?.childNodes?.toList()?.filter { + it.nodeType == Node.ELEMENT_NODE + }?.map { + it as Element + }?.filter { + it.tagName == "meta-data" + }?.map { + putString(it.attributes.getNamedItem("android:name").nodeValue, + it.attributes.getNamedItem("android:value").nodeValue) + } + } + + it.signatures = (parsed.apkSingers.flatMap { it.certificateMetas } + /*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 + .map { Signature(it.data) }.toTypedArray() + } + + fun verify(): Boolean { + val res = ApkVerifier.Builder(apk) + .build() + .verify() + + return res.isVerified + } + + fun writeIcon() { + try { + val icons = ApkFile(apk).allIcons + + val read = icons.filter { it.isFile }.map { + it.data.inputStream().use { + ImageIO.read(it) + } + }.sortedByDescending { it.width * it.height }.firstOrNull() ?: return + + ImageIO.write(read, "png", icon) + } catch(e: Exception) { + icon.delete() + } + } + + fun writeJar() { + try { + Dex2jar.from(apk).to(jar.toPath()) + } catch(e: Exception) { + jar.delete() + } + } + + private fun NodeList.toList(): List { + val out = mutableListOf() + + for(i in 0 until length) + out += item(i) + + return out + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt new file mode 100644 index 00000000..560dc6e2 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt @@ -0,0 +1,87 @@ +package xyz.nulldev.androidcompat.pm + +import net.dongliu.apk.parser.ApkParsers +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance +import xyz.nulldev.androidcompat.io.AndroidFiles +import java.io.File + +class PackageController { + private val androidFiles by DI.global.instance() + private val uninstallListeners = mutableListOf<(String) -> Unit>() + + fun registerUninstallListener(listener: (String) -> Unit) { + uninstallListeners.add(listener) + } + + fun unregisterUninstallListener(listener: (String) -> Unit) { + uninstallListeners.remove(listener) + } + + private fun findRoot(apk: File): File { + val pn = ApkParsers.getMetaInfo(apk).packageName + + return File(androidFiles.packagesDir, pn) + } + + fun installPackage(apk: File, allowReinstall: Boolean) { + val root = findRoot(apk) + + if (root.exists()) { + if (!allowReinstall) { + throw IllegalStateException("Package already installed!") + } else { + // TODO Compare past and new signature + root.deleteRecursively() + } + } + + try { + root.mkdirs() + + val installed = InstalledPackage(root) + apk.copyTo(installed.apk) + installed.writeIcon() + installed.writeJar() + + if (!installed.jar.exists()) { + throw IllegalStateException("Failed to translate APK dex!") + } + } catch(t: Throwable) { + root.deleteRecursively() + throw t + } + } + + fun listInstalled(): List { + return androidFiles.packagesDir.listFiles().orEmpty().filter { + it.isDirectory + }.map { + InstalledPackage(it) + } + } + + fun deletePackage(pack: InstalledPackage) { + if(!pack.root.exists()) error("Package was never installed!") + + val packageName = pack.info.packageName + pack.root.deleteRecursively() + uninstallListeners.forEach { + it(packageName) + } + } + + fun findPackage(packageName: String): InstalledPackage? { + val file = File(androidFiles.packagesDir, packageName) + return if(file.exists()) + InstalledPackage(file) + else + null + } + + fun findJarFromApk(apkFile: File): File? { + val pkgName = ApkParsers.getMetaInfo(apkFile).packageName + return findPackage(pkgName)?.jar + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt new file mode 100644 index 00000000..b42633be --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt @@ -0,0 +1,27 @@ +package xyz.nulldev.androidcompat.pm + +import android.content.pm.ApplicationInfo +import android.content.pm.FeatureInfo +import android.content.pm.PackageInfo +import net.dongliu.apk.parser.bean.ApkMeta +import java.io.File + +fun ApkMeta.toPackageInfo(root: File, apk: File): PackageInfo { + return PackageInfo().also { + it.packageName = packageName + it.versionCode = versionCode.toInt() + it.versionName = versionName + + it.reqFeatures = usesFeatures.map { + FeatureInfo().apply { + name = it.name + } + }.toTypedArray() + + it.applicationInfo = ApplicationInfo().apply { + packageName = it.packageName + nonLocalizedLabel = label + sourceDir = apk.absolutePath + } + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/BuildConfigCompat.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/BuildConfigCompat.java new file mode 100644 index 00000000..78e5122b --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/BuildConfigCompat.java @@ -0,0 +1,27 @@ +package xyz.nulldev.androidcompat.res; + +import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; +import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; + +import java.text.SimpleDateFormat; +import java.util.Calendar; + +/** + * BuildConfig compat class. + */ +public class BuildConfigCompat { + private static ApplicationInfoImpl applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class); + + public static final boolean DEBUG = applicationInfo.getDebug(); + + //We assume application ID = package name + public static final String APPLICATION_ID = applicationInfo.packageName; + + //TODO Build time is hardcoded currently + public static final String BUILD_TIME; + static { + Calendar cal = Calendar.getInstance(); + cal.set(2000, Calendar.JANUARY, 1); + BUILD_TIME = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'").format(cal.getTime()); + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/DrawableResource.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/DrawableResource.kt new file mode 100644 index 00000000..490c3b1c --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/DrawableResource.kt @@ -0,0 +1,7 @@ +package xyz.nulldev.androidcompat.res + +class DrawableResource(val location: String) : Resource { + override fun getType() = DrawableResource::class.java + + override fun getValue() = javaClass.getResourceAsStream(location) +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/RCompat.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/RCompat.java new file mode 100644 index 00000000..f8cd6e9a --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/RCompat.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.res; + +import java.util.HashMap; +import java.util.Map; + +/** + * Custom implementation of R.java. + */ +public class RCompat { + //Resources stored in memory + private static Map resources = new HashMap<>(); + //The last used resource ID, used to generate new resource IDs + private static int lastRes = 0; + + /** + * Add a new String resource. + * @param s The value of the String resource. + * @return The added String resource. + */ + public static int sres(String s) { + return res(new StringResource(s)); + } + + public static int dres(String s) { + return res(new DrawableResource(s)); + } + + /** + * Add a resource. + * @param res The resource to add. + * @return The ID of the added resource. + */ + public static int res(Resource res) { + int nextRes = lastRes++; + resources.put(nextRes, res); + return nextRes; + } + + /** + * Get a string resource + * @param id The id of the resource + * @return The string resource + */ + public static String getString(int id) { + return cast(resources.get(id), StringResource.class).getValue(); + } + + /** + * Convenience method for casting resources. + * @param resource The resource to cast + * @param output The class of the output resource type + * @param The type of the output resource + * @return The casted resource + */ + private static T cast(Resource resource, Class output) { + if(resource.getType().equals(output)) { + return (T) resource; + } else { + throw new IllegalArgumentException("This resource is not of type: " + output.getSimpleName() + "!"); + } + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/Resource.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/Resource.kt new file mode 100644 index 00000000..043fc9f3 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/Resource.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.res + +/** + * A static Android resource. + */ + +interface Resource { + fun getType(): Class + + fun getValue(): Any? +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/StringResource.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/StringResource.kt new file mode 100644 index 00000000..66c6b31a --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/res/StringResource.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.res + +/** + * String resource. + */ +class StringResource(val string: String) : Resource { + override fun getValue() = string + + override fun getType() = StringResource::class.java +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt new file mode 100644 index 00000000..a0e3c7ce --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt @@ -0,0 +1,68 @@ +package xyz.nulldev.androidcompat.service + +import android.app.Service +import android.content.Context +import android.content.Intent +import mu.KotlinLogging +import java.util.concurrent.ConcurrentHashMap +import kotlin.concurrent.thread + +/** + * Service emulation class + * + * TODO Possibly handle starting services via bindService + */ + +class ServiceSupport { + val runningServices = ConcurrentHashMap() + + private val logger = KotlinLogging.logger {} + + fun startService(context: Context, intent: Intent) { + val name = intentToClassName(intent) + + logger.debug { "Starting service: $name" } + + val service = serviceInstanceFromClass(name) + + runningServices[name] = service + + //Setup service + thread { + callOnCreate(service) + //TODO Handle more complex cases + service.onStartCommand(intent, 0, 0) + } + } + + fun stopService(context: Context, intent: Intent) { + val name = intentToClassName(intent) + stopService(name) + } + + fun stopService(name: String) { + logger.debug { "Stopping service: $name" } + val service = runningServices.remove(name) + if(service == null) { + logger.warn { "An attempt was made to stop a service that is not running: $name" } + } else { + thread { + service.onDestroy() + } + } + } + + fun stopSelf(service: Service) { + stopService(service.javaClass.name) + } + + fun callOnCreate(service: Service) = service.onCreate() + + fun intentToClassName(intent: Intent) = intent.component.className!! + + fun serviceInstanceFromClass(className: String): Service { + val clazzObj = Class.forName(className) + return clazzObj.getDeclaredConstructor().newInstance() as? Service + ?: throw IllegalArgumentException("$className is not a Service!") + } +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt new file mode 100644 index 00000000..d254a64c --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt @@ -0,0 +1,67 @@ +package xyz.nulldev.androidcompat.util + +import android.content.Context +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance +import xyz.nulldev.androidcompat.androidimpl.CustomContext +import xyz.nulldev.androidcompat.androidimpl.FakePackageManager +import xyz.nulldev.androidcompat.info.ApplicationInfoImpl +import xyz.nulldev.androidcompat.io.AndroidFiles +import xyz.nulldev.androidcompat.pm.PackageController +import xyz.nulldev.androidcompat.service.ServiceSupport + +/** + * Helper class to allow access to Kodein from Java + */ +object KodeinGlobalHelper { + /** + * Get the Kodein object + */ + @JvmStatic + fun kodein() = DI.global + + /** + * Get a dependency + */ + @JvmStatic + fun instance(type: Class, kodein: DI? = null): T { + return when(type) { + AndroidFiles::class.java -> { + val instance: AndroidFiles by (kodein ?: kodein()).instance() + instance as T + } + ApplicationInfoImpl::class.java -> { + val instance: ApplicationInfoImpl by (kodein ?: kodein()).instance() + instance as T + } + ServiceSupport::class.java -> { + val instance: ServiceSupport by (kodein ?: kodein()).instance() + instance as T + } + FakePackageManager::class.java -> { + val instance: FakePackageManager by (kodein ?: kodein()).instance() + instance as T + } + PackageController::class.java -> { + val instance: PackageController by (kodein ?: kodein()).instance() + instance as T + } + CustomContext::class.java -> { + val instance: CustomContext by (kodein ?: kodein()).instance() + instance as T + } + Context::class.java -> { + val instance: Context by (kodein ?: kodein()).instance() + instance as T + } + else -> throw IllegalArgumentException("Kodein instance not found") + } + } + + @JvmStatic + fun instance(type: Class): T { + return instance(type, null) + } + +} diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt new file mode 100644 index 00000000..d8f92a82 --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Andy Bao + * + * 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 xyz.nulldev.androidcompat.util + +import android.net.Uri +import java.io.File +import java.net.URI + +/** + * Utilites to convert between Java and Android Uris. + */ +fun Uri.java() = URI(this.toString()) +fun Uri.file() = File(this.path) +fun URI.android() = Uri.parse(this.toString())!! diff --git a/README.md b/README.md index 8d0e695f..33ecad06 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,10 @@ This project has two components: 2. **webUI:** A react project that works with the server to do the presentation ## How do I run the thing? +## Getting things ready +run `$ scripts/getAndroid.sh` to do download Google's Android stubs jar.(do this only once) ### The Server - run `./gradlew :server:run` to run the server +run `./gradlew :server:run` to run the server ### the webUI how to do it is described in `webUI/react/README.md` but for short, first cd into `webUI/react` then run `yarn` to install the node modules(do this only once) diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..07800cfd --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,80 @@ +import org.jetbrains.kotlin.config.KotlinCompilerVersion + +plugins { + id("org.jetbrains.kotlin.jvm") version "1.4.21" apply false // Also in buildSrc Config.kt + id("java") +} + +allprojects { + group = "xyz.nulldev.ts" + + version = "1.0" + + repositories { + jcenter() + mavenCentral() + maven("https://jitpack.io") + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://dl.bintray.com/inorichi/maven") + maven("https://dl.google.com/dl/android/maven2/") + } +} + +val javaProjects = listOf( + project(":AndroidCompat"), + project(":AndroidCompat:Config"), + project(":server") +) + +configure(javaProjects) { + apply(plugin = "java") + apply(plugin = "org.jetbrains.kotlin.jvm") + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + } + } + + dependencies { + // Kotlin + implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION)) + implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION)) + testImplementation(kotlin("test", version = "1.4.21")) + } +} + +configure(listOf( + project(":AndroidCompat"), + project(":server"), + project(":AndroidCompat:Config") + +)) { + dependencies { + // Dependency Injection + implementation("org.kodein.di:kodein-di-conf-jvm:7.1.0") + + // Logging + implementation("org.slf4j:slf4j-api:1.7.30") + implementation("org.slf4j:slf4j-simple:1.7.30") + implementation("io.github.microutils:kotlin-logging:2.0.3") + + // RxJava + implementation("io.reactivex:rxjava:1.3.8") + implementation("io.reactivex:rxkotlin:1.0.0") + + // JSoup + implementation("org.jsoup:jsoup:1.13.1") + + // Kotlin + implementation(kotlin("reflect", version = "1.4.21")) + + // dependency of :AndroidCompat:Config + implementation("com.typesafe:config:1.4.0") + } +} \ No newline at end of file diff --git a/scripts/getAndroid.sh b/scripts/getAndroid.sh new file mode 100755 index 00000000..9d001cda --- /dev/null +++ b/scripts/getAndroid.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +echo "Getting required Android.jar..." +rm -rf "tmp" +mkdir -p "tmp" +pushd "tmp" + +curl "https://android.googlesource.com/platform/prebuilts/sdk/+/3b8a524d25fa6c3d795afb1eece3f24870c60988/27/public/android.jar?format=TEXT" | base64 --decode > android.jar + +# We need to remove any stub classes that we might use +echo "Patching JAR..." + +echo "Removing org.json..." +zip --delete android.jar org/json/* + +echo "Removing org.apache..." +zip --delete android.jar org/apache/* + +echo "Removing org.w3c..." +zip --delete android.jar org/w3c/* + +echo "Removing org.xml..." +zip --delete android.jar org/xml/* + +echo "Removing org.xmlpull..." +zip --delete android.jar org/xmlpull/* + +echo "Removing junit..." +zip --delete android.jar junit/* + +echo "Removing javax..." +zip --delete android.jar javax/* + +echo "Removing java..." +zip --delete android.jar java/* + +echo "Removing overriden classes..." +zip --delete android.jar android/app/Application.class +zip --delete android.jar android/app/Service.class +zip --delete android.jar android/net/Uri.class +zip --delete android.jar 'android/net/Uri$Builder.class' +zip --delete android.jar android/os/Environment.class +zip --delete android.jar android/text/format/Formatter.class +zip --delete android.jar android/text/Html.class + +# Dedup overriden Android classes +ABS_JAR="$(realpath android.jar)" +function dedup() { + pushd "$1" + CLASSES="$(find * -type f)" + echo "$CLASSES" | while read class + do + NAME="${class%.*}" + echo "Processing class: $NAME" + zip --delete "$ABS_JAR" "$NAME.class" "$NAME\$*.class" "${NAME}Kt.class" "${NAME}Kt\$*.class" > /dev/null + done + popd +} + +pushd .. +dedup AndroidCompat/src/main/java +dedup TachiServer/src/main/java +dedup Tachiyomi-App/src/main/java +dedup Tachiyomi-App/src/compat/java +popd + +popd +echo "Copying Android.jar to library folder..." +mv tmp/android.jar AndroidCompat/lib + +echo "Cleaning up..." +rm -rf "tmp" + +echo "Done!" diff --git a/server/build.gradle.kts b/server/build.gradle.kts index bcd817c1..8faec9c0 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -1,18 +1,9 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -val compileKotlin: KotlinCompile by tasks - - plugins { - id("org.jetbrains.kotlin.jvm") version "1.4.21" +// id("org.jetbrains.kotlin.jvm") version "1.4.21" application } -compileKotlin.kotlinOptions { - jvmTarget = "1.8" -} - repositories { mavenCentral() jcenter() @@ -83,6 +74,10 @@ dependencies { implementation ("org.jetbrains.exposed:exposed-jdbc:$exposed_version") implementation ("org.xerial:sqlite-jdbc:3.30.1") + // AndroidCompat + implementation(project(":AndroidCompat")) + implementation(project(":AndroidCompat:Config")) + testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt new file mode 100644 index 00000000..e10c4764 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi + +import android.app.Application +import android.content.Context +//import android.content.res.Configuration +//import android.support.multidex.MultiDex +//import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.InjektScope +import uy.kohesive.injekt.registry.default.DefaultRegistrar + +open class App : Application() { + + override fun onCreate() { + super.onCreate() + Injekt = InjektScope(DefaultRegistrar()) + Injekt.importModule(AppModule(this)) + +// if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) + } + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) +// if (BuildConfig.DEBUG) { +// MultiDex.install(this) +// } + } + +// override fun onConfigurationChanged(newConfig: Configuration) { +// super.onConfigurationChanged(newConfig) +// } +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt new file mode 100644 index 00000000..3c86f62b --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt @@ -0,0 +1,66 @@ +package eu.kanade.tachiyomi + +import android.app.Application +import com.google.gson.Gson +//import eu.kanade.tachiyomi.data.cache.ChapterCache +//import eu.kanade.tachiyomi.data.cache.CoverCache +//import eu.kanade.tachiyomi.data.database.DatabaseHelper +//import eu.kanade.tachiyomi.data.download.DownloadManager +//import eu.kanade.tachiyomi.data.preference.PreferencesHelper +//import eu.kanade.tachiyomi.data.sync.LibrarySyncManager +//import eu.kanade.tachiyomi.data.track.TrackManager +//import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.source.SourceManager +import rx.Observable +import rx.schedulers.Schedulers +import uy.kohesive.injekt.api.* + +class AppModule(val app: Application) : InjektModule { + + override fun InjektRegistrar.registerInjectables() { + + addSingleton(app) + +// addSingletonFactory { PreferencesHelper(app) } +// +// addSingletonFactory { DatabaseHelper(app) } +// +// addSingletonFactory { ChapterCache(app) } +// +// addSingletonFactory { CoverCache(app) } + + addSingletonFactory { NetworkHelper(app) } + +// addSingletonFactory { SourceManager(app).also { get().init(it) } } +// +// addSingletonFactory { ExtensionManager(app) } +// +// addSingletonFactory { DownloadManager(app) } +// +// addSingletonFactory { TrackManager(app) } +// +// addSingletonFactory { LibrarySyncManager(app) } + + addSingletonFactory { Gson() } + + // Asynchronously init expensive components for a faster cold start + +// rxAsync { get() } + + rxAsync { get() } + + rxAsync { +// get() +// get() + } + +// rxAsync { get() } + + } + + private fun rxAsync(block: () -> Unit) { + Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe() + } + +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt index eb37e861..678be31b 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt @@ -7,6 +7,8 @@ import kotlinx.serialization.json.JsonArray import okhttp3.MediaType.Companion.toMediaType import retrofit2.Retrofit import retrofit2.http.GET +import uy.kohesive.injekt.injectLazy + //import uy.kohesive.injekt.injectLazy /** @@ -16,7 +18,7 @@ interface ExtensionGithubService { companion object { private val client by lazy { - val network: NetworkHelper = NetworkHelper() + val network: NetworkHelper by injectLazy() network.client.newBuilder() .addNetworkInterceptor { chain -> val originalResponse = chain.proceed(chain.request()) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index 2d2fced2..224e68eb 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network //import android.content.Context //import eu.kanade.tachiyomi.BuildConfig //import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import android.content.Context import okhttp3.Cache //import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient @@ -13,7 +14,7 @@ import java.io.File import java.net.InetAddress import java.util.concurrent.TimeUnit -class NetworkHelper() { +class NetworkHelper(context: Context) { // private val preferences: PreferencesHelper by injectLazy() diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index a9d4a200..b7c8175e 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -15,6 +15,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import rx.Observable +import uy.kohesive.injekt.injectLazy //import uy.kohesive.injekt.injectLazy import java.net.URI import java.net.URISyntaxException @@ -28,7 +29,7 @@ abstract class HttpSource : CatalogueSource { /** * Network service. */ - protected val network = NetworkHelper() + protected val network: NetworkHelper by injectLazy() // /** // * Preferences that a source may need. diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index b9f68fd6..a57e56a6 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -1,16 +1,43 @@ package ir.armor.tachidesk +import eu.kanade.tachiyomi.App import io.javalin.Javalin import ir.armor.tachidesk.util.* +import org.kodein.di.DI +import org.kodein.di.conf.global +import xyz.nulldev.androidcompat.AndroidCompat +import xyz.nulldev.androidcompat.AndroidCompatInitializer +import xyz.nulldev.ts.config.ConfigKodeinModule +import xyz.nulldev.ts.config.GlobalConfigManager +import xyz.nulldev.ts.config.ServerConfig import java.util.* class Main { companion object { + val androidCompat by lazy { AndroidCompat() } + + fun registerConfigModules() { + GlobalConfigManager.registerModules( +// ServerConfig.register(GlobalConfigManager.config), +// SyncConfigModule.register(GlobalConfigManager.config) + ) + } + @JvmStatic fun main(args: Array) { // make sure everything we need exists applicationSetup() + registerConfigModules() + + //Load config API + DI.global.addImport(ConfigKodeinModule().create()) + //Load Android compatibility dependencies + AndroidCompatInitializer().init() + // start app + androidCompat.startApp(App()) + + val app = Javalin.create().start(4567) @@ -40,6 +67,8 @@ class Main { ctx.json(getPopularManga(sourceId)) } } + + } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/APK.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/APK.kt index 86a81efe..a653535d 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/APK.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/APK.kt @@ -18,6 +18,9 @@ import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.io.File import java.net.URL import java.net.URLClassLoader @@ -118,9 +121,11 @@ fun installAPK(apkName: String): Int { } } +val networkHelper: NetworkHelper by injectLazy() + private fun downloadAPKFile(url: String, apkPath: String) { val request = Request.Builder().url(url).build() - val response = NetworkHelper().client.newCall(request).execute(); + val response = networkHelper.client.newCall(request).execute() val downloadedFile = File(apkPath) val sink = downloadedFile.sink().buffer() diff --git a/settings.gradle.kts b/settings.gradle.kts index 1b327990..971401e9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,4 +2,7 @@ rootProject.name = "Tachidesk" include("server") -include("webUI") \ No newline at end of file +include("webUI") + +include("AndroidCompat") +include("AndroidCompat:Config") \ No newline at end of file