add CryptUrils. import anko source code instead of aar library to avoid build problem

This commit is contained in:
tateisu 2023-02-02 01:11:39 +09:00
parent fb59239edd
commit 492995dc40
88 changed files with 11883 additions and 146 deletions

View File

@ -3,6 +3,7 @@
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8">
<module name="apng" target="1.7" />
<module name="SubwayTooter.anko" target="11" />
<module name="SubwayTooter.apng_android" target="11" />
<module name="SubwayTooter.app" target="11" />
<module name="SubwayTooter.base" target="11" />

View File

@ -11,6 +11,7 @@
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/anko" />
<option value="$PROJECT_DIR$/apng" />
<option value="$PROJECT_DIR$/apng_android" />
<option value="$PROJECT_DIR$/app" />

View File

@ -3,6 +3,10 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" />

1
anko/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

48
anko/build.gradle Normal file
View File

@ -0,0 +1,48 @@
plugins {
id "com.android.library"
id "org.jetbrains.kotlin.android"
id "org.jetbrains.kotlin.kapt"
}
android {
namespace "jp.juggler.anko"
compileSdk stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
defaultConfig {
minSdk stMinSdkVersion
targetSdk stTargetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = kotlinJvmTarget
}
}
dependencies {
implementation "androidx.core:core-ktx:$coreKtxVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "com.google.android.material:material:$materialVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxTestExtJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
implementation "androidx.preference:preference:$preferenceVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
kapt "androidx.annotation:annotation:$androidxAnnotationVersion"
}

0
anko/consumer-rules.pro Normal file
View File

21
anko/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package jp.juggler.anko
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("jp.juggler.anko.test", appContext.packageName)
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,119 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.graphics.drawable.Drawable
import android.view.KeyEvent
import android.view.View
import android.view.ViewManager
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
import kotlin.DeprecationLevel.ERROR
@SuppressLint("SupportAnnotationUsage")
interface AlertBuilder<out D : DialogInterface> {
val ctx: Context
var title: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get
var titleResource: Int
@Deprecated(NO_GETTER, level = ERROR) get
var message: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get
var messageResource: Int
@Deprecated(NO_GETTER, level = ERROR) get
var icon: Drawable
@Deprecated(NO_GETTER, level = ERROR) get
@setparam:DrawableRes
var iconResource: Int
@Deprecated(NO_GETTER, level = ERROR) get
var customTitle: View
@Deprecated(NO_GETTER, level = ERROR) get
var customView: View
@Deprecated(NO_GETTER, level = ERROR) get
var isCancelable: Boolean
@Deprecated(NO_GETTER, level = ERROR) get
fun onCancelled(handler: (dialog: DialogInterface) -> Unit)
fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean)
fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
fun positiveButton(
@StringRes buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
)
fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
fun negativeButton(
@StringRes buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
)
fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
fun neutralPressed(
@StringRes buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
)
fun items(
items: List<CharSequence>,
onItemSelected: (dialog: DialogInterface, index: Int) -> Unit,
)
fun <T> items(
items: List<T>,
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit,
)
fun build(): D
fun show(): D
}
fun AlertBuilder<*>.customTitle(dsl: ViewManager.() -> Unit) {
customTitle = ctx.UI(dsl).view
}
fun AlertBuilder<*>.customView(dsl: ViewManager.() -> Unit) {
customView = ctx.UI(dsl).view
}
inline fun AlertBuilder<*>.okButton(noinline handler: (dialog: DialogInterface) -> Unit) =
positiveButton(android.R.string.ok, handler)
inline fun AlertBuilder<*>.cancelButton(noinline handler: (dialog: DialogInterface) -> Unit) =
negativeButton(android.R.string.cancel, handler)
inline fun AlertBuilder<*>.yesButton(noinline handler: (dialog: DialogInterface) -> Unit) =
positiveButton(android.R.string.yes, handler)
inline fun AlertBuilder<*>.noButton(noinline handler: (dialog: DialogInterface) -> Unit) =
negativeButton(android.R.string.no, handler)

View File

@ -0,0 +1,304 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
//import android.app.AlertDialog
//import android.content.Context
//import android.content.DialogInterface
//import android.database.Cursor
//import android.graphics.drawable.Drawable
//import android.view.KeyEvent
//import android.view.View
//import android.view.ViewManager
//import android.widget.ListAdapter
//
//@Deprecated("Use AlertBuilder class instead.")
//class AlertDialogBuilder(val ctx: Context) {
// private var builder: AlertDialog.Builder? = AlertDialog.Builder(ctx)
//
// /**
// * Returns the [AlertDialog] instance if created.
// * Returns null until the [show] function is called.
// */
// var dialog: AlertDialog? = null
// private set
//
// constructor(ankoContext: AnkoContext<*>) : this(ankoContext.ctx)
//
// fun dismiss() {
// dialog?.dismiss()
// }
//
// private fun checkBuilder() {
// if (builder == null) {
// throw IllegalStateException("show() was already called for this AlertDialogBuilder")
// }
// }
//
// /**
// * Create the [AlertDialog] and display it on screen.
// *
// */
// fun show(): AlertDialogBuilder {
// checkBuilder()
// dialog = builder!!.create()
// builder = null
// dialog!!.show()
// return this
// }
//
// /**
// * Set the [title] displayed in the dialog.
// */
// fun title(title: CharSequence) {
// checkBuilder()
// builder!!.setTitle(title)
// }
//
// /**
// * Set the title using the given [title] resource id.
// */
// fun title(title: Int) {
// checkBuilder()
// builder!!.setTitle(title)
// }
//
// /**
// * Set the [message] to display.
// */
// fun message(message: CharSequence) {
// checkBuilder()
// builder!!.setMessage(message)
// }
//
// /**
// * Set the message to display using the given [message] resource id.
// */
// fun message(message: Int) {
// checkBuilder()
// builder!!.setMessage(message)
// }
//
// /**
// * Set the resource id of the [Drawable] to be used in the title.
// */
// fun icon(icon: Int) {
// checkBuilder()
// builder!!.setIcon(icon)
// }
//
// /**
// * Set the [icon] Drawable to be used in the title.
// */
// fun icon(icon: Drawable) {
// checkBuilder()
// builder!!.setIcon(icon)
// }
//
// /**
// * Set the title using the custom [view].
// */
// fun customTitle(view: View) {
// checkBuilder()
// builder!!.setCustomTitle(view)
// }
//
// /**
// * Set the title using the custom DSL view.
// */
// fun customTitle(dsl: ViewManager.() -> Unit) {
// checkBuilder()
// val view = ctx.UI(dsl).view
// builder!!.setCustomTitle(view)
// }
//
// /**
// * Set a custom [view] to be the contents of the Dialog.
// */
// fun customView(view: View) {
// checkBuilder()
// builder!!.setView(view)
// }
//
// /**
// * Set a custom DSL view to be the contents of the Dialog.
// */
// fun customView(dsl: ViewManager.() -> Unit) {
// checkBuilder()
// val view = ctx.UI(dsl).view
// builder!!.setView(view)
// }
//
// /**
// * Set if the dialog is cancellable.
// *
// * @param cancellable if true, the created dialog will be cancellable.
// */
// fun cancellable(cancellable: Boolean = true) {
// checkBuilder()
// builder!!.setCancelable(cancellable)
// }
//
// /**
// * Sets the [callback] that will be called if the dialog is canceled.
// */
// fun onCancel(callback: () -> Unit) {
// checkBuilder()
// builder!!.setOnCancelListener { callback() }
// }
//
// /**
// * Sets the [callback] that will be called if a key is dispatched to the dialog.
// */
// fun onKey(callback: (keyCode: Int, e: KeyEvent) -> Boolean) {
// checkBuilder()
// builder!!.setOnKeyListener({ dialog, keyCode, event -> callback(keyCode, event) })
// }
//
// /**
// * Set a listener to be invoked when the neutral button of the dialog is pressed.
// *
// * @param neutralText the text resource to display in the neutral button.
// * @param callback the callback that will be called if the neutral button is pressed.
// */
// fun neutralButton(
// neutralText: Int = android.R.string.ok,
// callback: DialogInterface.() -> Unit = { dismiss() },
// ) {
// neutralButton(ctx.getString(neutralText), callback)
// }
//
// /**
// * Set a listener to be invoked when the neutral button of the dialog is pressed.
// *
// * @param neutralText the text to display in the neutral button.
// * @param callback the callback that will be called if the neutral button is pressed.
// */
// fun neutralButton(
// neutralText: CharSequence,
// callback: DialogInterface.() -> Unit = { dismiss() },
// ) {
// checkBuilder()
// builder!!.setNeutralButton(neutralText, { dialog, which -> dialog.callback() })
// }
//
// /**
// * Set a listener to be invoked when the positive button of the dialog is pressed.
// *
// * @param positiveText the text to display in the positive button.
// * @param callback the callback that will be called if the positive button is pressed.
// */
// fun positiveButton(positiveText: Int, callback: DialogInterface.() -> Unit) {
// positiveButton(ctx.getString(positiveText), callback)
// }
//
// /**
// * Set a listener to be invoked when the positive button of the dialog is pressed.
// *
// * @param callback the callback that will be called if the positive button is pressed.
// */
// fun okButton(callback: DialogInterface.() -> Unit) {
// positiveButton(ctx.getString(android.R.string.ok), callback)
// }
//
// /**
// * Set a listener to be invoked when the positive button of the dialog is pressed.
// *
// * @param callback the callback that will be called if the positive button is pressed.
// */
// fun yesButton(callback: DialogInterface.() -> Unit) {
// positiveButton(ctx.getString(android.R.string.yes), callback)
// }
//
// /**
// * Set a listener to be invoked when the positive button of the dialog is pressed.
// *
// * @param positiveText the text to display in the positive button.
// * @param callback the callback that will be called if the positive button is pressed.
// */
// fun positiveButton(positiveText: CharSequence, callback: DialogInterface.() -> Unit) {
// checkBuilder()
// builder!!.setPositiveButton(positiveText, { dialog, which -> dialog.callback() })
// }
//
// /**
// * Set a listener to be invoked when the negative button of the dialog is pressed.
// *
// * @param negativeText the text to display in the negative button.
// * @param callback the callback that will be called if the negative button is pressed.
// */
// fun negativeButton(negativeText: Int, callback: DialogInterface.() -> Unit = { dismiss() }) {
// negativeButton(ctx.getString(negativeText), callback)
// }
//
// /**
// * Set a listener to be invoked when the negative button of the dialog is pressed.
// *
// * @param callback the callback that will be called if the negative button is pressed.
// */
// fun cancelButton(callback: DialogInterface.() -> Unit = { dismiss() }) {
// negativeButton(ctx.getString(android.R.string.cancel), callback)
// }
//
// /**
// * Set a listener to be invoked when the negative button of the dialog is pressed.
// *
// * @param callback the callback that will be called if the negative button is pressed.
// */
// fun noButton(callback: DialogInterface.() -> Unit = { dismiss() }) {
// negativeButton(ctx.getString(android.R.string.no), callback)
// }
//
// /**
// * Set a listener to be invoked when the negative button of the dialog is pressed.
// *
// * @param negativeText the text to display in the negative button.
// * @param callback the callback that will be called if the negative button is pressed.
// */
// fun negativeButton(
// negativeText: CharSequence,
// callback: DialogInterface.() -> Unit = { dismiss() },
// ) {
// checkBuilder()
// builder!!.setNegativeButton(negativeText, { dialog, which -> dialog.callback() })
// }
//
// fun items(itemsId: Int, callback: (which: Int) -> Unit) {
// items(ctx.resources!!.getTextArray(itemsId), callback)
// }
//
// fun items(items: List<CharSequence>, callback: (which: Int) -> Unit) {
// items(items.toTypedArray(), callback)
// }
//
// fun items(items: Array<CharSequence>, callback: (which: Int) -> Unit) {
// checkBuilder()
// builder!!.setItems(items, { dialog, which -> callback(which) })
// }
//
// fun adapter(adapter: ListAdapter, callback: (which: Int) -> Unit) {
// checkBuilder()
// builder!!.setAdapter(adapter, { dialog, which -> callback(which) })
// }
//
// fun adapter(cursor: Cursor, labelColumn: String, callback: (which: Int) -> Unit) {
// checkBuilder()
// builder!!.setCursor(cursor, { dialog, which -> callback(which) }, labelColumn)
// }
//}

View File

@ -0,0 +1,117 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.jetbrains.anko
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.graphics.drawable.Drawable
import android.view.KeyEvent
import android.view.View
import org.jetbrains.anko.internals.AnkoInternals
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
import kotlin.DeprecationLevel.ERROR
val Android: AlertBuilderFactory<AlertDialog> = ::AndroidAlertBuilder
internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<AlertDialog> {
private val builder = AlertDialog.Builder(ctx)
override var title: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setTitle(value) }
override var titleResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setTitle(value) }
override var message: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setMessage(value) }
override var messageResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setMessage(value) }
override var icon: Drawable
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setIcon(value) }
override var iconResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setIcon(value) }
override var customTitle: View
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setCustomTitle(value) }
override var customView: View
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setView(value) }
override var isCancelable: Boolean
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) { builder.setCancelable(value) }
override fun onCancelled(handler: (DialogInterface) -> Unit) {
builder.setOnCancelListener(handler)
}
override fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) {
builder.setOnKeyListener(handler)
}
override fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setPositiveButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun positiveButton(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setPositiveButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNegativeButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun negativeButton(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNegativeButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNeutralButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun neutralPressed(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNeutralButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun items(items: List<CharSequence>, onItemSelected: (dialog: DialogInterface, index: Int) -> Unit) {
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
onItemSelected(dialog, which)
}
}
override fun <T> items(items: List<T>, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) {
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
onItemSelected(dialog, items[which], which)
}
}
override fun build(): AlertDialog = builder.create()
override fun show(): AlertDialog = builder.show()
}

View File

@ -0,0 +1,184 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import androidx.fragment.app.Fragment
inline fun AnkoContext<*>.alert(
message: CharSequence,
title: CharSequence? = null,
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
) = ctx.alert(message, title, init)
inline fun Fragment.alert(
message: CharSequence,
title: CharSequence? = null,
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
) = requireActivity().alert(message, title, init)
fun Context.alert(
message: CharSequence,
title: CharSequence? = null,
init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
): AlertBuilder<AlertDialog> {
return AndroidAlertBuilder(this).apply {
if (title != null) {
this.title = title
}
this.message = message
if (init != null) init()
}
}
inline fun AnkoContext<*>.alert(
message: Int,
title: Int? = null,
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
) = ctx.alert(message, title, init)
inline fun Fragment.alert(
message: Int,
title: Int? = null,
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
) = requireActivity().alert(message, title, init)
fun Context.alert(
messageResource: Int,
titleResource: Int? = null,
init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
): AlertBuilder<DialogInterface> {
return AndroidAlertBuilder(this).apply {
if (titleResource != null) {
this.titleResource = titleResource
}
this.messageResource = messageResource
if (init != null) init()
}
}
inline fun AnkoContext<*>.alert(noinline init: AlertBuilder<DialogInterface>.() -> Unit) =
ctx.alert(init)
inline fun Fragment.alert(noinline init: AlertBuilder<DialogInterface>.() -> Unit) =
requireActivity().alert(init)
fun Context.alert(init: AlertBuilder<DialogInterface>.() -> Unit): AlertBuilder<DialogInterface> =
AndroidAlertBuilder(this).apply { init() }
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun AnkoContext<*>.progressDialog(
// message: Int? = null,
// title: Int? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = ctx.progressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun Fragment.progressDialog(
// message: Int? = null,
// title: Int? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = requireActivity().progressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//fun Context.progressDialog(
// message: Int? = null,
// title: Int? = null,
// init: (ProgressDialog.() -> Unit)? = null,
//) = progressDialog(false, message?.let { getString(it) }, title?.let { getString(it) }, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun AnkoContext<*>.indeterminateProgressDialog(
// message: Int? = null,
// title: Int? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = ctx.indeterminateProgressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun Fragment.indeterminateProgressDialog(
// message: Int? = null,
// title: Int? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = requireActivity().indeterminateProgressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//fun Context.indeterminateProgressDialog(
// message: Int? = null,
// title: Int? = null,
// init: (ProgressDialog.() -> Unit)? = null,
//) = progressDialog(true, message?.let { getString(it) }, title?.let { getString(it) }, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun AnkoContext<*>.progressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = ctx.progressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun Fragment.progressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = requireActivity().progressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//fun Context.progressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// init: (ProgressDialog.() -> Unit)? = null,
//) = progressDialog(false, message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun AnkoContext<*>.indeterminateProgressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = ctx.indeterminateProgressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//inline fun Fragment.indeterminateProgressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// noinline init: (ProgressDialog.() -> Unit)? = null,
//) = requireActivity().indeterminateProgressDialog(message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//fun Context.indeterminateProgressDialog(
// message: CharSequence? = null,
// title: CharSequence? = null,
// init: (ProgressDialog.() -> Unit)? = null,
//) = progressDialog(true, message, title, init)
//
//@Deprecated(message = "Android progress dialogs are deprecated")
//private fun Context.progressDialog(
// indeterminate: Boolean,
// message: CharSequence? = null,
// title: CharSequence? = null,
// init: (ProgressDialog.() -> Unit)? = null,
//) = ProgressDialog(this).apply {
// isIndeterminate = indeterminate
// if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
// if (message != null) setMessage(message)
// if (title != null) setTitle(title)
// if (init != null) init()
// show()
//}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.content.DialogInterface
import androidx.fragment.app.Fragment
inline fun AnkoContext<*>.selector(
title: CharSequence? = null,
items: List<CharSequence>,
noinline onClick: (DialogInterface, Int) -> Unit,
) = ctx.selector(title, items, onClick)
inline fun Fragment.selector(
title: CharSequence? = null,
items: List<CharSequence>,
noinline onClick: (DialogInterface, Int) -> Unit,
) = requireActivity().selector(title, items, onClick)
fun Context.selector(
title: CharSequence? = null,
items: List<CharSequence>,
onClick: (DialogInterface, Int) -> Unit,
) {
with(AndroidAlertBuilder(this)) {
if (title != null) {
this.title = title
}
items(items, onClick)
show()
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import androidx.fragment.app.Fragment
import org.jetbrains.anko.internals.AnkoInternals.createAnkoContext
@DslMarker
private annotation class AnkoContextDslMarker
@AnkoContextDslMarker
interface AnkoContext<out T> : ViewManager {
val ctx: Context
val owner: T
val view: View
override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
throw UnsupportedOperationException()
}
override fun removeView(view: View) {
throw UnsupportedOperationException()
}
companion object {
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context> =
AnkoContextImpl(ctx, ctx, setContentView)
fun createReusable(ctx: Context, setContentView: Boolean = false): AnkoContext<Context> =
ReusableAnkoContext(ctx, ctx, setContentView)
fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T> =
AnkoContextImpl(ctx, owner, setContentView)
fun <T> createReusable(
ctx: Context,
owner: T,
setContentView: Boolean = false,
): AnkoContext<T> = ReusableAnkoContext(ctx, owner, setContentView)
fun <T : ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
}
}
internal class DelegatingAnkoContext<T : ViewGroup>(override val owner: T) : AnkoContext<T> {
override val ctx: Context = owner.context
override val view: View = owner
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
if (view == null) return
if (params == null) {
owner.addView(view)
} else {
owner.addView(view, params)
}
}
}
internal class ReusableAnkoContext<T>(
override val ctx: Context,
override val owner: T,
setContentView: Boolean,
) : AnkoContextImpl<T>(ctx, owner, setContentView) {
override fun alreadyHasView() {}
}
open class AnkoContextImpl<T>(
override val ctx: Context,
override val owner: T,
private val setContentView: Boolean,
) : AnkoContext<T> {
private var myView: View? = null
override val view: View
get() = myView ?: throw IllegalStateException("View was not set previously")
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
if (view == null) return
if (myView != null) {
alreadyHasView()
}
this.myView = view
if (setContentView) {
doAddView(ctx, view)
}
}
private fun doAddView(context: Context, view: View) {
when (context) {
is Activity -> context.setContentView(view)
is ContextWrapper -> doAddView(context.baseContext, view)
else -> throw IllegalStateException("Context is not an Activity, can't set content view")
}
}
protected open fun alreadyHasView(): Unit =
throw IllegalStateException("View is already set: $myView")
}
@Suppress("FunctionNaming")
inline fun Context.UI(
setContentView: Boolean,
init: AnkoContext<Context>.() -> Unit,
): AnkoContext<Context> =
createAnkoContext(this, init, setContentView)
@Suppress("FunctionNaming")
inline fun Context.UI(init: AnkoContext<Context>.() -> Unit): AnkoContext<Context> =
createAnkoContext(this, init)
@Suppress("FunctionNaming")
inline fun Fragment.UI(init: AnkoContext<Fragment>.() -> Unit): AnkoContext<Fragment> =
createAnkoContext(requireContext(), init)
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
fun <T : Activity> AnkoComponent<T>.setContentView(activity: T): View =
createView(AnkoContextImpl(activity, activity, true))

View File

@ -0,0 +1,209 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
import java.lang.ref.WeakReference
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
/**
* Execute [f] on the application UI thread.
*/
fun Context.runOnUiThread(f: Context.() -> Unit) {
if (Looper.getMainLooper() === Looper.myLooper()) f() else ContextHelper.handler.post { f() }
}
/**
* Execute [f] on the application UI thread.
*/
inline fun Fragment.runOnUiThread(crossinline f: () -> Unit) {
requireContext().runOnUiThread { f() }
}
class AnkoAsyncContext<T>(val weakRef: WeakReference<T>)
/**
* Execute [f] on the application UI thread.
* If the [doAsync] receiver still exists (was not collected by GC),
* [f] gets it as a parameter ([f] gets null if the receiver does not exist anymore).
*/
fun <T> AnkoAsyncContext<T>.onComplete(f: (T?) -> Unit) {
val ref = weakRef.get()
if (Looper.getMainLooper() === Looper.myLooper()) {
f(ref)
} else {
ContextHelper.handler.post { f(ref) }
}
}
/**
* Execute [f] on the application UI thread.
* [doAsync] receiver will be passed to [f].
* If the receiver does not exist anymore (it was collected by GC), [f] will not be executed.
*/
fun <T> AnkoAsyncContext<T>.uiThread(f: (T) -> Unit): Boolean {
val ref = weakRef.get() ?: return false
if (Looper.getMainLooper() === Looper.myLooper()) {
f(ref)
} else {
ContextHelper.handler.post { f(ref) }
}
return true
}
/**
* Execute [f] on the application UI thread if the underlying [Activity] still exists and is not finished.
* The receiver [Activity] will be passed to [f].
* If it is not exist anymore or if it was finished, [f] will not be called.
*/
fun <T : Activity> AnkoAsyncContext<T>.activityUiThread(f: (T) -> Unit): Boolean {
val activity = weakRef.get() ?: return false
if (activity.isFinishing) return false
activity.runOnUiThread { f(activity) }
return true
}
fun <T : Activity> AnkoAsyncContext<T>.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
val activity = weakRef.get() ?: return false
if (activity.isFinishing) return false
activity.runOnUiThread { activity.f(activity) }
return true
}
@JvmName("activityContextUiThread")
fun <T : Activity> AnkoAsyncContext<AnkoContext<T>>.activityUiThread(f: (T) -> Unit): Boolean {
val activity = weakRef.get()?.owner ?: return false
if (activity.isFinishing) return false
activity.runOnUiThread { f(activity) }
return true
}
@JvmName("activityContextUiThreadWithContext")
fun <T : Activity> AnkoAsyncContext<AnkoContext<T>>.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
val activity = weakRef.get()?.owner ?: return false
if (activity.isFinishing) return false
activity.runOnUiThread { activity.f(activity) }
return true
}
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
fun <T : Fragment> AnkoAsyncContext<T>.fragmentUiThread(f: (T) -> Unit): Boolean {
val fragment = weakRef.get() ?: return false
if (fragment.isDetached) return false
val activity = fragment.activity ?: return false
activity.runOnUiThread { f(fragment) }
return true
}
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
fun <T : Fragment> AnkoAsyncContext<T>.fragmentUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
val fragment = weakRef.get() ?: return false
if (fragment.isDetached) return false
val activity = fragment.activity ?: return false
activity.runOnUiThread { activity.f(fragment) }
return true
}
private val crashLogger = { throwable: Throwable -> throwable.printStackTrace() }
/**
* Execute [task] asynchronously.
*
* @param exceptionHandler optional exception handler.
* If defined, any exceptions thrown inside [task] will be passed to it. If not, exceptions will be ignored.
* @param task the code to execute asynchronously.
*/
fun <T> T.doAsync(
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
task: AnkoAsyncContext<T>.() -> Unit,
): Future<Unit> {
val context = AnkoAsyncContext(WeakReference(this))
return BackgroundExecutor.submit {
return@submit try {
context.task()
} catch (thr: Throwable) {
val result = exceptionHandler?.invoke(thr)
result ?: Unit
}
}
}
fun <T> T.doAsync(
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
executorService: ExecutorService,
task: AnkoAsyncContext<T>.() -> Unit,
): Future<Unit> {
val context = AnkoAsyncContext(WeakReference(this))
return executorService.submit<Unit> {
try {
context.task()
} catch (thr: Throwable) {
exceptionHandler?.invoke(thr)
}
}
}
fun <T, R> T.doAsyncResult(
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
task: AnkoAsyncContext<T>.() -> R,
): Future<R> {
val context = AnkoAsyncContext(WeakReference(this))
return BackgroundExecutor.submit {
try {
context.task()
} catch (thr: Throwable) {
exceptionHandler?.invoke(thr)
throw thr
}
}
}
fun <T, R> T.doAsyncResult(
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
executorService: ExecutorService,
task: AnkoAsyncContext<T>.() -> R,
): Future<R> {
val context = AnkoAsyncContext(WeakReference(this))
return executorService.submit<R> {
try {
context.task()
} catch (thr: Throwable) {
exceptionHandler?.invoke(thr)
throw thr
}
}
}
internal object BackgroundExecutor {
@Suppress("MemberVisibilityCanBePrivate")
var executor: ExecutorService =
Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors())
fun <T> submit(task: () -> T): Future<T> = executor.submit(task)
}
private object ContextHelper {
val handler = Handler(Looper.getMainLooper())
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE")
package org.jetbrains.anko
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.SharedPreferences
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
inline val AnkoContext<*>.resources: Resources
get() = ctx.resources
inline val AnkoContext<*>.assets: AssetManager
get() = ctx.assets
inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(ctx)
inline val Context.defaultSharedPreferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(this)
inline val Fragment.defaultSharedPreferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
inline val Fragment.act: Activity?
get() = activity
inline val Fragment.ctx: Context?
get() = context
@Deprecated(message = "Inline", replaceWith = ReplaceWith("this"))
inline val Context.ctx: Context
get() = this
@Deprecated(message = "Inline", replaceWith = ReplaceWith("this"))
inline val Activity.act: Activity
get() = this
/**
* Returns the content view of this Activity if set, null otherwise.
*/
inline val Activity.contentView: View?
get() = findOptional<ViewGroup>(android.R.id.content)?.getChildAt(0)
inline fun <reified T : View> View.find(@IdRes id: Int): T = findViewById(id)
inline fun <reified T : View> Activity.find(@IdRes id: Int): T = findViewById(id)
inline fun <reified T : View> Fragment.find(@IdRes id: Int): T = view?.findViewById(id) as T
inline fun <reified T : View> Dialog.find(@IdRes id: Int): T = findViewById(id)
inline fun <reified T : View> View.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Activity.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Fragment.findOptional(@IdRes id: Int): T? =
view?.findViewById(id) as? T
inline fun <reified T : View> Dialog.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
//@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
//inline fun <T : Fragment> T.withArguments(vararg params: Pair<String, Any?>): T {
// arguments = bundleOf(*params)
// return this
//}
//@Deprecated(
// message = "Use the Android KTX version",
// replaceWith = ReplaceWith("bundleOf(*params)", "androidx.core.os.bundleOf")
//)
//fun bundleOf(vararg params: Pair<String, Any?>): Bundle {
// val b = Bundle()
// for (p in params) {
// val (k, v) = p
// when (v) {
// null -> b.putSerializable(k, null)
// is Boolean -> b.putBoolean(k, v)
// is Byte -> b.putByte(k, v)
// is Char -> b.putChar(k, v)
// is Short -> b.putShort(k, v)
// is Int -> b.putInt(k, v)
// is Long -> b.putLong(k, v)
// is Float -> b.putFloat(k, v)
// is Double -> b.putDouble(k, v)
// is String -> b.putString(k, v)
// is CharSequence -> b.putCharSequence(k, v)
// is Parcelable -> b.putParcelable(k, v)
// is Serializable -> b.putSerializable(k, v)
// is BooleanArray -> b.putBooleanArray(k, v)
// is ByteArray -> b.putByteArray(k, v)
// is CharArray -> b.putCharArray(k, v)
// is DoubleArray -> b.putDoubleArray(k, v)
// is FloatArray -> b.putFloatArray(k, v)
// is IntArray -> b.putIntArray(k, v)
// is LongArray -> b.putLongArray(k, v)
// is Array<*> -> {
// @Suppress("UNCHECKED_CAST")
// when {
// v.isArrayOf<Parcelable>() -> b.putParcelableArray(k, v as Array<out Parcelable>)
// v.isArrayOf<CharSequence>() -> b.putCharSequenceArray(
// k,
// v as Array<out CharSequence>
// )
// v.isArrayOf<String>() -> b.putStringArray(k, v as Array<out String>)
// else -> throw AnkoException("Unsupported bundle component (${v.javaClass})")
// }
// }
// is ShortArray -> b.putShortArray(k, v)
// is Bundle -> b.putBundle(k, v)
// else -> throw AnkoException("Unsupported bundle component (${v.javaClass})")
// }
// }
//
// return b
//}
inline val Context.displayMetrics: android.util.DisplayMetrics
get() = resources.displayMetrics
inline val Context.configuration: Configuration
get() = resources.configuration
inline val AnkoContext<*>.displayMetrics: android.util.DisplayMetrics
get() = ctx.resources.displayMetrics
inline val AnkoContext<*>.configuration: Configuration
get() = ctx.resources.configuration
inline val Configuration.portrait: Boolean
get() = orientation == Configuration.ORIENTATION_PORTRAIT
inline val Configuration.landscape: Boolean
get() = orientation == Configuration.ORIENTATION_LANDSCAPE
inline val Configuration.long: Boolean
get() = (screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused", "TopLevelPropertyNaming")
package org.jetbrains.anko
import android.view.ViewGroup
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
import org.jetbrains.anko.internals.AnkoInternals.noGetter
import kotlin.DeprecationLevel.ERROR
const val matchParent: Int = ViewGroup.LayoutParams.MATCH_PARENT
const val wrapContent: Int = ViewGroup.LayoutParams.WRAP_CONTENT
var ViewGroup.MarginLayoutParams.verticalMargin: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(v) {
topMargin = v
bottomMargin = v
}
var ViewGroup.MarginLayoutParams.horizontalMargin: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(v) {
leftMargin = v
rightMargin = v
}
var ViewGroup.MarginLayoutParams.margin: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(v) {
leftMargin = v
rightMargin = v
topMargin = v
bottomMargin = v
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.content.Context
//inline val Context.vibrator: android.os.Vibrator
// get() = getSystemService(Context.VIBRATOR_SERVICE) as android.os.Vibrator
inline val Context.layoutInflater: android.view.LayoutInflater
get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as android.view.LayoutInflater

View File

@ -0,0 +1,107 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
import org.jetbrains.anko.internals.AnkoInternals.noGetter
import kotlin.DeprecationLevel.ERROR
var View.backgroundDrawable: Drawable?
inline get() = background
set(value) {
background = value
}
var View.backgroundColorResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(colorId) = setBackgroundColor(ContextCompat.getColor(context, colorId))
var View.leftPadding: Int
inline get() = paddingLeft
set(value) = setPadding(value, paddingTop, paddingRight, paddingBottom)
var View.topPadding: Int
inline get() = paddingTop
set(value) = setPadding(paddingLeft, value, paddingRight, paddingBottom)
var View.rightPadding: Int
inline get() = paddingRight
set(value) = setPadding(paddingLeft, paddingTop, value, paddingBottom)
var View.bottomPadding: Int
inline get() = paddingBottom
set(value) = setPadding(paddingLeft, paddingTop, paddingRight, value)
@Deprecated("Use horizontalPadding instead", ReplaceWith("horizontalPadding"))
var View.paddingHorizontal: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setPadding(value, paddingTop, value, paddingBottom)
var View.horizontalPadding: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setPadding(value, paddingTop, value, paddingBottom)
@Deprecated("Use verticalPadding instead", ReplaceWith("verticalPadding"))
var View.paddingVertical: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setPadding(paddingLeft, value, paddingRight, value)
var View.verticalPadding: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setPadding(paddingLeft, value, paddingRight, value)
var View.padding: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
inline set(value) = setPadding(value, value, value, value)
var TextView.allCaps: Boolean
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
inline set(value) {
isAllCaps = value
}
var TextView.ems: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
inline set(value) = setEms(value)
inline var TextView.isSelectable: Boolean
get() = isTextSelectable
set(value) = setTextIsSelectable(value)
var TextView.textAppearance: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setTextAppearance(value)
var TextView.textSizeDimen: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(value) = setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(value))
var TextView.textColorResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
set(colorId) = setTextColor(ContextCompat.getColor(context, colorId))
var ImageView.image: Drawable?
inline get() = drawable
inline set(value) = setImageDrawable(value)

View File

@ -0,0 +1,164 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused", "MatchingDeclarationName", "ClassNaming")
package org.jetbrains.anko
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.ProgressBar
import org.jetbrains.anko.custom.ankoView
@PublishedApi
internal object `$$Anko$Factories$CustomViews` {
val VERTICAL_LAYOUT_FACTORY = { ctx: Context ->
val view = _LinearLayout(ctx)
view.orientation = LinearLayout.VERTICAL
view
}
val EDIT_TEXT = { ctx: Context -> EditText(ctx) }
val HORIZONTAL_PROGRESS_BAR_FACTORY = { ctx: Context ->
ProgressBar(ctx, null, android.R.attr.progressBarStyleHorizontal)
}
}
inline fun ViewManager.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
inline fun ViewManager.verticalLayout(
theme: Int = 0,
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
inline fun Context.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
inline fun Context.verticalLayout(
theme: Int = 0,
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
inline fun Activity.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
inline fun Activity.verticalLayout(
theme: Int = 0,
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
inline fun ViewManager.editText(constraints: InputConstraints, theme: Int = 0): EditText =
editText(constraints, theme) {}
inline fun ViewManager.editText(
constraints: InputConstraints,
theme: Int = 0,
init: (@AnkoViewDslMarker EditText).() -> Unit,
): EditText {
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
v.inputType = constraints.value
return v
}
inline fun Context.editText(constraints: InputConstraints, theme: Int = 0): EditText =
editText(constraints, theme) {}
inline fun Context.editText(
constraints: InputConstraints,
theme: Int = 0,
init: (@AnkoViewDslMarker EditText).() -> Unit,
): EditText {
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
v.inputType = constraints.value
return v
}
inline fun Activity.editText(constraints: InputConstraints, theme: Int = 0): EditText =
editText(constraints, theme) {}
inline fun Activity.editText(
constraints: InputConstraints,
theme: Int = 0,
init: (@AnkoViewDslMarker EditText).() -> Unit,
): EditText {
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
v.inputType = constraints.value
return v
}
inline fun ViewManager.horizontalProgressBar(theme: Int = 0): ProgressBar =
horizontalProgressBar(theme) {}
inline fun ViewManager.horizontalProgressBar(
theme: Int = 0,
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
): ProgressBar {
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
}
inline fun Context.horizontalProgressBar(theme: Int = 0): ProgressBar =
horizontalProgressBar(theme) {}
inline fun Context.horizontalProgressBar(
theme: Int = 0,
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
): ProgressBar {
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
}
inline fun Activity.horizontalProgressBar(theme: Int = 0): ProgressBar =
horizontalProgressBar(theme) {}
inline fun Activity.horizontalProgressBar(
theme: Int = 0,
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
): ProgressBar {
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
}
inline fun <T : View> ViewManager.include(layoutId: Int): T = include(layoutId, {})
inline fun <T : View> ViewManager.include(
layoutId: Int,
init: (@AnkoViewDslMarker T).() -> Unit,
): T {
@Suppress("UNCHECKED_CAST")
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
}
inline fun <T : View> ViewGroup.include(layoutId: Int): T = include(layoutId, {})
inline fun <T : View> ViewGroup.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
@Suppress("UNCHECKED_CAST")
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, this, false) as T }, 0) { init() }
}
inline fun <T : View> Context.include(layoutId: Int): T = include(layoutId, {})
inline fun <T : View> Context.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
@Suppress("UNCHECKED_CAST")
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
}
inline fun <T : View> Activity.include(layoutId: Int): T = include(layoutId, {})
inline fun <T : View> Activity.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
@Suppress("UNCHECKED_CAST")
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.content.DialogInterface
import androidx.fragment.app.Fragment
typealias AlertBuilderFactory<D> = (Context) -> AlertBuilder<D>
inline fun <D : DialogInterface> AnkoContext<*>.alert(
noinline factory: AlertBuilderFactory<D>,
message: String,
title: String? = null,
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
) = ctx.alert(factory, message, title, init)
inline fun <D : DialogInterface> Fragment.alert(
noinline factory: AlertBuilderFactory<D>,
message: String,
title: String? = null,
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
) = requireActivity().alert(factory, message, title, init)
fun <D : DialogInterface> Context.alert(
factory: AlertBuilderFactory<D>,
message: String,
title: String? = null,
init: (AlertBuilder<D>.() -> Unit)? = null,
): AlertBuilder<D> {
return factory(this).apply {
if (title != null) {
this.title = title
}
this.message = message
if (init != null) init()
}
}
inline fun <D : DialogInterface> AnkoContext<*>.alert(
noinline factory: AlertBuilderFactory<D>,
message: Int,
title: Int? = null,
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
) = ctx.alert(factory, message, title, init)
inline fun <D : DialogInterface> Fragment.alert(
noinline factory: AlertBuilderFactory<D>,
message: Int,
title: Int? = null,
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
) = requireActivity().alert(factory, message, title, init)
fun <D : DialogInterface> Context.alert(
factory: AlertBuilderFactory<D>,
messageResource: Int,
titleResource: Int? = null,
init: (AlertBuilder<D>.() -> Unit)? = null,
): AlertBuilder<D> {
return factory(this).apply {
if (titleResource != null) {
this.titleResource = titleResource
}
this.messageResource = messageResource
if (init != null) init()
}
}
inline fun <D : DialogInterface> AnkoContext<*>.alert(
noinline factory: AlertBuilderFactory<D>,
noinline init: AlertBuilder<D>.() -> Unit,
) = ctx.alert(factory, init)
inline fun <D : DialogInterface> Fragment.alert(
noinline factory: AlertBuilderFactory<D>,
noinline init: AlertBuilder<D>.() -> Unit,
) = requireActivity().alert(factory, init)
fun <D : DialogInterface> Context.alert(
factory: AlertBuilderFactory<D>,
init: AlertBuilder<D>.() -> Unit,
): AlertBuilder<D> = factory(this).apply { init() }

View File

@ -0,0 +1,77 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.util.DisplayMetrics
import android.view.View
import androidx.annotation.DimenRes
import androidx.fragment.app.Fragment
const val LDPI: Int = DisplayMetrics.DENSITY_LOW
const val MDPI: Int = DisplayMetrics.DENSITY_MEDIUM
const val HDPI: Int = DisplayMetrics.DENSITY_HIGH
const val TVDPI: Int = DisplayMetrics.DENSITY_TV
const val XHDPI: Int = DisplayMetrics.DENSITY_XHIGH
const val XXHDPI: Int = DisplayMetrics.DENSITY_XXHIGH
const val XXXHDPI: Int = DisplayMetrics.DENSITY_XXXHIGH
const val MAXDPI: Int = 0xfffe
//returns dip(dp) dimension value in pixels
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
fun Context.dip(value: Float): Int = (value * resources.displayMetrics.density).toInt()
//return sp dimension value in pixels
fun Context.sp(value: Int): Int = (value * resources.displayMetrics.scaledDensity).toInt()
fun Context.sp(value: Float): Int = (value * resources.displayMetrics.scaledDensity).toInt()
//converts px value into dip or sp
fun Context.px2dip(px: Int): Float = px.toFloat() / resources.displayMetrics.density
fun Context.px2sp(px: Int): Float = px.toFloat() / resources.displayMetrics.scaledDensity
fun Context.dimen(@DimenRes resource: Int): Int = resources.getDimensionPixelSize(resource)
//the same for nested DSL components
inline fun AnkoContext<*>.dip(value: Int): Int = ctx.dip(value)
inline fun AnkoContext<*>.dip(value: Float): Int = ctx.dip(value)
inline fun AnkoContext<*>.sp(value: Int): Int = ctx.sp(value)
inline fun AnkoContext<*>.sp(value: Float): Int = ctx.sp(value)
inline fun AnkoContext<*>.px2dip(px: Int): Float = ctx.px2dip(px)
inline fun AnkoContext<*>.px2sp(px: Int): Float = ctx.px2sp(px)
inline fun AnkoContext<*>.dimen(@DimenRes resource: Int): Int = ctx.dimen(resource)
//the same for the views
inline fun View.dip(value: Int): Int = context.dip(value)
inline fun View.dip(value: Float): Int = context.dip(value)
inline fun View.sp(value: Int): Int = context.sp(value)
inline fun View.sp(value: Float): Int = context.sp(value)
inline fun View.px2dip(px: Int): Float = context.px2dip(px)
inline fun View.px2sp(px: Int): Float = context.px2sp(px)
inline fun View.dimen(@DimenRes resource: Int): Int = context.dimen(resource)
//the same for Fragments
inline fun Fragment.dip(value: Int): Int = requireContext().dip(value)
inline fun Fragment.dip(value: Float): Int = requireContext().dip(value)
inline fun Fragment.sp(value: Int): Int = requireContext().sp(value)
inline fun Fragment.sp(value: Float): Int = requireContext().sp(value)
inline fun Fragment.px2dip(px: Int): Float = requireContext().px2dip(px)
inline fun Fragment.px2sp(px: Int): Float = requireContext().px2sp(px)
inline fun Fragment.dimen(@DimenRes resource: Int): Int = requireContext().dimen(resource)

View File

@ -0,0 +1,301 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.app.Activity
import android.content.Context
import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.fragment.app.Fragment
import org.jetbrains.anko.internals.AnkoInternals
open class AnkoException(message: String = "") : RuntimeException(message)
/**
* Return the grayscale color with the zero opacity using the single color value.
* E.g., 0xC0 will be translated to 0xC0C0C0.
*/
val Int.gray: Int
get() = this or (this shl 8) or (this shl 16)
/**
* Return the color with 0xFF opacity.
* E.g., 0xabcdef will be translated to 0xFFabcdef.
*/
val Int.opaque: Int
get() = this or 0xff000000.toInt()
/**
* Return the color with the given alpha value.
* Examples:
* 0xabcdef.withAlpha(0xCF) == 0xCFabcdef
* 0xFFabcdef.withAlpha(0xCF) == 0xCFabcdef
*
* @param alpha the alpha channel value: [0x0..0xFF].
* @return the color with the given alpha value applied.
*/
fun Int.withAlpha(alpha: Int): Int {
require(alpha in 0..0xFF)
return this and 0x00FFFFFF or (alpha shl 24)
}
enum class ScreenSize {
SMALL,
NORMAL,
LARGE,
XLARGE
}
enum class UiMode {
NORMAL,
CAR,
DESK,
TELEVISION,
APPLIANCE,
WATCH
}
enum class Orientation {
PORTRAIT,
LANDSCAPE,
SQUARE
}
/**
* Execute [f] if the device configuration matches all given constraints.
* You can use named arguments to provide only the relevant constraints.
* All null constraints are ignored.
*
* @param screenSize the required screen size.
* @param density the required screen density.
* @param language the required system language.
* @param orientation the current screen orientation.
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
* @param fromSdk the minimal SDK version for code to execute.
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
* (lower or higher than the given value).
* @param uiMode the required interface mode.
* @param nightMode true, if the device should be in the night mode, false if should not.
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
* @param smallestWidth the required smallest width of the screen.
*/
inline fun <T : Any> Context.configuration(
screenSize: ScreenSize? = null,
density: ClosedRange<Int>? = null,
language: String? = null,
orientation: Orientation? = null,
long: Boolean? = null,
fromSdk: Int? = null,
sdk: Int? = null,
uiMode: UiMode? = null,
nightMode: Boolean? = null,
rightToLeft: Boolean? = null,
smallestWidth: Int? = null,
f: () -> T,
): T? = if (AnkoInternals.testConfiguration(
this, screenSize, density, language, orientation, long,
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
)
) f() else null
/**
* Execute [f] if the device configuration matches all given constraints.
* You can use named arguments to provide only the relevant constraints.
* All null constraints are ignored.
*
* @param screenSize the required screen size.
* @param density the required screen density.
* @param language the required system language.
* @param orientation the current screen orientation.
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
* @param fromSdk the minimal SDK version for code to execute.
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
* (lower or higher than the given value).
* @param uiMode the required interface mode.
* @param nightMode true, if the device should be in the night mode, false if should not.
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
* @param smallestWidth the required smallest width of the screen.
*/
inline fun <T : Any> Activity.configuration(
screenSize: ScreenSize? = null,
density: ClosedRange<Int>? = null,
language: String? = null,
orientation: Orientation? = null,
long: Boolean? = null,
fromSdk: Int? = null,
sdk: Int? = null,
uiMode: UiMode? = null,
nightMode: Boolean? = null,
rightToLeft: Boolean? = null,
smallestWidth: Int? = null,
f: () -> T,
): T? = if (AnkoInternals.testConfiguration(
this, screenSize, density, language, orientation, long,
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
)
) f() else null
/**
* Execute [f] if the device configuration matches all given constraints.
* You can use named arguments to provide only the relevant constraints.
* All null constraints are ignored.
*
* @param screenSize the required screen size.
* @param density the required screen density.
* @param language the required system language.
* @param orientation the current screen orientation.
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
* @param fromSdk the minimal SDK version for code to execute.
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
* (lower or higher than the given value).
* @param uiMode the required interface mode.
* @param nightMode true, if the device should be in the night mode, false if should not.
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
* @param smallestWidth the required smallest width of the screen.
*/
inline fun <T : Any> AnkoContext<*>.configuration(
screenSize: ScreenSize? = null,
density: ClosedRange<Int>? = null,
language: String? = null,
orientation: Orientation? = null,
long: Boolean? = null,
fromSdk: Int? = null,
sdk: Int? = null,
uiMode: UiMode? = null,
nightMode: Boolean? = null,
rightToLeft: Boolean? = null,
smallestWidth: Int? = null,
f: () -> T,
): T? = if (AnkoInternals.testConfiguration(
ctx, screenSize, density, language, orientation, long,
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
)
) f() else null
/**
* Execute [f] if the device configuration matches all given constraints.
* You can use named arguments to provide only the relevant constraints.
* All null constraints are ignored.
*
* @param screenSize the required screen size.
* @param density the required screen density.
* @param language the required system language.
* @param orientation the current screen orientation.
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
* @param fromSdk the minimal SDK version for code to execute.
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
* (lower or higher than the given value).
* @param uiMode the required interface mode.
* @param nightMode true, if the device should be in the night mode, false if should not.
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
* @param smallestWidth the required smallest width of the screen.
*/
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
inline fun <T : Any> Fragment.configuration(
screenSize: ScreenSize? = null,
density: ClosedRange<Int>? = null,
language: String? = null,
orientation: Orientation? = null,
long: Boolean? = null,
fromSdk: Int? = null,
sdk: Int? = null,
uiMode: UiMode? = null,
nightMode: Boolean? = null,
rightToLeft: Boolean? = null,
smallestWidth: Int? = null,
f: () -> T,
): T? {
val act = activity
return if (act != null) {
if (AnkoInternals.testConfiguration(
act, screenSize, density, language, orientation, long,
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
)
) f() else null
} else null
}
/**
* Execute [f] only if the current Android SDK version is [version] or older.
* Do nothing otherwise.
*/
inline fun doBeforeSdk(version: Int, f: () -> Unit) {
if (Build.VERSION.SDK_INT <= version) f()
}
/**
* Execute [f] only if the current Android SDK version is [version] or newer.
* Do nothing otherwise.
*/
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
inline fun doFromSdk(version: Int, f: () -> Unit) {
if (Build.VERSION.SDK_INT >= version) f()
}
/**
* Execute [f] only if the current Android SDK version is [version].
* Do nothing otherwise.
*/
inline fun doIfSdk(version: Int, f: () -> Unit) {
if (Build.VERSION.SDK_INT == version) f()
}
/**
* Result of the [attempt] function.
* Either [value] or [error] is not null.
*
* @property value the return value if code execution was finished without an exception, null otherwise.
* @property error a caught [Throwable] or null if nothing was caught.
*/
data class AttemptResult<out T> @PublishedApi internal constructor(
val value: T?,
val error: Throwable?,
) {
inline fun <R> then(f: (T) -> R): AttemptResult<R> {
if (isError) {
@Suppress("UNCHECKED_CAST")
return this as AttemptResult<R>
}
return attempt {
@Suppress("UNCHECKED_CAST")
f(value as T)
}
}
inline val isError: Boolean
get() = error != null
inline val hasValue: Boolean
get() = error == null
}
/**
* Execute [f] and return the result or an exception, if an exception was occurred.
*/
inline fun <T> attempt(f: () -> T): AttemptResult<T> {
var value: T? = null
var error: Throwable? = null
try {
value = f()
} catch (t: Throwable) {
error = t
}
return AttemptResult(value, error)
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.text.InputType
enum class InputConstraints(val value: Int) {
PASSWORD(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
}

View File

@ -0,0 +1,239 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.fragment.app.Fragment
import org.jetbrains.anko.internals.AnkoInternals
inline fun <reified T : Activity> Context.startActivity(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartActivity(this, T::class.java, params)
inline fun <reified T : Activity> AnkoContext<*>.startActivity(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartActivity(ctx, T::class.java, params)
inline fun <reified T : Activity> Fragment.startActivity(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartActivity(requireActivity(), T::class.java, params)
inline fun <reified T : Activity> Activity.startActivityForResult(
requestCode: Int,
vararg params: Pair<String, Any?>,
) =
AnkoInternals.internalStartActivityForResult(this, T::class.java, requestCode, params)
@Suppress("DEPRECATION")
@Deprecated(
message = "startActivityForResult is deprecated at Android 12",
replaceWith = ReplaceWith("ActivityResult")
)
inline fun <reified T : Activity> Fragment.startActivityForResult(
requestCode: Int,
vararg params: Pair<String, Any?>,
) =
startActivityForResult(
AnkoInternals.createIntent(requireActivity(), T::class.java, params),
requestCode
)
inline fun <reified T : Service> Context.startService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartService(this, T::class.java, params)
inline fun <reified T : Service> AnkoContext<*>.startService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartService(ctx, T::class.java, params)
inline fun <reified T : Service> Fragment.startService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartService(requireActivity(), T::class.java, params)
inline fun <reified T : Service> Context.stopService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStopService(this, T::class.java, params)
inline fun <reified T : Service> AnkoContext<*>.stopService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStopService(ctx, T::class.java, params)
inline fun <reified T : Service> Fragment.stopService(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStopService(requireActivity(), T::class.java, params)
inline fun <reified T : Any> Context.intentFor(vararg params: Pair<String, Any?>): Intent =
AnkoInternals.createIntent(this, T::class.java, params)
inline fun <reified T : Any> AnkoContext<*>.intentFor(vararg params: Pair<String, Any?>): Intent =
AnkoInternals.createIntent(ctx, T::class.java, params)
inline fun <reified T : Any> Fragment.intentFor(vararg params: Pair<String, Any?>): Intent =
AnkoInternals.createIntent(requireActivity(), T::class.java, params)
/**
* Add the [Intent.FLAG_ACTIVITY_CLEAR_TASK] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.clearTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) }
/**
* Add the [Intent.FLAG_ACTIVITY_CLEAR_TOP] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.clearTop(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) }
/**
* Add the [Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
@Deprecated(
message = "Deprecated in Android",
replaceWith = ReplaceWith("org.jetbrains.anko.newDocument")
)
inline fun Intent.clearWhenTaskReset(): Intent = apply {
@Suppress("DEPRECATION")
addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
}
/**
* Add the [Intent.FLAG_ACTIVITY_NEW_DOCUMENT] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.newDocument(): Intent = apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
}
/**
* Add the [Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.excludeFromRecents(): Intent =
apply { addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) }
/**
* Add the [Intent.FLAG_ACTIVITY_MULTIPLE_TASK] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.multipleTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) }
/**
* Add the [Intent.FLAG_ACTIVITY_NEW_TASK] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.newTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
/**
* Add the [Intent.FLAG_ACTIVITY_NO_ANIMATION] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.noAnimation(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) }
/**
* Add the [Intent.FLAG_ACTIVITY_NO_HISTORY] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.noHistory(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) }
/**
* Add the [Intent.FLAG_ACTIVITY_SINGLE_TOP] flag to the [Intent].
*
* @return the same intent with the flag applied.
*/
inline fun Intent.singleTop(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) }
inline fun AnkoContext<*>.browse(url: String, newTask: Boolean = false) = ctx.browse(url, newTask)
inline fun Fragment.browse(url: String, newTask: Boolean = false) =
requireActivity().browse(url, newTask)
fun Context.browse(url: String, newTask: Boolean = false) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
// ActivityNotFoundException
}
inline fun AnkoContext<*>.share(text: String, subject: String = "", title: String? = null) =
ctx.share(text, subject, title)
inline fun Fragment.share(text: String, subject: String = "", title: String? = null) =
requireActivity().share(text, subject, title)
fun Context.share(text: String, subject: String = "", title: String? = null) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
intent.putExtra(Intent.EXTRA_TEXT, text)
startActivity(Intent.createChooser(intent, title))
// ActivityNotFoundException
}
inline fun AnkoContext<*>.email(email: String, subject: String = "", text: String = "") =
ctx.email(email, subject, text)
inline fun Fragment.email(email: String, subject: String = "", text: String = "") =
requireActivity().email(email, subject, text)
@SuppressLint("QueryPermissionsNeeded")
fun Context.email(email: String, subject: String = "", text: String = ""): Boolean {
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
if (subject.isNotEmpty())
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
if (text.isNotEmpty())
intent.putExtra(Intent.EXTRA_TEXT, text)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
return true
}
return false
}
inline fun AnkoContext<*>.makeCall(number: String) = ctx.makeCall(number)
inline fun Fragment.makeCall(number: String) = requireActivity().makeCall(number)
fun Context.makeCall(number: String) {
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$number"))
startActivity(intent)
// ActivityNotFoundException
}
inline fun AnkoContext<*>.sendSMS(number: String, text: String = "") =
ctx.sendSMS(number, text)
inline fun Fragment.sendSMS(number: String, text: String = "") =
requireActivity().sendSMS(number, text)
fun Context.sendSMS(number: String, text: String = "") {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:$number"))
intent.putExtra("sms_body", text)
startActivity(intent)
// ActivityNotFoundException
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,278 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE", "MatchingDeclarationName", "NAME_SHADOWING")
@file:JvmName("Logging")
package org.jetbrains.anko
import android.util.Log
/**
* Interface for the Anko logger.
* Normally you should pass the logger tag to the [Log] methods, such as [Log.d] or [Log.e].
* This can be inconvenient because you should store the tag somewhere or hardcode it,
* which is considered to be a bad practice.
*
* Instead of hardcoding tags, Anko provides an [AnkoLogger] interface. You can just add the interface to
* any of your classes, and use any of the provided extension functions, such as
* [AnkoLogger.debug] or [AnkoLogger.error].
*
* The tag is the simple class name by default, but you can change it to anything you want just
* by overriding the [loggerTag] property.
*/
interface AnkoLogger {
/**
* The logger tag used in extension functions for the [AnkoLogger].
* Note that the tag length should not be more than 23 symbols.
*/
val loggerTag: String
get() = getTag(javaClass)
}
fun AnkoLogger(clazz: Class<*>): AnkoLogger = object : AnkoLogger {
override val loggerTag = getTag(clazz)
}
fun AnkoLogger(tag: String): AnkoLogger = object : AnkoLogger {
init {
assert(tag.length <= 23) { "The maximum tag length is 23, got $tag" }
}
override val loggerTag = tag
}
inline fun <reified T : Any> AnkoLogger(): AnkoLogger = AnkoLogger(T::class.java)
/**
* Send a log message with the [Log.VERBOSE] severity.
* Note that the log message will not be written if the current log level is above [Log.VERBOSE].
* The default log level is [Log.INFO].
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.v].
*/
fun AnkoLogger.verbose(message: Any?, thr: Throwable? = null) {
log(this, message, thr, Log.VERBOSE,
{ tag, msg -> Log.v(tag, msg) },
{ tag, msg, thr -> Log.v(tag, msg, thr) })
}
/**
* Send a log message with the [Log.DEBUG] severity.
* Note that the log message will not be written if the current log level is above [Log.DEBUG].
* The default log level is [Log.INFO].
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.d].
*/
fun AnkoLogger.debug(message: Any?, thr: Throwable? = null) {
log(this, message, thr, Log.DEBUG,
{ tag, msg -> Log.d(tag, msg) },
{ tag, msg, thr -> Log.d(tag, msg, thr) })
}
/**
* Send a log message with the [Log.INFO] severity.
* Note that the log message will not be written if the current log level is above [Log.INFO]
* (it is the default level).
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.i].
*/
fun AnkoLogger.info(message: Any?, thr: Throwable? = null) {
log(this, message, thr, Log.INFO,
{ tag, msg -> Log.i(tag, msg) },
{ tag, msg, thr -> Log.i(tag, msg, thr) })
}
/**
* Send a log message with the [Log.WARN] severity.
* Note that the log message will not be written if the current log level is above [Log.WARN].
* The default log level is [Log.INFO].
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.w].
*/
fun AnkoLogger.warn(message: Any?, thr: Throwable? = null) {
log(this, message, thr, Log.WARN,
{ tag, msg -> Log.w(tag, msg) },
{ tag, msg, thr -> Log.w(tag, msg, thr) })
}
/**
* Send a log message with the [Log.ERROR] severity.
* Note that the log message will not be written if the current log level is above [Log.ERROR].
* The default log level is [Log.INFO].
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.e].
*/
fun AnkoLogger.error(message: Any?, thr: Throwable? = null) {
log(this, message, thr, Log.ERROR,
{ tag, msg -> Log.e(tag, msg) },
{ tag, msg, thr -> Log.e(tag, msg, thr) })
}
/**
* Send a log message with the "What a Terrible Failure" severity.
* Report an exception that should never happen.
*
* @param message the message text to log. `null` value will be represent as "null", for any other value
* the [Any.toString] will be invoked.
* @param thr an exception to log (optional).
*
* @see [Log.wtf].
*/
fun AnkoLogger.wtf(message: Any?, thr: Throwable? = null) {
if (thr != null) {
Log.wtf(loggerTag, message?.toString() ?: "null", thr)
} else {
Log.wtf(loggerTag, message?.toString() ?: "null")
}
}
/**
* Send a log message with the [Log.VERBOSE] severity.
* Note that the log message will not be written if the current log level is above [Log.VERBOSE].
* The default log level is [Log.INFO].
*
* @param message the function that returns message text to log.
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
*
* @see [Log.v].
*/
inline fun AnkoLogger.verbose(message: () -> Any?) {
val tag = loggerTag
if (Log.isLoggable(tag, Log.VERBOSE)) {
Log.v(tag, message()?.toString() ?: "null")
}
}
/**
* Send a log message with the [Log.DEBUG] severity.
* Note that the log message will not be written if the current log level is above [Log.DEBUG].
* The default log level is [Log.INFO].
*
* @param message the function that returns message text to log.
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
*
* @see [Log.d].
*/
inline fun AnkoLogger.debug(message: () -> Any?) {
val tag = loggerTag
if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, message()?.toString() ?: "null")
}
}
/**
* Send a log message with the [Log.INFO] severity.
* Note that the log message will not be written if the current log level is above [Log.INFO].
* The default log level is [Log.INFO].
*
* @param message the function that returns message text to log.
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
*
* @see [Log.i].
*/
inline fun AnkoLogger.info(message: () -> Any?) {
val tag = loggerTag
if (Log.isLoggable(tag, Log.INFO)) {
Log.i(tag, message()?.toString() ?: "null")
}
}
/**
* Send a log message with the [Log.WARN] severity.
* Note that the log message will not be written if the current log level is above [Log.WARN].
* The default log level is [Log.INFO].
*
* @param message the function that returns message text to log.
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
*
* @see [Log.w].
*/
inline fun AnkoLogger.warn(message: () -> Any?) {
val tag = loggerTag
if (Log.isLoggable(tag, Log.WARN)) {
Log.w(tag, message()?.toString() ?: "null")
}
}
/**
* Send a log message with the [Log.ERROR] severity.
* Note that the log message will not be written if the current log level is above [Log.ERROR].
* The default log level is [Log.INFO].
*
* @param message the function that returns message text to log.
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
*
* @see [Log.e].
*/
inline fun AnkoLogger.error(message: () -> Any?) {
val tag = loggerTag
if (Log.isLoggable(tag, Log.ERROR)) {
Log.e(tag, message()?.toString() ?: "null")
}
}
/**
* Return the stack trace [String] of a throwable.
*/
inline fun Throwable.getStackTraceString(): String = Log.getStackTraceString(this)
private inline fun log(
logger: AnkoLogger,
message: Any?,
thr: Throwable?,
level: Int,
f: (String, String) -> Unit,
fThrowable: (String, String, Throwable) -> Unit,
) {
val tag = logger.loggerTag
if (Log.isLoggable(tag, level)) {
if (thr != null) {
fThrowable(tag, message?.toString() ?: "null", thr)
} else {
f(tag, message?.toString() ?: "null")
}
}
}
private fun getTag(clazz: Class<*>): String {
val tag = clazz.simpleName
return if (tag.length <= 23) {
tag
} else {
tag.substring(0, 23)
}
}

View File

@ -0,0 +1,151 @@
package org.jetbrains.anko
import android.view.Menu
import android.view.MenuItem
import android.view.SubMenu
/**
* Create a plain menu item
*/
fun Menu.item(
title: CharSequence, /*@DrawableRes*/
icon: Int = 0,
checkable: Boolean = false,
): MenuItem =
add(title).apply {
setIcon(icon)
isCheckable = checkable
}
/**
* Create a menu item and configure it
*/
inline fun Menu.item(
title: CharSequence, /*@DrawableRes*/
icon: Int = 0,
checkable: Boolean = false,
configure: MenuItem.() -> Unit,
): MenuItem =
add(title).apply {
setIcon(icon)
isCheckable = checkable
configure()
}
/**
* Create a menu item with title from resources
*/
fun Menu.item(
/*@StringRes*/
titleRes: Int, /*@DrawableRes*/
icon: Int = 0,
checkable: Boolean = false,
): MenuItem =
add(titleRes).apply {
setIcon(icon)
isCheckable = checkable
}
/**
* Create a menu item with title from resources and configure it
*/
inline fun Menu.item(
/*@StringRes*/
titleRes: Int, /*@DrawableRes*/
icon: Int = 0,
checkable: Boolean = false,
configure: MenuItem.() -> Unit,
): MenuItem =
add(titleRes).apply {
setIcon(icon)
isCheckable = checkable
configure()
}
/**
* Create a submenu
*/
fun Menu.subMenu(title: CharSequence): SubMenu =
addSubMenu(title)
/**
* Create a submenu and configure it
*/
inline fun Menu.subMenu(title: CharSequence, configure: SubMenu.() -> Unit): SubMenu =
addSubMenu(title).apply { configure() }
/**
* Create a submenu with title from resources
*/
fun Menu.subMenu(/*@StringRes*/ titleRes: Int): SubMenu =
addSubMenu(titleRes)
/**
* Create a submenu with title from resources and configure it
*/
inline fun Menu.subMenu(/*@StringRes*/ titleRes: Int, configure: SubMenu.() -> Unit): SubMenu =
addSubMenu(titleRes).apply { configure() }
/**
* Create a checkable menu item for use in NavigationView
*/
fun Menu.navigationItem(title: CharSequence, /*@DrawableRes*/ icon: Int = 0) {
add(title).apply {
setIcon(icon)
isCheckable = true
}
}
/**
* Create a navigation item with OnClickListener
*/
inline fun Menu.navigationItem(
title: CharSequence, /*@DrawableRes*/
icon: Int = 0,
crossinline onClick: () -> Unit,
) {
add(title).apply {
setIcon(icon)
isCheckable = true
setOnMenuItemClickListener {
onClick()
false
}
}
}
/**
* Create a navigation item with title from resources
*/
fun Menu.navigationItem(/*@StringRes*/ titleRes: Int, /*@DrawableRes*/ icon: Int = 0) {
add(titleRes).apply {
setIcon(icon)
isCheckable = true
}
}
/**
* Create a navigation item with title from resources and onClick listener
*/
inline fun Menu.navigationItem(
/*@StringRes*/
titleRes: Int, /*@DrawableRes*/
icon: Int = 0,
crossinline onClick: () -> Unit,
) {
add(titleRes).apply {
setIcon(icon)
isCheckable = true
setOnMenuItemClickListener {
onClick()
false
}
}
}
/**
* Set OnClickListener on a menu item
*/
inline fun MenuItem.onClick(consume: Boolean = true, crossinline action: () -> Unit) {
setOnMenuItemClickListener { action(); consume }
}

View File

@ -0,0 +1,198 @@
@file:JvmName("Sdk28PropertiesKt")
package org.jetbrains.anko
import org.jetbrains.anko.internals.AnkoInternals
var android.view.View.backgroundColor: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setBackgroundColor(v)
var android.view.View.backgroundResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setBackgroundResource(v)
var android.widget.ImageView.imageResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setImageResource(v)
var android.widget.ImageView.imageURI: android.net.Uri?
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setImageURI(v)
var android.widget.ImageView.imageBitmap: android.graphics.Bitmap?
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setImageBitmap(v)
var android.widget.TextView.textColor: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setTextColor(v)
var android.widget.TextView.hintTextColor: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setHintTextColor(v)
var android.widget.TextView.linkTextColor: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setLinkTextColor(v)
var android.widget.TextView.lines: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setLines(v)
var android.widget.TextView.singleLine: Boolean
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) {
isSingleLine = v
}
var android.widget.RelativeLayout.horizontalGravity: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setHorizontalGravity(v)
var android.widget.RelativeLayout.verticalGravity: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setVerticalGravity(v)
var android.widget.LinearLayout.horizontalGravity: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setHorizontalGravity(v)
var android.widget.LinearLayout.verticalGravity: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setVerticalGravity(v)
//var android.widget.Gallery.gravity: Int
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
// set(v) = setGravity(v)
var android.widget.AbsListView.selectorResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setSelector(v)
//var android.widget.CalendarView.selectedDateVerticalBarResource: Int
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
// set(v) = setSelectedDateVerticalBar(v)
var android.widget.CheckedTextView.checkMarkDrawableResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setCheckMarkDrawable(v)
var android.widget.CompoundButton.buttonDrawableResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setButtonDrawable(v)
//var android.widget.TabWidget.leftStripDrawableResource: Int
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
// set(v) = setLeftStripDrawable(v)
//
//var android.widget.TabWidget.rightStripDrawableResource: Int
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
// set(v) = setRightStripDrawable(v)
var android.widget.TextView.hintResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setHint(v)
var android.widget.TextView.textResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setText(v)
var android.widget.Toolbar.logoResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setLogo(v)
var android.widget.Toolbar.logoDescriptionResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setLogoDescription(v)
var android.widget.Toolbar.navigationContentDescriptionResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setNavigationContentDescription(v)
var android.widget.Toolbar.navigationIconResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setNavigationIcon(v)
var android.widget.Toolbar.subtitleResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setSubtitle(v)
var android.widget.Toolbar.titleResource: Int
@Deprecated(
AnkoInternals.NO_GETTER,
level = DeprecationLevel.ERROR
) get() = AnkoInternals.noGetter()
set(v) = setTitle(v)

View File

@ -0,0 +1,292 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.view.View
import android.widget.RelativeLayout.*
import androidx.annotation.IdRes
/**
* Place the current View above [view].
* It is an alias for [above].
*/
fun LayoutParams.topOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ABOVE, id)
}
/**
* Place the current View above [view].
*/
fun LayoutParams.above(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ABOVE, id)
}
/**
* Place the current View below [view].
* It is an alias for [below].
*/
fun LayoutParams.bottomOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(BELOW, id)
}
/**
* Place the current View below [view].
*/
fun LayoutParams.below(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(BELOW, id)
}
/**
* Place the current View to the left of [view].
*/
inline fun LayoutParams.leftOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(LEFT_OF, id)
}
/**
* Place the current View to the start of [view].
*/
inline fun LayoutParams.startOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(START_OF, id)
}
/**
* Place the current View to the right of [view].
*/
inline fun LayoutParams.rightOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(RIGHT_OF, id)
}
/**
* Place the current View to the end of [view].
*/
inline fun LayoutParams.endOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(END_OF, id)
}
/**
* Set the current View left attribute the same as for [view].
*/
inline fun LayoutParams.sameLeft(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_LEFT, id)
}
/**
* Set the current View start attribute the same as for [view].
*/
inline fun LayoutParams.sameStart(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_START, id)
}
/**
* Set the current View top attribute the same as for [view].
*/
inline fun LayoutParams.sameTop(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_TOP, id)
}
/**
* Set the current View right attribute the same as for [view].
*/
inline fun LayoutParams.sameRight(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_RIGHT, id)
}
/**
* Set the current View end attribute the same as for [view].
*/
inline fun LayoutParams.sameEnd(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_END, id)
}
/**
* Set the current View bottom attribute the same as for [view].
*/
inline fun LayoutParams.sameBottom(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_BOTTOM, id)
}
/**
* Place the current View above the View with a given [id].
* It is an alias for [above].
*/
inline fun LayoutParams.topOf(@IdRes id: Int) = addRule(ABOVE, id)
/**
* Place the current View above the View with a given [id].
*/
inline fun LayoutParams.above(@IdRes id: Int) = addRule(ABOVE, id)
/**
* Place the current View below the View with a given [id].
*/
inline fun LayoutParams.below(@IdRes id: Int) = addRule(BELOW, id)
/**
* Place the current View below the View with a given [id].
* It is an alias for [below].
*/
inline fun LayoutParams.bottomOf(@IdRes id: Int) = addRule(BELOW, id)
/**
* Place the current View to the left of the View with a given [id].
*/
inline fun LayoutParams.leftOf(@IdRes id: Int) = addRule(LEFT_OF, id)
/**
* Place the current View to the start of the View with a given [id].
*/
inline fun LayoutParams.startOf(@IdRes id: Int): Unit = addRule(START_OF, id)
/**
* Place the current View to the left of the View with a given [id].
*/
inline fun LayoutParams.rightOf(@IdRes id: Int) = addRule(RIGHT_OF, id)
/**
* Place the current View to the end of the View with a given [id].
*/
inline fun LayoutParams.endOf(@IdRes id: Int): Unit = addRule(END_OF, id)
/**
* Set the current View left attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameLeft(@IdRes id: Int) = addRule(ALIGN_LEFT, id)
/**
* Set the current View start attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameStart(@IdRes id: Int): Unit = addRule(ALIGN_START, id)
/**
* Set the current View top attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameTop(@IdRes id: Int) = addRule(ALIGN_TOP, id)
/**
* Set the current View right attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameRight(@IdRes id: Int) = addRule(ALIGN_RIGHT, id)
/**
* Set the current View end attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameEnd(@IdRes id: Int): Unit = addRule(ALIGN_END, id)
/**
* Set the current View bottom attribute the same as for View with a given [id].
*/
inline fun LayoutParams.sameBottom(@IdRes id: Int) = addRule(ALIGN_BOTTOM, id)
/**
* Align the current View's start edge with another child's start edge.
*/
inline fun LayoutParams.alignStart(@IdRes id: Int): Unit = addRule(ALIGN_START, id)
/**
* Align the current View's end edge with another child's end edge.
*/
inline fun LayoutParams.alignEnd(@IdRes id: Int): Unit = addRule(ALIGN_END, id)
/**
* Align the current View's top edge with its parent's top edge.
*/
inline fun LayoutParams.alignParentTop() = addRule(ALIGN_PARENT_TOP)
/**
* Align the current View's right edge with its parent's right edge.
*/
inline fun LayoutParams.alignParentRight() = addRule(ALIGN_PARENT_RIGHT)
/**
* Align the current View's bottom edge with its parent's bottom edge.
*/
inline fun LayoutParams.alignParentBottom() = addRule(ALIGN_PARENT_BOTTOM)
/**
* Align the current View's left edge with its parent's left edge.
*/
inline fun LayoutParams.alignParentLeft() = addRule(ALIGN_PARENT_LEFT)
/**
* Center the child horizontally in its parent.
*/
inline fun LayoutParams.centerHorizontally() = addRule(CENTER_HORIZONTAL)
/**
* Center the child vertically in its parent.
*/
inline fun LayoutParams.centerVertically() = addRule(CENTER_VERTICAL)
/**
* Center the child horizontally and vertically in its parent.
*/
inline fun LayoutParams.centerInParent() = addRule(CENTER_IN_PARENT)
/**
* Align the current View's start edge with its parent's start edge.
*/
inline fun LayoutParams.alignParentStart(): Unit = addRule(ALIGN_PARENT_START)
/**
* Align the current View's end edge with its parent's end edge.
*/
inline fun LayoutParams.alignParentEnd(): Unit = addRule(ALIGN_PARENT_END)
/**
* Positions the baseline of this view on the baseline of the given anchor [view].
*/
inline fun LayoutParams.baselineOf(view: View) {
val id = view.id
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
addRule(ALIGN_BASELINE, id)
}
/**
* Positions the baseline of this view on the baseline of the anchor View with a given [id].
*/
inline fun LayoutParams.baselineOf(@IdRes id: Int) = addRule(ALIGN_BASELINE, id)

View File

@ -0,0 +1,52 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.content.DialogInterface
import androidx.fragment.app.Fragment
inline fun <D : DialogInterface> AnkoContext<*>.selector(
noinline factory: AlertBuilderFactory<D>,
title: CharSequence? = null,
items: List<CharSequence>,
noinline onClick: (DialogInterface, CharSequence, Int) -> Unit,
) = ctx.selector(factory, title, items, onClick)
inline fun <D : DialogInterface> Fragment.selector(
noinline factory: AlertBuilderFactory<D>,
title: CharSequence? = null,
items: List<CharSequence>,
noinline onClick: (DialogInterface, CharSequence, Int) -> Unit,
) = requireActivity().selector(factory, title, items, onClick)
fun <D : DialogInterface> Context.selector(
factory: AlertBuilderFactory<D>,
title: CharSequence? = null,
items: List<CharSequence>,
onClick: (DialogInterface, CharSequence, Int) -> Unit,
) {
with(factory(this)) {
if (title != null) {
this.title = title
}
items(items, onClick)
show()
}
}

View File

@ -0,0 +1,265 @@
@file:JvmName("Sdk28ServicesKt")
package org.jetbrains.anko
import android.accounts.AccountManager
import android.app.*
import android.app.admin.DevicePolicyManager
import android.app.usage.NetworkStatsManager
import android.app.usage.StorageStatsManager
import android.app.usage.UsageStatsManager
import android.bluetooth.BluetoothManager
import android.companion.CompanionDeviceManager
import android.content.ClipboardManager
import android.content.Context
import android.content.RestrictionsManager
import android.content.pm.ShortcutManager
import android.hardware.ConsumerIrManager
import android.hardware.SensorManager
import android.hardware.camera2.CameraManager
import android.hardware.display.DisplayManager
import android.hardware.input.InputManager
import android.hardware.usb.UsbManager
import android.location.LocationManager
import android.media.AudioManager
import android.media.midi.MidiManager
import android.media.projection.MediaProjectionManager
import android.media.session.MediaSessionManager
import android.media.tv.TvInputManager
import android.net.ConnectivityManager
import android.net.nsd.NsdManager
import android.net.wifi.WifiManager
import android.net.wifi.aware.WifiAwareManager
import android.net.wifi.p2p.WifiP2pManager
import android.nfc.NfcManager
import android.os.BatteryManager
import android.os.HardwarePropertiesManager
import android.os.PowerManager
import android.os.UserManager
import android.os.health.SystemHealthManager
import android.os.storage.StorageManager
import android.print.PrintManager
import android.telecom.TelecomManager
import android.telephony.CarrierConfigManager
import android.telephony.TelephonyManager
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.CaptioningManager
import android.view.inputmethod.InputMethodManager
import android.view.textclassifier.TextClassificationManager
/** Returns the AccessibilityManager instance. **/
val Context.accessibilityManager: AccessibilityManager
get() = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
/** Returns the AccountManager instance. **/
val Context.accountManager: AccountManager
get() = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
/** Returns the ActivityManager instance. **/
val Context.activityManager: ActivityManager
get() = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
/** Returns the AlarmManager instance. **/
val Context.alarmManager: AlarmManager
get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager
/** Returns the AppOpsManager instance. **/
val Context.appOpsManager: AppOpsManager
get() = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
/** Returns the AudioManager instance. **/
val Context.audioManager: AudioManager
get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
/** Returns the BatteryManager instance. **/
val Context.batteryManager: BatteryManager
get() = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
/** Returns the BluetoothManager instance. **/
val Context.bluetoothManager: BluetoothManager
get() = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
/** Returns the CameraManager instance. **/
val Context.cameraManager: CameraManager
get() = getSystemService(Context.CAMERA_SERVICE) as CameraManager
/** Returns the CaptioningManager instance. **/
val Context.captioningManager: CaptioningManager
get() = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager
/** Returns the CarrierConfigManager instance. **/
val Context.carrierConfigManager: CarrierConfigManager
get() = getSystemService(Context.CARRIER_CONFIG_SERVICE) as CarrierConfigManager
/** Returns the ClipboardManager instance. **/
val Context.clipboardManager: ClipboardManager
get() = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
/** Returns the CompanionDeviceManager instance. **/
val Context.companionDeviceManager: CompanionDeviceManager
get() = getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
/** Returns the ConnectivityManager instance. **/
val Context.connectivityManager: ConnectivityManager
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
/** Returns the ConsumerIrManager instance. **/
val Context.consumerIrManager: ConsumerIrManager
get() = getSystemService(Context.CONSUMER_IR_SERVICE) as ConsumerIrManager
/** Returns the DevicePolicyManager instance. **/
val Context.devicePolicyManager: DevicePolicyManager
get() = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
/** Returns the DisplayManager instance. **/
val Context.displayManager: DisplayManager
get() = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
/** Returns the DownloadManager instance. **/
val Context.downloadManager: DownloadManager
get() = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
///** Returns the EuiccManager instance. **/
//val Context.euiccManager: EuiccManager
// get() = getSystemService(Context.EUICC_SERVICE) as EuiccManager
///** Returns the FingerprintManager instance. **/
//val Context.fingerprintManager: FingerprintManager
// get() = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
/** Returns the HardwarePropertiesManager instance. **/
val Context.hardwarePropertiesManager: HardwarePropertiesManager
get() = getSystemService(Context.HARDWARE_PROPERTIES_SERVICE) as HardwarePropertiesManager
/** Returns the InputManager instance. **/
val Context.inputManager: InputManager
get() = getSystemService(Context.INPUT_SERVICE) as InputManager
/** Returns the InputMethodManager instance. **/
val Context.inputMethodManager: InputMethodManager
get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
/** Returns the KeyguardManager instance. **/
val Context.keyguardManager: KeyguardManager
get() = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
/** Returns the LocationManager instance. **/
val Context.locationManager: LocationManager
get() = getSystemService(Context.LOCATION_SERVICE) as LocationManager
/** Returns the MediaProjectionManager instance. **/
val Context.mediaProjectionManager: MediaProjectionManager
get() = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
/** Returns the MediaSessionManager instance. **/
val Context.mediaSessionManager: MediaSessionManager
get() = getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
/** Returns the MidiManager instance. **/
val Context.midiManager: MidiManager
get() = getSystemService(Context.MIDI_SERVICE) as MidiManager
/** Returns the NetworkStatsManager instance. **/
val Context.networkStatsManager: NetworkStatsManager
get() = getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager
/** Returns the NfcManager instance. **/
val Context.nfcManager: NfcManager
get() = getSystemService(Context.NFC_SERVICE) as NfcManager
/** Returns the NotificationManager instance. **/
val Context.notificationManager: NotificationManager
get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
/** Returns the NsdManager instance. **/
val Context.nsdManager: NsdManager
get() = getSystemService(Context.NSD_SERVICE) as NsdManager
/** Returns the PowerManager instance. **/
val Context.powerManager: PowerManager
get() = getSystemService(Context.POWER_SERVICE) as PowerManager
/** Returns the PrintManager instance. **/
val Context.printManager: PrintManager
get() = getSystemService(Context.PRINT_SERVICE) as PrintManager
/** Returns the RestrictionsManager instance. **/
val Context.restrictionsManager: RestrictionsManager
get() = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
/** Returns the SearchManager instance. **/
val Context.searchManager: SearchManager
get() = getSystemService(Context.SEARCH_SERVICE) as SearchManager
/** Returns the SensorManager instance. **/
val Context.sensorManager: SensorManager
get() = getSystemService(Context.SENSOR_SERVICE) as SensorManager
/** Returns the ShortcutManager instance. **/
val Context.shortcutManager: ShortcutManager
get() = getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
/** Returns the StorageManager instance. **/
val Context.storageManager: StorageManager
get() = getSystemService(Context.STORAGE_SERVICE) as StorageManager
/** Returns the StorageStatsManager instance. **/
val Context.storageStatsManager: StorageStatsManager
get() = getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
/** Returns the SystemHealthManager instance. **/
val Context.systemHealthManager: SystemHealthManager
get() = getSystemService(Context.SYSTEM_HEALTH_SERVICE) as SystemHealthManager
/** Returns the TelecomManager instance. **/
val Context.telecomManager: TelecomManager
get() = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
/** Returns the TelephonyManager instance. **/
val Context.telephonyManager: TelephonyManager
get() = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
/** Returns the TextClassificationManager instance. **/
val Context.textClassificationManager: TextClassificationManager
get() = getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager
/** Returns the TvInputManager instance. **/
val Context.tvInputManager: TvInputManager
get() = getSystemService(Context.TV_INPUT_SERVICE) as TvInputManager
/** Returns the UiModeManager instance. **/
val Context.uiModeManager: UiModeManager
get() = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
/** Returns the UsageStatsManager instance. **/
val Context.usageStatsManager: UsageStatsManager
get() = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
/** Returns the UsbManager instance. **/
val Context.usbManager: UsbManager
get() = getSystemService(Context.USB_SERVICE) as UsbManager
/** Returns the UserManager instance. **/
val Context.userManager: UserManager
get() = getSystemService(Context.USER_SERVICE) as UserManager
/** Returns the WallpaperManager instance. **/
val Context.wallpaperManager: WallpaperManager
get() = getSystemService(Context.WALLPAPER_SERVICE) as WallpaperManager
/** Returns the WifiAwareManager instance. **/
val Context.wifiAwareManager: WifiAwareManager
get() = getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager
/** Returns the WifiManager instance. **/
val Context.wifiManager: WifiManager
get() = getSystemService(Context.WIFI_SERVICE) as WifiManager
/** Returns the WifiP2pManager instance. **/
val Context.wifiP2pManager: WifiP2pManager
get() = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
/** Returns the WindowManager instance. **/
val Context.windowManager: WindowManager
get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.jetbrains.anko
import android.content.SharedPreferences
/**
* Opens the [SharedPreferences.Editor], applies the [modifer] to it and then applies the changes asynchronously
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("edit(modifier)", "androidx.core.content.edit"))
inline fun SharedPreferences.apply(modifier: SharedPreferences.Editor.() -> Unit) {
val editor = this.edit()
editor.modifier()
editor.apply()
}
/**
* Opens the [SharedPreferences.Editor], applies the [modifer] to it and then applies the changes synchronously
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("edit(true, modifier)", "androidx.core.content.edit"))
inline fun SharedPreferences.commit(modifier: SharedPreferences.Editor.() -> Unit) {
val editor = this.edit()
editor.modifier()
editor.commit()
}

View File

@ -0,0 +1,63 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.content.res.Resources
import android.util.TypedValue
import android.view.View
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.Dimension
import androidx.fragment.app.Fragment
fun Resources.Theme.attr(@AttrRes attribute: Int): TypedValue {
val typedValue = TypedValue()
if (!resolveAttribute(attribute, typedValue, true)) {
throw IllegalArgumentException("Failed to resolve attribute: $attribute")
}
return typedValue
}
@ColorInt
fun Resources.Theme.color(@AttrRes attribute: Int): Int {
val attr = attr(attribute)
if (attr.type < TypedValue.TYPE_FIRST_COLOR_INT || attr.type > TypedValue.TYPE_LAST_COLOR_INT) {
throw IllegalArgumentException("Attribute value type is not color: $attribute")
}
return attr.data
}
fun Context.attr(@AttrRes attribute: Int): TypedValue = theme.attr(attribute)
@Dimension(unit = Dimension.PX)
fun Context.dimenAttr(@AttrRes attribute: Int): Int =
TypedValue.complexToDimensionPixelSize(attr(attribute).data, resources.displayMetrics)
@ColorInt
fun Context.colorAttr(@AttrRes attribute: Int): Int = theme.color(attribute)
@Dimension(unit = Dimension.PX)
inline fun AnkoContext<*>.dimenAttr(@AttrRes attribute: Int): Int = ctx.dimenAttr(attribute)
@ColorInt
inline fun AnkoContext<*>.colorAttr(@AttrRes attribute: Int): Int = ctx.colorAttr(attribute)
inline fun AnkoContext<*>.attr(@AttrRes attribute: Int): TypedValue = ctx.attr(attribute)
@Dimension(unit = Dimension.PX)
inline fun View.dimenAttr(@AttrRes attribute: Int): Int = context.dimenAttr(attribute)
@ColorInt
inline fun View.colorAttr(@AttrRes attribute: Int): Int = context.colorAttr(attribute)
inline fun View.attr(@AttrRes attribute: Int): TypedValue = context.attr(attribute)
@Dimension(unit = Dimension.PX)
inline fun Fragment.dimenAttr(@AttrRes attribute: Int): Int = requireContext().dimenAttr(attribute)
@ColorInt
inline fun Fragment.colorAttr(@AttrRes attribute: Int): Int = requireContext().colorAttr(attribute)
inline fun Fragment.attr(@AttrRes attribute: Int): TypedValue = requireContext().attr(attribute)

View File

@ -0,0 +1,123 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package org.jetbrains.anko
import android.content.Context
import android.widget.Toast
import androidx.fragment.app.Fragment
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text resource.
*/
inline fun AnkoContext<*>.toast(message: Int) = ctx.toast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text resource.
*/
inline fun Fragment.toast(message: Int) = requireContext().toast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text resource.
*/
inline fun Context.toast(message: Int): Toast = Toast
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text.
*/
inline fun AnkoContext<*>.toast(message: CharSequence) = ctx.toast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text.
*/
inline fun Fragment.toast(message: CharSequence) = requireContext().toast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
*
* @param message the message text.
*/
inline fun Context.toast(message: CharSequence): Toast = Toast
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text resource.
*/
inline fun AnkoContext<*>.longToast(message: Int) = ctx.longToast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text resource.
*/
inline fun Fragment.longToast(message: Int) = requireContext().longToast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text resource.
*/
inline fun Context.longToast(message: Int): Toast = Toast
.makeText(this, message, Toast.LENGTH_LONG)
.apply {
show()
}
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text.
*/
inline fun AnkoContext<*>.longToast(message: CharSequence) = ctx.longToast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text.
*/
inline fun Fragment.longToast(message: CharSequence) = requireContext().longToast(message)
/**
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
*
* @param message the message text.
*/
inline fun Context.longToast(message: CharSequence): Toast = Toast
.makeText(this, message, Toast.LENGTH_LONG)
.apply {
show()
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE", "MatchingDeclarationName")
package org.jetbrains.anko
import android.view.View
import org.jetbrains.anko.internals.AnkoInternals
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class AnkoViewDslMarker
/**
* Apply [f] to this [View] and to all of its children recursively.
*
* @return the receiver.
*/
inline fun <T : View> T.applyRecursively(noinline f: (View) -> Unit): T {
AnkoInternals.applyRecursively(this, f)
return this
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.jetbrains.anko.appcompat.v7
import android.content.Context
import android.content.DialogInterface
import android.graphics.drawable.Drawable
import android.view.KeyEvent
import android.view.View
import androidx.appcompat.app.AlertDialog
import org.jetbrains.anko.AlertBuilder
import org.jetbrains.anko.AlertBuilderFactory
import org.jetbrains.anko.internals.AnkoInternals
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
import kotlin.DeprecationLevel.ERROR
val Appcompat: AlertBuilderFactory<AlertDialog> = ::AppcompatAlertBuilder
internal class AppcompatAlertBuilder(override val ctx: Context) : AlertBuilder<AlertDialog> {
private val builder = AlertDialog.Builder(ctx)
override var title: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setTitle(value)
}
override var titleResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setTitle(value)
}
override var message: CharSequence
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setMessage(value)
}
override var messageResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setMessage(value)
}
override var icon: Drawable
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setIcon(value)
}
override var iconResource: Int
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setIcon(value)
}
override var customTitle: View
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setCustomTitle(value)
}
override var customView: View
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setView(value)
}
override var isCancelable: Boolean
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
set(value) {
builder.setCancelable(value)
}
override fun onCancelled(handler: (DialogInterface) -> Unit) {
builder.setOnCancelListener(handler)
}
override fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) {
builder.setOnKeyListener(handler)
}
override fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setPositiveButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun positiveButton(
buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
) {
builder.setPositiveButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNegativeButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun negativeButton(
buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
) {
builder.setNegativeButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
builder.setNeutralButton(buttonText) { dialog, _ -> onClicked(dialog) }
}
override fun neutralPressed(
buttonTextResource: Int,
onClicked: (dialog: DialogInterface) -> Unit,
) {
builder.setNeutralButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
}
override fun items(
items: List<CharSequence>,
onItemSelected: (dialog: DialogInterface, index: Int) -> Unit,
) {
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
onItemSelected(dialog, which)
}
}
override fun <T> items(
items: List<T>,
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit,
) {
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
onItemSelected(dialog, items[which], which)
}
}
override fun build(): AlertDialog = builder.create()
override fun show(): AlertDialog = builder.show()
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE")
package org.jetbrains.anko
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.BackgroundColorSpan
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import android.text.style.UnderlineSpan
import android.view.View
inline fun buildSpanned(f: SpannableStringBuilder.() -> Unit): Spanned =
SpannableStringBuilder().apply(f)
inline val SpannableStringBuilder.Bold: StyleSpan
get() = StyleSpan(Typeface.BOLD)
inline val SpannableStringBuilder.Italic: StyleSpan
get() = StyleSpan(Typeface.ITALIC)
inline val SpannableStringBuilder.Underline: UnderlineSpan
get() = UnderlineSpan()
inline val SpannableStringBuilder.Strikethrough: StrikethroughSpan
get() = StrikethroughSpan()
inline fun SpannableStringBuilder.foregroundColor(color: Int): ForegroundColorSpan =
ForegroundColorSpan(color)
inline fun SpannableStringBuilder.backgroundColor(color: Int): BackgroundColorSpan =
BackgroundColorSpan(color)
inline fun SpannableStringBuilder.clickable(crossinline onClick: (View) -> Unit): ClickableSpan {
return object : ClickableSpan() {
override fun onClick(widget: View) {
onClick(widget)
}
}
}
inline fun SpannableStringBuilder.link(url: String): URLSpan {
return URLSpan(url)
}
fun SpannableStringBuilder.append(text: CharSequence, vararg spans: Any) {
val textLength = text.length
append(text)
spans.forEach { span ->
setSpan(span, this.length - textLength, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
}
fun SpannableStringBuilder.append(text: CharSequence, span: Any) {
val textLength = text.length
append(text)
setSpan(span, this.length - textLength, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
inline fun SpannableStringBuilder.append(span: Any, f: SpannableStringBuilder.() -> Unit) = apply {
val start = length
f()
setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
inline fun SpannableStringBuilder.appendln(text: CharSequence, vararg spans: Any) {
append(text, *spans)
appendln()
}
inline fun SpannableStringBuilder.appendln(text: CharSequence, span: Any) {
append(text, span)
appendln()
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE")
package org.jetbrains.anko.collections
/**
* Iterate the receiver [Array] using an index.
*
* @f an action to invoke on each array element.
*/
@Deprecated(message = "Use the native Kotlin version", replaceWith = ReplaceWith("forEach(f)"))
inline fun <T> Array<T>.forEachByIndex(f: (T) -> Unit) {
val lastIndex = size - 1
for (i in 0..lastIndex) {
f(get(i))
}
}
/**
* Iterate the receiver [Array] using an index.
*
* @f an action to invoke on each array element (index, element).
*/
@Deprecated(
message = "Use the native Kotlin version",
replaceWith = ReplaceWith("forEachIndexed(f)")
)
inline fun <T> Array<T>.forEachWithIndex(f: (Int, T) -> Unit) {
val lastIndex = size - 1
for (i in 0..lastIndex) {
f(i, get(i))
}
}
/**
* Iterate the receiver [Array] backwards using an index.
*
* @f an action to invoke on each array element.
*/
inline fun <T> Array<T>.forEachReversedByIndex(f: (T) -> Unit) {
var i = size - 1
while (i >= 0) {
f(get(i))
i--
}
}
/**
* Iterate the receiver [Array] backwards using an index.
*
* @f an action to invoke on each array element (index, element).
*/
inline fun <T> Array<T>.forEachReversedWithIndex(f: (Int, T) -> Unit) {
var i = size - 1
while (i >= 0) {
f(i, get(i))
i--
}
}
///**
// * Create a [Sequence] instance that wraps the original [SparseArray] returning its elements when being iterated.
// */
//inline fun <T> SparseArray<T>.asSequence(): Sequence<T> = SparseArraySequence(this)
//
///**
// * Create a [Sequence] instance that wraps the original [SparseBooleanArray] returning its elements when being iterated.
// */
//inline fun <T> SparseBooleanArray.asSequence(): Sequence<Boolean> = SparseBooleanArraySequence(this)
//
///**
// * Create a [Sequence] instance that wraps the original [SparseIntArray] returning its elements when being iterated.
// */
//inline fun <T> SparseIntArray.asSequence(): Sequence<Int> = SparseIntArraySequence(this)
//
//@PublishedApi
//internal class SparseArraySequence<T>(private val a: SparseArray<T>) : Sequence<T> {
// override fun iterator(): Iterator<T> = SparseArrayIterator()
//
// private inner class SparseArrayIterator : Iterator<T> {
// private var index = 0
// private val size = a.size()
//
// override fun hasNext() = size > index
//
// override fun next(): T {
// if (a.size() != size) throw ConcurrentModificationException()
// return a.valueAt(index++)
// }
// }
//}
//
//@PublishedApi
//internal class SparseBooleanArraySequence(private val a: SparseBooleanArray) : Sequence<Boolean> {
// override fun iterator(): Iterator<Boolean> = SparseIntArrayIterator()
//
// private inner class SparseIntArrayIterator : Iterator<Boolean> {
// private var index = 0
// private val size = a.size()
//
// override fun hasNext() = size > index
//
// override fun next(): Boolean {
// if (a.size() != size) throw ConcurrentModificationException()
// return a.get(index++)
// }
// }
//}
//
//@PublishedApi
//internal class SparseIntArraySequence(private val a: SparseIntArray) : Sequence<Int> {
// override fun iterator(): Iterator<Int> = SparseIntArrayIterator()
//
// private inner class SparseIntArrayIterator : Iterator<Int> {
// private var index = 0
// private val size = a.size()
//
// override fun hasNext() = size > index
//
// override fun next(): Int {
// if (a.size() != size) throw ConcurrentModificationException()
// return a.get(index++)
// }
// }
//}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "NOTHING_TO_INLINE")
package org.jetbrains.anko.collections
/**
* Iterate the receiver [List] using an index.
*
* @f an action to invoke on each list element.
*/
inline fun <T> List<T>.forEachByIndex(f: (T) -> Unit) {
val lastIndex = size - 1
for (i in 0..lastIndex) {
f(get(i))
}
}
/**
* Iterate the receiver [List] using an index.
*
* @f an action to invoke on each list element (index, element).
*/
inline fun <T> List<T>.forEachWithIndex(f: (Int, T) -> Unit) {
val lastIndex = size - 1
for (i in 0..lastIndex) {
f(i, get(i))
}
}
/**
* Iterate the receiver [List] backwards using an index.
*
* @f an action to invoke on each list element.
*/
inline fun <T> List<T>.forEachReversedByIndex(f: (T) -> Unit) {
var i = size - 1
while (i >= 0) {
f(get(i))
i--
}
}
/**
* Iterate the receiver [List] backwards using an index.
*
* @f an action to invoke on each list element (index, element).
*/
inline fun <T> List<T>.forEachReversedWithIndex(f: (Int, T) -> Unit) {
var i = size - 1
while (i >= 0) {
f(i, get(i))
i--
}
}
/**
* Convert the Android pair to a Kotlin one.
*
* @see [toAndroidPair].
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("toKotlinPair()", "androidx.core.util.toKotlinPair"))
inline fun <F, S> android.util.Pair<F, S>.toKotlinPair(): Pair<F, S> = first to second
/**
* Convert the Kotlin pair to an Android one.
*
* @see [toKotlinPair].
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("toAndroidPair()", "androidx.core.util.toAndroidPair"))
inline fun <F, S> Pair<F, S>.toAndroidPair(): android.util.Pair<F, S> = android.util.Pair(first, second)

View File

@ -0,0 +1,64 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.jetbrains.anko.collections
import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import java.util.ConcurrentModificationException
/**
* Iterate the receiver [SparseArray]
* @action an action to invoke on each key value pair
* @throws [ConcurrentModificationException] if modified while iterating
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
inline fun <E> SparseArray<E>.forEach(action: (Int, E) -> Unit) {
val size = this.size()
for (i in 0..size - 1) {
if (size != this.size()) throw ConcurrentModificationException()
action(this.keyAt(i), this.valueAt(i))
}
}
/**
* Iterate the receiver [SparseBooleanArray]
* @action an action to invoke on each key value pair
* @throws [ConcurrentModificationException] if modified while iterating
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
inline fun SparseBooleanArray.forEach(action: (Int, Boolean) -> Unit) {
val size = this.size()
for (i in 0..size - 1) {
if (size != this.size()) throw ConcurrentModificationException()
action(this.keyAt(i), this.valueAt(i))
}
}
/**
* Iterate the receiver [SparseIntArray]
* @action an action to invoke on each key value pair
* @throws [ConcurrentModificationException] if modified while iterating
*/
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
inline fun SparseIntArray.forEach(action: (Int, Int) -> Unit) {
val size = this.size()
for (i in 0..size - 1) {
if (size != this.size()) throw ConcurrentModificationException()
action(this.keyAt(i), this.valueAt(i))
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko.custom
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.ViewManager
import org.jetbrains.anko.internals.AnkoInternals
inline fun <T : View> ViewManager.ankoView(
factory: (ctx: Context) -> T,
theme: Int,
init: T.() -> Unit,
): T {
val ctx = AnkoInternals.wrapContextIfNeeded(AnkoInternals.getContext(this), theme)
val view = factory(ctx)
view.init()
AnkoInternals.addView(this, view)
return view
}
inline fun <T : View> Context.ankoView(
factory: (ctx: Context) -> T,
theme: Int,
init: T.() -> Unit,
): T {
val ctx = AnkoInternals.wrapContextIfNeeded(this, theme)
val view = factory(ctx)
view.init()
AnkoInternals.addView(this, view)
return view
}
inline fun <T : View> Activity.ankoView(
factory: (ctx: Context) -> T,
theme: Int,
init: T.() -> Unit,
): T {
val ctx = AnkoInternals.wrapContextIfNeeded(this, theme)
val view = factory(ctx)
view.init()
AnkoInternals.addView(this, view)
return view
}
inline fun <reified T : View> ViewManager.customView(theme: Int = 0, init: T.() -> Unit): T =
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }
inline fun <reified T : View> Context.customView(theme: Int = 0, init: T.() -> Unit): T =
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }
inline fun <reified T : View> Activity.customView(theme: Int = 0, init: T.() -> Unit): T =
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }

View File

@ -0,0 +1,94 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko.custom
import android.content.Context
import android.view.View
import androidx.fragment.app.Fragment
import org.jetbrains.anko.AnkoAsyncContext
import org.jetbrains.anko.BackgroundExecutor
import org.jetbrains.anko.applyRecursively
import org.jetbrains.anko.collections.forEachReversedByIndex
import org.jetbrains.anko.runOnUiThread
import java.lang.ref.WeakReference
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
@Deprecated(
"Use forEachReversedByIndex(f) instead.",
ReplaceWith(
"forEachReversedByIndex(f)",
"org.jetbrains.anko.collections.forEachReversedByIndex"
)
)
inline fun <T> Array<T>.forEachReversed(f: (T) -> Unit) = forEachReversedByIndex(f)
@Deprecated(
"Use forEachReversedByIndex(f) instead.",
ReplaceWith(
"forEachReversedByIndex(f)",
"org.jetbrains.anko.collections.forEachReversedByIndex"
)
)
inline fun <T> List<T>.forEachReversed(f: (T) -> Unit) = forEachReversedByIndex(f)
@Deprecated("Use runOnUiThread(f) instead.", ReplaceWith("runOnUiThread(f)"))
inline fun Fragment.onUiThread(crossinline f: () -> Unit) = runOnUiThread(f)
@Deprecated("Use runOnUiThread(f) instead.", ReplaceWith("runOnUiThread(f)"))
fun Context.onUiThread(f: Context.() -> Unit) = runOnUiThread(f)
@Deprecated("Use doAsync(task) instead.", ReplaceWith("doAsync(task)"))
fun <T> T.async(task: AnkoAsyncContext<T>.() -> Unit): Future<Unit> {
val context = AnkoAsyncContext(WeakReference(this))
return BackgroundExecutor.submit { context.task() }
}
@Deprecated(
"Use doAsync(executorService, task) instead.",
ReplaceWith("doAsync(executorService, task)")
)
fun <T> T.async(
executorService: ExecutorService,
task: AnkoAsyncContext<T>.() -> Unit,
): Future<Unit> {
val context = AnkoAsyncContext(WeakReference(this))
return executorService.submit<Unit> { context.task() }
}
@Deprecated("Use doAsyncResult(task) instead.", ReplaceWith("doAsyncResult(task)"))
fun <T, R> T.asyncResult(task: AnkoAsyncContext<T>.() -> R): Future<R> {
val context = AnkoAsyncContext(WeakReference(this))
return BackgroundExecutor.submit { context.task() }
}
@Deprecated(
"Use doAsyncResult(executorService, task) instead.",
ReplaceWith("doAsyncResult(executorService, task)")
)
fun <T, R> T.asyncResult(
executorService: ExecutorService,
task: AnkoAsyncContext<T>.() -> R,
): Future<R> {
val context = AnkoAsyncContext(WeakReference(this))
return executorService.submit<R> { context.task() }
}
@Deprecated("Use applyRecursively(block) instead.", ReplaceWith("applyRecursively(style)"))
fun <T : View> T.style(style: (View) -> Unit): T = applyRecursively(style)

View File

@ -0,0 +1,316 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("MatchingDeclarationName")
package org.jetbrains.anko.internals
import android.app.Activity
import android.app.Service
import android.app.UiModeManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.database.Cursor
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import org.jetbrains.anko.*
import java.io.Serializable
import java.util.*
object AnkoInternals {
const val NO_GETTER: String = "Property does not have a getter"
fun noGetter(): Nothing = throw AnkoException("Property does not have a getter")
private class AnkoContextThemeWrapper(base: Context?, val theme: Int) :
ContextThemeWrapper(base, theme)
fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
is ViewGroup -> manager.addView(view)
is AnkoContext<*> -> manager.addView(view, null)
else -> throw AnkoException("$manager is the wrong parent")
}
fun <T : View> addView(ctx: Context, view: T) {
ctx.UI { addView(this, view) }
}
fun <T : View> addView(activity: Activity, view: T) {
createAnkoContext(activity, { addView(this, view) }, true)
}
fun wrapContextIfNeeded(ctx: Context, theme: Int): Context {
return if (theme != 0 && (ctx !is AnkoContextThemeWrapper || ctx.theme != theme)) {
// If the context isn't a ContextThemeWrapper, or it is but does not have
// the same theme as we need, wrap it in a new wrapper
AnkoContextThemeWrapper(ctx, theme)
} else {
ctx
}
}
fun applyRecursively(v: View, style: (View) -> Unit) {
style(v)
if (v is ViewGroup) {
val maxIndex = v.childCount - 1
for (i in 0..maxIndex) {
v.getChildAt(i)?.let { applyRecursively(it, style) }
}
}
}
fun getContext(manager: ViewManager): Context = when (manager) {
is ViewGroup -> manager.context
is AnkoContext<*> -> manager.ctx
else -> throw AnkoException("$manager is the wrong parent")
}
inline fun <T> T.createAnkoContext(
ctx: Context,
init: AnkoContext<T>.() -> Unit,
setContentView: Boolean = false,
): AnkoContext<T> {
val dsl = AnkoContextImpl(ctx, this, setContentView)
dsl.init()
return dsl
}
// Some constants not present in Android SDK v.15
private object InternalConfiguration {
const val SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0
const val SCREENLAYOUT_LAYOUTDIR_SHIFT = 6
const val SCREENLAYOUT_LAYOUTDIR_RTL = 0x02 shl SCREENLAYOUT_LAYOUTDIR_SHIFT
const val UI_MODE_TYPE_APPLIANCE = 0x05
const val UI_MODE_TYPE_WATCH = 0x06
}
@JvmStatic
fun <T> createIntent(
ctx: Context,
clazz: Class<out T>,
params: Array<out Pair<String, Any?>>,
): Intent {
val intent = Intent(ctx, clazz)
if (params.isNotEmpty()) fillIntentArguments(intent, params)
return intent
}
@JvmStatic
fun internalStartActivity(
ctx: Context,
activity: Class<out Activity>,
params: Array<out Pair<String, Any?>>,
) {
ctx.startActivity(createIntent(ctx, activity, params))
}
@JvmStatic
fun internalStartActivityForResult(
act: Activity,
activity: Class<out Activity>,
requestCode: Int,
params: Array<out Pair<String, Any?>>,
) {
act.startActivityForResult(createIntent(act, activity, params), requestCode)
}
@JvmStatic
fun internalStartService(
ctx: Context,
service: Class<out Service>,
params: Array<out Pair<String, Any?>>,
): ComponentName? = ctx.startService(createIntent(ctx, service, params))
@JvmStatic
fun internalStopService(
ctx: Context,
service: Class<out Service>,
params: Array<out Pair<String, Any?>>,
): Boolean = ctx.stopService(createIntent(ctx, service, params))
@JvmStatic
private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
params.forEach {
when (val value = it.second) {
null -> intent.putExtra(it.first, null as Serializable?)
is Int -> intent.putExtra(it.first, value)
is Long -> intent.putExtra(it.first, value)
is String -> intent.putExtra(it.first, value)
is Float -> intent.putExtra(it.first, value)
is Double -> intent.putExtra(it.first, value)
is Char -> intent.putExtra(it.first, value)
is Short -> intent.putExtra(it.first, value)
is Boolean -> intent.putExtra(it.first, value)
is Bundle -> intent.putExtra(it.first, value)
is Array<*> -> when {
value.isArrayOf<CharSequence>() -> intent.putExtra(it.first, value)
value.isArrayOf<String>() -> intent.putExtra(it.first, value)
value.isArrayOf<Parcelable>() -> intent.putExtra(it.first, value)
else -> throw AnkoException("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
}
is IntArray -> intent.putExtra(it.first, value)
is LongArray -> intent.putExtra(it.first, value)
is FloatArray -> intent.putExtra(it.first, value)
is DoubleArray -> intent.putExtra(it.first, value)
is CharArray -> intent.putExtra(it.first, value)
is ShortArray -> intent.putExtra(it.first, value)
is BooleanArray -> intent.putExtra(it.first, value)
is CharSequence -> intent.putExtra(it.first, value)
is Serializable -> intent.putExtra(it.first, value)
is Parcelable -> intent.putExtra(it.first, value)
else -> throw AnkoException("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
}
return@forEach
}
}
@JvmStatic
inline fun <T> useCursor(cursor: Cursor, f: (Cursor) -> T): T {
return cursor.use(f)
// Closeable only added in API 16
}
@JvmStatic
fun <T : View> initiateView(ctx: Context, viewClass: Class<T>): T {
fun getConstructor1() = viewClass.getConstructor(Context::class.java)
fun getConstructor2() =
viewClass.getConstructor(Context::class.java, AttributeSet::class.java)
return try {
getConstructor1().newInstance(ctx)
} catch (_: NoSuchMethodException) {
try {
getConstructor2().newInstance(ctx, null)
} catch (_: NoSuchMethodException) {
throw AnkoException("Can't initiate View of class ${viewClass.name}: can't find proper constructor")
}
}
}
@Suppress("LongParameterList")
@JvmStatic
fun testConfiguration(
ctx: Context,
screenSize: ScreenSize?,
density: ClosedRange<Int>?,
language: String?,
orientation: Orientation?,
long: Boolean?,
fromSdk: Int?,
sdk: Int?,
uiMode: UiMode?,
nightMode: Boolean?,
rightToLeft: Boolean?,
smallestWidth: Int?,
): Boolean {
val config = ctx.resources?.configuration
if (screenSize != null) {
if (config == null) return false
when (config.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) {
Configuration.SCREENLAYOUT_SIZE_UNDEFINED -> {}
Configuration.SCREENLAYOUT_SIZE_SMALL -> if (screenSize != ScreenSize.SMALL) return false
Configuration.SCREENLAYOUT_SIZE_NORMAL -> if (screenSize != ScreenSize.NORMAL) return false
Configuration.SCREENLAYOUT_SIZE_LARGE -> if (screenSize != ScreenSize.LARGE) return false
Configuration.SCREENLAYOUT_SIZE_XLARGE -> if (screenSize != ScreenSize.XLARGE) return false
}
}
if (density != null) {
val currentDensityDpi = ctx.resources?.displayMetrics?.densityDpi ?: return false
if (currentDensityDpi !in density || currentDensityDpi == density.endInclusive) return false
}
if (language != null) {
val locale = Locale.getDefault()
val currentLanguage =
if (language.indexOf('_') >= 0) locale.toString() else locale.language
if (currentLanguage != language) return false
}
if (orientation != null) {
if (config == null) return false
@Suppress("DEPRECATION")
when (config.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> if (orientation != Orientation.LANDSCAPE) return false
Configuration.ORIENTATION_PORTRAIT -> if (orientation != Orientation.PORTRAIT) return false
Configuration.ORIENTATION_SQUARE -> if (orientation != Orientation.SQUARE) return false
Configuration.ORIENTATION_UNDEFINED -> Unit
}
}
if (long != null) {
if (config == null) return false
val currentLong = config.screenLayout and Configuration.SCREENLAYOUT_LONG_MASK
if (currentLong == Configuration.SCREENLAYOUT_LONG_YES && !long) return false
if (currentLong == Configuration.SCREENLAYOUT_LONG_NO && long) return false
}
if (fromSdk != null) {
if (Build.VERSION.SDK_INT < fromSdk) return false
}
if (sdk != null) {
if (Build.VERSION.SDK_INT != sdk) return false
}
if (uiMode != null) {
if (config == null) return false
when (config.uiMode and Configuration.UI_MODE_TYPE_MASK) {
Configuration.UI_MODE_TYPE_NORMAL -> if (uiMode != UiMode.NORMAL) return false
Configuration.UI_MODE_TYPE_DESK -> if (uiMode != UiMode.DESK) return false
Configuration.UI_MODE_TYPE_CAR -> if (uiMode != UiMode.CAR) return false
Configuration.UI_MODE_TYPE_TELEVISION -> if (uiMode != UiMode.TELEVISION) return false
InternalConfiguration.UI_MODE_TYPE_APPLIANCE -> if (uiMode != UiMode.APPLIANCE) return false
InternalConfiguration.UI_MODE_TYPE_WATCH -> if (uiMode != UiMode.WATCH) return false
}
}
if (nightMode != null) {
val uiModeManager =
ctx.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager ?: return false
val currentMode = uiModeManager.nightMode
if (currentMode == UiModeManager.MODE_NIGHT_YES && !nightMode) return false
if (currentMode == UiModeManager.MODE_NIGHT_NO && nightMode) return false
}
if (rightToLeft != null) {
if (config == null) return false
val rtlMode = (config.screenLayout and
InternalConfiguration.SCREENLAYOUT_LAYOUTDIR_MASK) == InternalConfiguration.SCREENLAYOUT_LAYOUTDIR_RTL
if (rtlMode != rightToLeft) return false
}
if (smallestWidth != null) {
if (config == null) return false
if (config.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
if (smallestWidth != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) return false
} else if (config.smallestScreenWidthDp < smallestWidth) return false
}
return true
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.view.Menu
import android.view.MenuItem
import java.util.ConcurrentModificationException
import java.util.NoSuchElementException
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("children", "androidx.core.view.children"))
fun Menu.itemsSequence(): Sequence<MenuItem> = MenuItemsSequence(this)
private class MenuItemsSequence(private val menu: Menu) : Sequence<MenuItem> {
override fun iterator(): Iterator<MenuItem> = MenuItemIterator(menu)
private class MenuItemIterator(private val menu: Menu) : Iterator<MenuItem> {
private var index = 0
private val count = menu.size()
override fun next(): MenuItem {
if (!hasNext()) {
throw NoSuchElementException()
}
return menu.getItem(index++)
}
override fun hasNext(): Boolean {
if (count != menu.size()) {
throw ConcurrentModificationException()
}
return index < count
}
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright 2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package org.jetbrains.anko
import android.view.View
import android.view.ViewGroup
/**
* Execute [action] for each child of the received [ViewGroup].
*
* @param action the action to execute.
*/
@Deprecated(
message = "Use the Android KTX version",
replaceWith = ReplaceWith("forEach(action)", "androidx.core.view.forEach")
)
inline fun ViewGroup.forEachChild(action: (View) -> Unit) {
for (i in 0..childCount - 1) {
action(getChildAt(i))
}
}
/**
* Execute [action] for each child of the received [ViewGroup].
*
* @param action the action to execute. The first index is 0.
*/
@Deprecated(
message = "Use the Android KTX version",
replaceWith = ReplaceWith("forEachIndexed(action)", "androidx.core.view.forEachIndexed")
)
inline fun ViewGroup.forEachChildWithIndex(action: (Int, View) -> Unit) {
for (i in 0..childCount - 1) {
action(i, getChildAt(i))
}
}
/**
* Return the first child [View] matching the given [predicate].
*
* @param predicate the predicate to check against.
* @return the child [View] that matches [predicate].
* [NoSuchElementException] will be thrown if no such child was found.
*/
inline fun ViewGroup.firstChild(predicate: (View) -> Boolean): View {
return firstChildOrNull(predicate)
?: throw NoSuchElementException("No element matching predicate was found.")
}
/**
* Return the first child [View] matching the given [predicate].
*
* @param predicate the predicate to check against.
* @return the child [View] that matches [predicate], or null if no such child was found.
*/
inline fun ViewGroup.firstChildOrNull(predicate: (View) -> Boolean): View? {
for (i in 0..childCount - 1) {
val child = getChildAt(i)
if (predicate(child)) {
return child
}
}
return null
}
/**
* Return the sequence of children of the received [View].
* Note that the sequence is not thread-safe.
*
* @return the [Sequence] of children.
*/
@Deprecated(
message = "Use the Android KTX version",
replaceWith = ReplaceWith("children", "androidx.core.view.children")
)
fun View.childrenSequence(): Sequence<View> = ViewChildrenSequence(this)
/**
* Return the [Sequence] of all children of the received [View], recursively.
* Note that the sequence is not thread-safe.
*
* @return the [Sequence] of children.
*/
fun View.childrenRecursiveSequence(): Sequence<View> = ViewChildrenRecursiveSequence(this)
private class ViewChildrenSequence(private val view: View) : Sequence<View> {
override fun iterator(): Iterator<View> {
if (view !is ViewGroup) return emptyList<View>().iterator()
return ViewIterator(view)
}
private class ViewIterator(private val view: ViewGroup) : Iterator<View> {
private var index = 0
private val count = view.childCount
override fun next(): View {
if (!hasNext()) throw NoSuchElementException()
return view.getChildAt(index++)
}
override fun hasNext(): Boolean {
checkCount()
return index < count
}
private fun checkCount() {
if (count != view.childCount) throw ConcurrentModificationException()
}
}
}
private class ViewChildrenRecursiveSequence(private val view: View) : Sequence<View> {
override fun iterator(): Iterator<View> {
if (view !is ViewGroup) return emptyList<View>().iterator()
return RecursiveViewIterator(view)
}
private class RecursiveViewIterator(view: View) : Iterator<View> {
private val sequences = arrayListOf(view.childrenSequence())
private var current = sequences.removeLast().iterator()
override fun next(): View {
if (!hasNext()) throw NoSuchElementException()
val view = current.next()
if (view is ViewGroup && view.childCount > 0) {
sequences.add(view.childrenSequence())
}
return view
}
override fun hasNext(): Boolean {
if (!current.hasNext() && sequences.isNotEmpty()) {
current = sequences.removeLast().iterator()
}
return current.hasNext()
}
@Suppress("NOTHING_TO_INLINE")
private inline fun <T : Any> MutableList<T>.removeLast(): T {
if (isEmpty()) throw NoSuchElementException()
return removeAt(size - 1)
}
}
}

View File

@ -0,0 +1,17 @@
package jp.juggler.anko
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -10,7 +10,7 @@ compileKotlin {
targetCompatibility = JavaVersion.VERSION_1_8
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
freeCompilerArgs += [
"-opt-in=kotlin.ExperimentalStdlibApi",
]
@ -20,8 +20,8 @@ compileKotlin {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib"
testImplementation "junit:junit:$junit_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testImplementation "junit:junit:$junitVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
}

View File

@ -2,19 +2,19 @@ apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
compileSdkVersion stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'jp.juggler.apng'
namespace "jp.juggler.apng"
defaultConfig {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
targetSdkVersion stTargetSdkVersion
minSdkVersion stMinSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@ -27,7 +27,7 @@ android {
}
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
freeCompilerArgs += [
// "-opt-in=kotlin.ExperimentalStdlibApi",
// "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
@ -47,5 +47,5 @@ dependencies {
implementation project(":base")
implementation "com.github.zjupure:webpdecoder:2.3.$glideVersion"
testImplementation 'junit:junit:4.12'
testImplementation "junit:junit:$junitVersion"
}

View File

@ -11,8 +11,8 @@ apply plugin: "io.gitlab.arturbosch.detekt"
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
compileSdkVersion stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
// exoPlayer 2.9.0 Java 8 compiler support
compileOptions {
@ -22,8 +22,8 @@ android {
}
defaultConfig {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
targetSdkVersion stTargetSdkVersion
minSdkVersion stMinSdkVersion
versionCode 511
versionName "5.511"
@ -38,7 +38,7 @@ android {
}
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
freeCompilerArgs += [
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
@ -128,39 +128,39 @@ kapt {
dependencies {
// desugar_jdk_libs 2.0.0 AGP 7.4.0-alpha10
//noinspection GradleDependency
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
implementation(project(":base"))
implementation project(':colorpicker')
implementation project(':emoji')
implementation project(':apng_android')
implementation project(':anko')
implementation fileTree(include: ['*.aar'], dir: 'src/main/libs')
// App1 使
api "org.conscrypt:conscrypt-android:2.5.2"
implementation "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
kapt "androidx.annotation:annotation:1.5.0"
kapt "androidx.annotation:annotation:$androidxAnnotationVersion"
kapt "androidx.room:room-compiler:$roomVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
testImplementation(project(":base"))
androidTestImplementation(project(":base"))
androidTestApi "androidx.test.espresso:espresso-core:3.5.1"
androidTestApi "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
androidTestApi "androidx.test.ext:junit-ktx:1.1.5"
androidTestApi "androidx.test.ext:junit:1.1.5"
androidTestApi "androidx.test.ext:junit:$androidxTestExtJunitVersion"
androidTestApi "androidx.test.ext:truth:1.5.0"
androidTestApi "androidx.test:core-ktx:1.5.0"
androidTestApi "androidx.test:core:$androidx_test_version"
androidTestApi "androidx.test:core-ktx:$testKtxVersion"
androidTestApi "androidx.test:core:$androidxTestVersion"
androidTestApi "androidx.test:runner:1.5.2"
androidTestApi "org.jetbrains.kotlin:kotlin-test"
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
testApi "androidx.arch.core:core-testing:$arch_version"
testApi "junit:junit:$junit_version"
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
testApi "androidx.arch.core:core-testing:$archVersion"
testApi "junit:junit:$junitVersion"
testApi "org.jetbrains.kotlin:kotlin-test"
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
// To use android test orchestrator
androidTestUtil "androidx.test:orchestrator:1.4.2"
@ -211,6 +211,7 @@ tasks.register("detektAll", Detekt) {
}
source = files(
"$rootDir/anko/src",
"$rootDir/apng/src",
"$rootDir/apng_android/src",
"$rootDir/app/src",

View File

@ -12,11 +12,11 @@ plugins {
android {
namespace "jp.juggler.base"
compileSdk compile_sdk_version
compileSdk stCompileSdkVersion
defaultConfig {
minSdk min_sdk_version
targetSdk target_sdk_version
minSdk stMinSdkVersion
targetSdk stTargetSdkVersion
consumerProguardFiles "consumer-rules.pro"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -36,14 +36,14 @@ android {
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = kotlinJvmTarget
}
packagingOptions {
jniLibs {
excludes += ['META-INF/LICENSE*']
excludes += ["META-INF/LICENSE*"]
}
resources {
excludes += ['META-INF/LICENSE*']
excludes += ["META-INF/LICENSE*"]
}
}
}
@ -51,31 +51,31 @@ android {
dependencies {
// desugar_jdk_libs 2.0.0 AGP 7.4.0-alpha10
//noinspection GradleDependency
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
def emoji2Version = "1.2.0"
api "androidx.appcompat:appcompat:$appcompat_version"
api "androidx.appcompat:appcompat:$appcompatVersion"
api "androidx.browser:browser:1.4.0"
api "androidx.core:core-ktx:1.9.0"
api "androidx.core:core-ktx:$coreKtxVersion"
api "androidx.drawerlayout:drawerlayout:1.1.1"
api "androidx.emoji2:emoji2-bundled:$emoji2Version"
api "androidx.emoji2:emoji2-views-helper:$emoji2Version"
api "androidx.emoji2:emoji2-views:$emoji2Version"
api "androidx.emoji2:emoji2:$emoji2Version"
api "androidx.exifinterface:exifinterface:1.3.5"
api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-service:$lifecycle_version"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
api "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycleVersion"
api "androidx.recyclerview:recyclerview:1.2.1"
api "androidx.room:room-ktx:$roomVersion"
api "androidx.room:room-runtime:$roomVersion"
api "androidx.startup:startup-runtime:$startup_version"
api "androidx.startup:startup-runtime:$startupVersion"
api "androidx.work:work-runtime-ktx:$workVersion"
api "androidx.work:work-runtime:$workVersion"
api "com.astuetz:pagerslidingtabstrip:1.0.1"
@ -86,26 +86,29 @@ dependencies {
api "com.github.woxthebox:draglistview:1.6.6"
api "com.google.android.exoplayer:exoplayer:2.18.2"
api "com.google.android.flexbox:flexbox:3.0.0"
api "com.google.android.material:material:1.7.0"
api "com.google.android.material:material:$materialVersion"
api "com.google.firebase:firebase-messaging:23.1.1"
api "com.otaliastudios:transcoder:0.10.4"
api "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
api "com.squareup.okhttp3:okhttp:$okhttpVersion"
api "commons-codec:commons-codec:1.15"
api "io.github.inflationx:calligraphy3:3.1.1"
api "io.github.inflationx:viewpump:2.0.3"
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
api "org.bouncycastle:bcpkix-jdk15on:1.70"
api "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
api "ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0"
// Koin main features for Android
api "io.insert-koin:koin-android:$koin_version"
api "io.insert-koin:koin-android-compat:$koin_version"
api "io.insert-koin:koin-androidx-workmanager:$koin_version"
// api "io.insert-koin:koin-androidx-navigation:$koin_version"
// api "io.insert-koin:koin-androidx-compose:$koin_version"
api "io.insert-koin:koin-android:$koinVersion"
api "io.insert-koin:koin-android-compat:$koinVersion"
api "io.insert-koin:koin-androidx-workmanager:$koinVersion"
// api "io.insert-koin:koin-androidx-navigation:$koinVersion"
// api "io.insert-koin:koin-androidx-compose:$koinVersion"
api "com.github.bumptech.glide:glide:$glideVersion"
api "com.github.bumptech.glide:annotations:$glideVersion"
@ -113,19 +116,19 @@ dependencies {
exclude group: "com.squareup.okhttp3", module: "okhttp"
}
androidTestApi "androidx.test.espresso:espresso-core:3.5.1"
androidTestApi "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
androidTestApi "androidx.test.ext:junit-ktx:1.1.5"
androidTestApi "androidx.test.ext:junit:1.1.5"
androidTestApi "androidx.test.ext:junit:$androidxTestExtJunitVersion"
androidTestApi "androidx.test.ext:truth:1.5.0"
androidTestApi "androidx.test:core-ktx:1.5.0"
androidTestApi "androidx.test:core:$androidx_test_version"
androidTestApi "androidx.test:core-ktx:$testKtxVersion"
androidTestApi "androidx.test:core:$androidxTestVersion"
androidTestApi "androidx.test:runner:1.5.2"
androidTestApi "org.jetbrains.kotlin:kotlin-test"
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
testApi "androidx.arch.core:core-testing:$arch_version"
testApi "junit:junit:$junit_version"
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
testApi "androidx.arch.core:core-testing:$archVersion"
testApi "junit:junit:$junitVersion"
testApi "org.jetbrains.kotlin:kotlin-test"
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
// To use android test orchestrator
androidTestUtil "androidx.test:orchestrator:1.4.2"
@ -144,4 +147,10 @@ dependencies {
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
}
// conscrypt Unitテストするための指定
// https://github.com/google/conscrypt/issues/649
compileOnly "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
testImplementation "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
}

View File

@ -0,0 +1,154 @@
package jp.juggler.crypt
import jp.juggler.util.data.encodeUTF8
import java.io.ByteArrayOutputStream
import java.security.Provider
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.xor
import kotlin.math.min
/**
* Content-Encoding: aes128gcm のデコード
* - 単体テストで中間状態を見たいので状態を持つクラスとして実装した
* - 鍵の導出が RFC8188生とWebPushの2通りある
*/
class Aes128GcmDecoder(
// ペイロードのリーダー
reader: ByteRangeReader,
// 暗号プロバイダ
private val provider: Provider = defaultSecurityProvider,
) {
companion object {
private const val b0 = 0.toByte()
private const val b1 = 1.toByte()
private const val b2 = 2.toByte()
private val prefixWpInfoZ = "WebPush: info\u0000".encodeUTF8().toByteRange()
private val ceAes128gcmZ = "Content-Encoding: aes128gcm\u0000".encodeUTF8().toByteRange()
private val ceNonceZ = "Content-Encoding: nonce\u0000".encodeUTF8().toByteRange()
}
// ペイロードのヘッダから導出される
val salt: ByteRange
val recordSize: Int
val keyId: ByteRange
val encryptedContent: ByteRange
// RFC8188のキー導出手順で計算される
var prk = ByteRange.empty
var key = ByteRange.empty
var nonce = ByteRange.empty
init {
// ペイロード内部のヘッダを読む
salt = reader.readBytes(16)
recordSize = reader.readUInt32()
val keyIdLen = reader.readUInt8()
keyId = reader.readBytes(keyIdLen)
encryptedContent = reader.remainBytes()
}
/**
* RFC8188のキー導出手順
*/
fun deriveKeyRfc8188(inputKey: ByteRange) {
prk = hmacSha256(salt, inputKey).toByteRange()
key = hmacSha256Plus1(prk, ceAes128gcmZ).toByteRange(end = 16)
nonce = hmacSha256Plus1(prk, ceNonceZ).toByteRange(end = 12)
}
/**
* WebPush inputKey の計算が少し増える
*/
fun deriveKeyWebPush(
// receiver private key in X509 format
receiverPrivateBytes: ByteArray,
// receiver public key in 65bytes X9.62 uncompressed format
receiverPublicBytes: ByteArray,
// auth secrets created at subscription
authSecret: ByteArray,
) {
// sender public key in 65bytes X9.62 uncompressed format
val senderPublicBytes = keyId
val sharedKeyBytes = provider.sharedKeyBytes(
receiverPrivateBytes = receiverPrivateBytes,
senderPublicBytes = senderPublicBytes.toByteArray(),
)
// create input key material
val ikm = hmacSha256Plus1(
hmacSha256(
authSecret.toByteRange(),
sharedKeyBytes,
).toByteRange(),
ByteArrayOutputStream(130 + prefixWpInfoZ.size).apply {
write(prefixWpInfoZ)
write(receiverPublicBytes)
write(senderPublicBytes)
}.toByteRange()
).toByteRange(end = 32)
deriveKeyRfc8188(ikm)
}
/**
* レコードを順にデコードする
*/
fun decode(): ByteRange {
if (recordSize < 1) error("invalid record size")
val iv = ByteArray(12)
nonce.copyElements(iv, length = 12)
var counter = 0L
fun updateIv() {
// 短いプッシュメッセージならカウンタは64bitを超えない。
// カウンターをBEで12バイト表現すると上位8バイトは常に0なのでXOR演算する必要がない
// 下位バイトだけXOR演算する
var v = counter++
if (v != 0L) {
for (i in 0 until 8) {
val pos = 11 - i
iv[pos] = nonce[pos].xor(v.toByte())
v = v.ushr(8)
}
}
}
val cipher = Cipher.getInstance("AES/GCM/NoPadding", provider)
val src = encryptedContent
val bao = ByteArrayOutputStream(src.size)
val deltaBuffer = ByteArray(recordSize)
val end = src.size
for (i in src.indices step recordSize) {
val step = min(recordSize, end - i)
// カウンター番号でivを更新
updateIv()
cipher.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(key.ba, 0, 16, "AES-GCM"),
GCMParameterSpec(GCM_TAG_BITS, iv),
)
val delta = cipher.doFinal(src.ba, src.start + i, step, deltaBuffer)
var deltaLast = delta - 1
// 末尾の0を削ると、その手前にデリミタが見えるはず
while (deltaLast >= 0 && deltaBuffer[deltaLast] == b0) --deltaLast
// 最後のレコードかどうかでデリミタが異なる
val delimiter = if (i + step >= end) b2 else b1
// デリミタがないのはエラー
if (deltaLast < 0 || deltaBuffer[deltaLast] != delimiter) {
error("missing delimiter")
}
// デリミタの手前までが有効なデータ
bao.write(deltaBuffer, 0, deltaLast)
}
return bao.toByteRange()
}
}

View File

@ -0,0 +1,144 @@
package jp.juggler.crypt
import jp.juggler.util.data.encodeUTF8
import java.io.ByteArrayOutputStream
import java.security.Provider
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Content-Encoding: aesgcm のデコード
* 中間状態のテストがしたいので状態をもつクラスとして実装した
*/
class AesGcmDecoder(
// receiver private key in X509 format
receiverPrivateBytes: ByteArray,
// receiver public key in 65bytes X9.62 uncompressed format, created at subscription
private val receiverPublicBytes: ByteArray,
// auth secret, created at subscription
authSecret: ByteArray,
// sender public key in 65bytes X9.62 uncompressed format
private val senderPublicBytes: ByteArray,
// salt in HTTP header
saltBytes: ByteArray,
// provider for security functions
private val provider: Provider = defaultSecurityProvider,
) {
companion object {
private const val b0 = 0.toByte()
private val infoCeAuthZ = "Content-Encoding: auth\u0000".encodeUTF8().toByteRange()
private val prefixCeAesGcmZP256Z = "Content-Encoding: aesgcm\u0000P-256\u0000".encodeUTF8()
private val prefixCeNonceZP256Z = "Content-Encoding: nonce\u0000P-256\u0000".encodeUTF8()
fun ByteArrayOutputStream.writeLengthAndBytes(b: ByteArray) {
val len = b.size
write(len.shr(8).and(255))
write(len.and(255))
write(b)
}
}
private val salt = saltBytes.toByteRange()
val auth = authSecret.toByteRange()
val sharedKeyBytes = provider.sharedKeyBytes(
receiverPrivateBytes = receiverPrivateBytes,
senderPublicBytes = senderPublicBytes,
)
//
var ikm = ByteRange.empty
@Suppress("MemberVisibilityCanBePrivate")
var prk = ByteRange.empty
var key = ByteRange.empty
var nonce = ByteRange.empty
// The start index for each element within the buffer is:
// value | length | start |
// -----------------------------------------
// 'Content-Encoding: '| 18 | 0 |
// type | len | 18 |
// nul byte | 1 | 18 + len |
// 'P-256' | 5 | 19 + len |
// nul byte | 1 | 24 + len |
// client key length | 2 | 25 + len |
// client key | 65 | 27 + len |
// server key length | 2 | 92 + len |
// server key | 65 | 94 + len |
private fun createInfo(
prefix: ByteArray,
clientPublicKey: ByteArray, // 65 byte
serverPublicKey: ByteArray, // 65 byte
) = ByteArrayOutputStream(120).apply {
write(prefix)
// For the purposes of push encryption the length of the keys will always be 65 bytes.
writeLengthAndBytes(clientPublicKey)
writeLengthAndBytes(serverPublicKey)
}.toByteRange()
fun deriveKey() {
// input key material の導出はWebPushの仕様に依存する
ikm = hmacSha256Plus1(
hmacSha256(auth, sharedKeyBytes).toByteRange(),
infoCeAuthZ
).toByteRange(end = 32)
// ikm, salt から prk, key, nonce を導出する
prk = hmacSha256(salt, ikm).toByteRange()
key = hmacSha256Plus1(
prk,
createInfo(
prefix = prefixCeAesGcmZP256Z,
clientPublicKey = receiverPublicBytes,
serverPublicKey = senderPublicBytes,
)
).toByteRange(end = 16)
nonce = hmacSha256Plus1(
prk,
createInfo(
prefix = prefixCeNonceZP256Z,
clientPublicKey = receiverPublicBytes,
serverPublicKey = senderPublicBytes,
)
).toByteRange(end = 12)
}
fun decode(src: ByteRange): ByteRange {
val cip = Cipher.getInstance("AES/GCM/NoPadding", provider)
cip.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(key.ba, key.start, key.size, "AES"),
GCMParameterSpec(
GCM_TAG_BITS,
nonce.ba, nonce.start, nonce.size
),
)
var dst = cip.doFinal(src.ba, src.start, src.size).toByteRange()
// 多分不要だと思うが、
// https://greenbytes.de/tech/webdav/draft-ietf-httpbis-encryption-encoding-02.html
// にあるレコード先頭のパディングを除去する
// テキストメッセージなら nul文字が含まれないのでヒットしないはず
if (dst.size >= 3 && dst[2] == b0) {
val reader = dst.byteRangeReader()
val padLen = 2 + reader.readUInt16()
reader.skip(padLen)
dst = reader.remainBytes()
}
// 先頭の空白を除去する
var i = 0
while (i < dst.size && dst[i] in 0..32) ++i
if (i > 0) dst = dst.subRange(start = i)
return dst
}
}

View File

@ -0,0 +1,58 @@
package jp.juggler.crypt
import org.apache.commons.codec.binary.Base64.encodeBase64String
import org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
/**
* バイト配列の一部を参照する
*/
class ByteRange(
val ba: ByteArray,
val start: Int,
end: Int,
) {
companion object {
val empty = ByteRange(ByteArray(0), 0, 0)
val UTF8 = StandardCharsets.UTF_8.name()!!
}
val size = end - start
val indices = 0 until size
fun subRange(start: Int = 0, end: Int = size) =
ByteRange(ba, start = this.start + start, end = this.start + end)
operator fun get(pos: Int) = ba[start + pos]
fun toByteArray() =
ba.copyOfRange(start, start + size)
fun elementAtOrNull(pos: Int) =
if (pos in indices) ba.elementAtOrNull(start + pos) else null
fun encodeBase64Url(): String =
encodeBase64URLSafeString(toByteArray())
fun encodeBase64(): String =
encodeBase64String(toByteArray())
fun decodeUTF8() =
java.lang.String(ba, start, size, UTF8)
fun copyElements(
dst: ByteArray,
dstOffset: Int = 0,
srcOffset: Int = 0,
length: Int = size - srcOffset,
) = System.arraycopy(ba, start + srcOffset, dst, dstOffset, length)
}
fun ByteArray.toByteRange(start: Int = 0, end: Int = size) = ByteRange(this, start, end)
fun ByteArrayOutputStream.write(src: ByteRange, start: Int = 0, len: Int = src.size) =
write(src.ba, src.start + start, len)
fun ByteArrayOutputStream.toByteRange(start: Int = 0, end: Int = size()) =
toByteArray().toByteRange(start = start, end = end)

View File

@ -0,0 +1,53 @@
package jp.juggler.crypt
class ByteRangeReader(
private val src: ByteRange,
) {
private val end = src.size
private var pos = 0
fun skip(length: Int) {
pos += length
}
fun readBytes(size: Int = end - pos): ByteRange {
if (pos + size > end) error("unexpected end.")
val rv = src.subRange(pos, pos + size)
pos += size
return rv
}
/**
* 残り全部
*/
fun remainBytes() = readBytes()
fun readUInt8(): Int {
if (pos >= end) error("unexpected end.")
val b = src[pos++]
return b.toInt().and(255)
}
fun readUInt16(): Int {
if (pos + 2 > end) error("unexpected end.")
// Big Endian
val b0 = src[pos].toInt().and(255).shl(8)
val b1 = src[pos + 1].toInt().and(255)
pos += 2
return b0.or(b1)
}
fun readUInt32(): Int {
if (pos + 4 > end) error("unexpected end.")
// Big Endian
val b0 = src[pos].toInt().and(255).shl(24)
val b1 = src[pos + 1].toInt().and(255).shl(16)
val b2 = src[pos + 2].toInt().and(255).shl(8)
val b3 = src[pos + 3].toInt().and(255)
pos += 4
return b0.or(b1).or(b2).or(b3)
}
}
fun ByteRange.byteRangeReader() = ByteRangeReader(this)
fun ByteArray.byteRangeReader() = ByteRangeReader(this.toByteRange())

View File

@ -0,0 +1,195 @@
package jp.juggler.crypt
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.ECPointUtil
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import org.conscrypt.Conscrypt
import java.math.BigInteger
import java.security.*
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.spec.ECGenParameterSpec
import java.security.spec.ECPrivateKeySpec
import java.security.spec.ECPublicKeySpec
import java.security.spec.PKCS8EncodedKeySpec
import javax.crypto.KeyAgreement
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
val defaultSecurityProvider by lazy {
Conscrypt.newProvider()!!.also { Security.addProvider(it) }
}
const val DEFAULT_CURVE_NAME = "secp256r1"
const val GCM_TAG_BITS = 16 * 8
// HKDFのExpandの末尾に付与する
private val hkdfTrailBytes = ByteArray(1) { 1 }.toByteRange()
// k=v;k=v;... を解釈する
fun String.parseSemicolon() = split(";").map { pair ->
pair.split("=", limit = 2).map { it.trim() }
}.mapNotNull {
when {
it.isEmpty() -> null
else -> it[0] to it.elementAtOrNull(1)
}
}.toMap()
// ECPrivateKey のS値を32バイトの配列にコピーする
// BigInteger.toByteArrayは可変長なので、長さの調節を行う
fun encodePrivateKeyRaw(key: ECPrivateKey): ByteArray {
val srcBytes = key.s.toByteArray()
return when {
srcBytes.size == 32 -> srcBytes
// 32バイト以内なら先頭にゼロを詰めて返す
srcBytes.size < 32 -> ByteArray(32).also {
System.arraycopy(
srcBytes, 0,
it, it.size - srcBytes.size,
srcBytes.size
)
}
// ビッグエンディアンの先頭に符号ビットが付与されるので、32バイトに収まらない場合がある
// 末尾32バイト分を返す
else -> ByteArray(32).also {
System.arraycopy(
srcBytes, srcBytes.size - it.size,
it, 0,
it.size
)
}
}
}
/**
* WebPushのp256dhつまり公開鍵を X9.62 uncompressed format で符号化したバイト列を作る
* - 出力の長さは65バイト
*/
fun encodeP256Dh(src: ECPublicKey): ByteArray = src.run {
val bitsInByte = 8
val keySizeBytes = (params.order.bitLength() + bitsInByte - 1) / bitsInByte
return ByteArray(1 + 2 * keySizeBytes).also { dst ->
var offset = 0
dst[offset++] = 0x04
w.affineX.toByteArray().let { x ->
when {
x.size <= keySizeBytes ->
System.arraycopy(
x, 0,
dst, offset + keySizeBytes - x.size,
x.size
)
x.size == keySizeBytes + 1 && x[0].toInt() == 0 ->
System.arraycopy(
x, 1,
dst, offset,
keySizeBytes
)
else -> error("x value is too large")
}
}
offset += keySizeBytes
w.affineY.toByteArray().let { y ->
when {
y.size <= keySizeBytes -> System.arraycopy(
y, 0,
dst, offset + keySizeBytes - y.size,
y.size
)
y.size == keySizeBytes + 1 && y[0].toInt() == 0 -> System.arraycopy(
y, 1,
dst, offset,
keySizeBytes
)
else -> error("y value is too large")
}
}
}
}
// JavaScriptのcreateECDHがエンコードした秘密鍵をデコードする
// https://github.com/nodejs/node/blob/main/lib/internal/crypto/diffiehellman.js#L232
// https://github.com/nodejs/node/blob/main/src/crypto/crypto_ec.cc#L265
fun Provider.decodePrivateKeyRaw(srcBytes: ByteArray): ECPrivateKey {
// 符号拡張が起きないように先頭に0を追加する
val newBytes = ByteArray(srcBytes.size + 1).also {
System.arraycopy(
srcBytes, 0,
it, it.size - srcBytes.size,
srcBytes.size
)
}
val s = BigInteger(newBytes)
// テキトーに鍵を作る
val keyPair = generateKeyPair()
// params部分を取り出す
val ecParameterSpec = (keyPair.private as ECPrivateKey).params
// s値を指定して鍵を作る
val privateKeySpec = ECPrivateKeySpec(s, ecParameterSpec)
return KeyFactory.getInstance("EC", this)
.generatePrivate(privateKeySpec) as ECPrivateKey
}
/**
* ECの鍵ペアを作成する
*/
fun Provider.generateKeyPair(curveName: String = DEFAULT_CURVE_NAME): KeyPair =
KeyPairGenerator.getInstance("EC", this).apply {
@Suppress("SpellCheckingInspection")
initialize(ECGenParameterSpec(curveName))
}.genKeyPair() ?: error("genKeyPair returns null")
fun Mac.update(br: ByteRange) = update(br.ba, br.start, br.size)
fun hmacSha256(salt: ByteRange, keyMaterial: ByteRange, plus1: Boolean = false) =
Mac.getInstance("HMacSHA256").run {
init(SecretKeySpec(salt.ba, salt.start, salt.size, "HMacSHA256"))
update(keyMaterial)
if (plus1) update(hkdfTrailBytes)
doFinal()
} ?: error("Mac.doFinal returns null")
/**
* HKDF の2段階目は末尾に \01 を追加する
*/
fun hmacSha256Plus1(salt: ByteRange, keyMaterial: ByteRange) =
hmacSha256(salt, keyMaterial, plus1 = true)
// 鍵全体をX509でエンコードしたものをデコードする
fun Provider.decodePrivateKeyX509(src: ByteArray) =
KeyFactory.getInstance("EC", this)
.generatePrivate(PKCS8EncodedKeySpec(src))
?: error("generatePrivate returns null")
/**
* p256dh(65バイト)から公開鍵を復元する
* - ECPointUtil.decodePoint はバイト配列全体を指定するしかないのでsrc引数はByteArrayである
*/
fun Provider.decodeP256dh(src: ByteArray, curveName: String = DEFAULT_CURVE_NAME): ECPublicKey {
val spec = ECNamedCurveTable.getParameterSpec(curveName)
val params = ECNamedCurveSpec(curveName, spec.curve, spec.g, spec.n)
val pubKeySpec = ECPublicKeySpec(
ECPointUtil.decodePoint(params.curve, src),
params
)
return KeyFactory.getInstance("EC", this)
.generatePublic(pubKeySpec) as ECPublicKey
}
fun Provider.sharedKeyBytes(
receiverPrivateBytes: ByteArray,
senderPublicBytes: ByteArray,
) = KeyAgreement.getInstance("ECDH", this).run {
val receiverPrivate = decodePrivateKeyX509(receiverPrivateBytes)
val senderPublic = decodeP256dh(senderPublicBytes)
init(receiverPrivate)
doPhase(senderPublic, true)
generateSecret()
}.toByteRange()

View File

@ -0,0 +1,58 @@
package jp.juggler.util.data
import java.io.ByteArrayOutputStream
/**
* Base128 エンコーディング
*
* FCMで何文字送れるか試験したところ文字種により送れる文字数に差異はあった
* n=4050 c=a 1bytes
* n=4050 c=\u0000 1bytes
* n=4050 c=\u000a 1bytes
* n=2025 c=\u00a9 2bytes
* n=1350 c=\u82b1 3bytes
*
* - jsonのエスケープなどは考慮しなくてよさそう
* - UTF-8表現でバイト数が増えると送れる文字数が減る
*
* UTF-8の長い表現を使うと使えるビットがむしろ減るので
* UTF-8 0x00-0x7f の範囲の文字にエンコードするのが効率が良さそうだった
*
*/
object Base128 {
fun String.decodeBase128(): ByteArray =
ByteArrayOutputStream(this.length).also {
var bits = 0
var bitsUsed = 0
for (c in this) {
bits = bits.shl(7).or(c.code.and(0x7f))
bitsUsed += 7
if (bitsUsed >= 8) {
val outByte = bits.shr(bitsUsed - 8).and(0xff)
it.write(outByte)
bitsUsed -= 8
}
}
// bitsUsedに8未満のbitが残ることがあるが、末尾のパディングなので読み捨てる
}.toByteArray()
fun ByteArray.encodeBase128(): String =
StringBuilder(this.size).also {
var bits = 0
var bitsUsed = 0
for (inByte in this) {
bits = bits.shl(8).or(inByte.toInt().and(255))
bitsUsed += 8
while (bitsUsed >= 7) {
val outBits = bits.shr(bitsUsed - 7).and(0x7f)
bitsUsed -= 7
it.append(outBits.toChar())
}
}
if (bitsUsed > 0) {
val outBits = bits.shl(7 - bitsUsed).and(0x7f)
it.append(outBits.toChar())
}
}.toString()
}

View File

@ -0,0 +1,285 @@
package jp.juggler.util.data
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.StandardCharsets
/**
* BinPack format
*
* アプリサーバからプッシュサービス経由で受け取るデータを包む
* - 構造的にはJSONに似ている階層を持つデータをエンコードできる
*
* Pros:
* - バイト配列をエンコードできる
* - 整数浮動少数をエンコードできる
* - エンコードされたデータは外部ライブラリの仕様変更に影響されない
* - 内部実装のenumの名前やorderに依存しない
*
* Cons:
* - ORMをサポートしない
* - デコード結果の解読は生jsonと同程度に面倒
* - ByteArray以外のプリミティブ配列をサポートしない
*
* Usage:
* ListやMap をエンコード/デコードしたら BinPackMap BinPackList になる
* 利便性のために string(k), bytes(k), map(k) などのメソッドがある
*
* Background:
* WebPushで暗号化されたバイト配列とHTTPヘッダ情報と宛先ハッシュを送るため
* バイナリの汎用データフォーマットが欲しかった
* 既存のものをいくつか検討した結果300行程度で済むなら自前実装
*
*
*/
fun Any?.encodeBinPack(): ByteArray =
ByteArrayOutputStream().use {
BinPackWriter(it).writeValue(this)
it.flush()
it.toByteArray()
}
fun ByteArray.decodeBinPack(): Any? =
ByteArrayInputStream(this).use {
BinPackReader(it).readValue()
}
fun ByteArray.decodeBinPackMap() = decodeBinPack() as? BinPackMap
@Suppress("unused")
fun ByteArray.decodeBinPackList() = decodeBinPack() as? BinPackList
class BinPackMap() : HashMap<Any?, Any?>() {
constructor(vararg pairs: Pair<Any?, Any?>) : this() {
putAll(pairs)
}
fun string(k: Any?) = get(k) as? String
fun bytes(k: Any?) = get(k) as? ByteArray
fun map(k: Any?) = get(k) as? BinPackMap
}
class BinPackList() : ArrayList<Any?>() {
constructor(vararg args: Any?) : this() {
addAll(args)
}
fun string(k: Int) = elementAtOrNull(k) as? String
@Suppress("unused")
fun bytes(k: Int) = elementAtOrNull(k) as? ByteArray
fun map(k: Int) = elementAtOrNull(k) as? BinPackMap
}
private enum class ValueType(val id: Int) {
VNull(0),
VTrue(1),
VFalse(2),
VBytes(3),
VString(4),
VList(5),
VMap(6),
VDouble(7),
VFloat(8),
@Suppress("unused")
VInt8(10), // Byte, signed
VInt16(11),
VInt32(12),
VInt64(13),
VUInt16(15), // Char, unsigned
;
companion object {
val valuesCache = values()
val idMap = valuesCache.associateBy { it.id }
}
}
private class BinPackWriter(
private val out: OutputStream,
) {
private fun writeInt16(n: Int) {
out.write(n)
out.write(n.ushr(8))
}
private fun writeInt32(n: Int) {
out.write(n)
out.write(n.ushr(8))
out.write(n.ushr(16))
out.write(n.ushr(24))
}
private fun writeInt64(n: Long) {
writeInt32(n.toInt())
writeInt32(n.ushr(32).toInt())
}
private fun writeDouble(n: Double) = writeInt64(n.toRawBits())
private fun writeFloat(n: Float) = writeInt32(n.toRawBits())
private fun writeVType(t: ValueType) = out.write(t.id)
private fun writeSize(n: Int) = writeInt32(n)
fun writeValue(value: Any?) {
when (value) {
null -> writeVType(ValueType.VNull)
true -> writeVType(ValueType.VTrue)
false -> writeVType(ValueType.VFalse)
is ByteArray -> {
writeVType(ValueType.VBytes)
writeSize(value.size)
out.write(value)
}
is String -> {
writeVType(ValueType.VString)
val encoded = value.toByteArray(StandardCharsets.UTF_8)
writeSize(encoded.size)
out.write(encoded)
}
is Collection<*> -> {
writeVType(ValueType.VList)
writeSize(value.size)
for (x in value) {
writeValue(x)
}
}
is Array<*> -> {
writeVType(ValueType.VList)
writeSize(value.size)
for (x in value) {
writeValue(x)
}
}
is Map<*, *> -> {
writeVType(ValueType.VMap)
val entries = value.entries
writeSize(entries.size)
for (entry in value.entries) {
writeValue(entry.key)
writeValue(entry.value)
}
}
is Double -> {
writeVType(ValueType.VDouble)
writeDouble(value)
}
is Float -> {
writeVType(ValueType.VFloat)
writeFloat(value)
}
is Long -> {
writeVType(ValueType.VInt64)
writeInt64(value)
}
is Int -> {
writeVType(ValueType.VInt32)
writeInt32(value)
}
is Short -> {
writeVType(ValueType.VInt16)
writeInt16(value.toInt())
}
is Char -> {
writeVType(ValueType.VUInt16)
writeInt16(value.code)
}
is Byte -> {
writeVType(ValueType.VInt8)
out.write(value.toInt())
}
else -> error("unsupported type ${value.javaClass.simpleName}")
}
}
}
private class BinPackReader(
private val ins: InputStream,
) {
private fun readUInt8(): Int {
val n = ins.read().and(255)
if (n == -1) error("unexpected end")
return n
}
private fun readUInt16(): Int {
val b0 = readUInt8()
val b1 = readUInt8().shl(8)
return b0.or(b1)
}
private fun readInt32(): Int {
val b0 = readUInt8()
val b1 = readUInt8().shl(8)
val b2 = readUInt8().shl(16)
val b3 = readUInt8().shl(24)
return b0.or(b1).or(b2).or(b3)
}
private fun readInt64(): Long {
val low = readInt32().toLong().and(0xFFFFFFFFL)
val high = readInt32().toLong().shl(32)
return low.or(high)
}
private fun readFloat() = Float.fromBits(readInt32())
private fun readDouble() = Double.fromBits(readInt64())
private fun readBytes(size: Int) = ByteArray(size).also { dst ->
var nRead = 0
while (nRead < size) {
val delta = ins.read(dst, nRead, size - nRead)
if (delta < 0) error("unexpected end.")
if (delta > 0) nRead += delta
}
}
private fun readSize() = readInt32()
fun readValue(): Any? {
val id = readUInt8()
return when (ValueType.idMap[id]) {
null -> error("unknown type id=$id")
ValueType.VNull -> null
ValueType.VTrue -> true
ValueType.VFalse -> false
ValueType.VBytes -> readBytes(readSize())
ValueType.VString -> readBytes(readSize()).toString(StandardCharsets.UTF_8)
ValueType.VList -> BinPackList().apply {
val size = readSize()
ensureCapacity(size)
repeat(size) { add(readValue()) }
}
ValueType.VMap -> BinPackMap().apply {
repeat(readSize()) {
val k = readValue()
val v = readValue()
put(k, v)
}
}
ValueType.VDouble -> readDouble()
ValueType.VFloat -> readFloat()
ValueType.VInt8 -> readUInt8().toByte()
ValueType.VInt16 -> readUInt16().toShort()
ValueType.VInt32 -> readInt32()
ValueType.VInt64 -> readInt64()
ValueType.VUInt16 -> readUInt16().toChar()
}
}
}

View File

@ -5,46 +5,48 @@ import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.util.Base64
import androidx.core.net.toUri
import jp.juggler.util.log.LogCategory
import org.apache.commons.codec.binary.Base64.*
import java.security.MessageDigest
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
object StringUtils {
private val log = LogCategory("StringUtils")
val log = LogCategory("StringUtils")
private const val HEX_LOWER = "0123456789abcdef"
val hexLower =
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
// BDI制御文字からその制御文字を閉じる文字を得るためのマップ
val SanitizeBdiMap = HashMap<Char, Char>().apply {
// BDI制御文字からその制御文字を閉じる文字を得るためのマップ
val sanitizeBdiMap = HashMap<Char, Char>().apply {
val PDF = 0x202C.toChar() // Pop directional formatting (PDF)
this[0x202A.toChar()] = PDF // Left-to-right embedding (LRE)
this[0x202B.toChar()] = PDF // Right-to-left embedding (RLE)
this[0x202D.toChar()] = PDF // Left-to-right override (LRO)
this[0x202E.toChar()] = PDF // Right-to-left override (RLO)
val PDF = 0x202C.toChar() // Pop directional formatting (PDF)
this[0x202A.toChar()] = PDF // Left-to-right embedding (LRE)
this[0x202B.toChar()] = PDF // Right-to-left embedding (RLE)
this[0x202D.toChar()] = PDF // Left-to-right override (LRO)
this[0x202E.toChar()] = PDF // Right-to-left override (RLO)
val PDI = 0x2069.toChar() // Pop directional isolate (PDI)
this[0x2066.toChar()] = PDI // Left-to-right isolate (LRI)
this[0x2067.toChar()] = PDI // Right-to-left isolate (RLI)
this[0x2068.toChar()] = PDI // First strong isolate (FSI)
val PDI = 0x2069.toChar() // Pop directional isolate (PDI)
this[0x2066.toChar()] = PDI // Left-to-right isolate (LRI)
this[0x2067.toChar()] = PDI // Right-to-left isolate (RLI)
this[0x2068.toChar()] = PDI // First strong isolate (FSI)
// private const val ALM = 0x061c.toChar() // Arabic letter mark (ALM)
// private const val LRM = 0x200E.toChar() // Left-to-right mark (LRM)
// private const val RLM = 0x200F.toChar() // Right-to-left mark (RLM)
}
// private const val ALM = 0x061c.toChar() // Arabic letter mark (ALM)
// private const val LRM = 0x200E.toChar() // Left-to-right mark (LRM)
// private const val RLM = 0x200F.toChar() // Right-to-left mark (RLM)
}
////////////////////////////////////////////////////////////////////
// ByteArray
fun ByteArray.encodeBase64Url(): String =
Base64.encodeToString(this, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
encodeBase64URLSafeString(this)
fun ByteArray.encodeBase64(): String =
encodeBase64String(this)
fun String.decodeBase64(): ByteArray =
decodeBase64(this)
fun ByteArray.digestSHA256(): ByteArray {
val digest = MessageDigest.getInstance("SHA-256")
@ -171,8 +173,8 @@ fun ByteArray.encodeHex(): String {
}
fun StringBuilder.appendHex2(value: Int): StringBuilder {
this.append(StringUtils.hexLower[(value shr 4) and 15])
this.append(StringUtils.hexLower[value and 15])
append(HEX_LOWER[(value shr 4) and 15])
append(HEX_LOWER[value and 15])
return this
}
@ -181,8 +183,8 @@ fun ByteArray.encodeHexLower(): String {
val sb = StringBuilder(size * 2)
for (i in 0 until size) {
val value = this[i].toInt()
sb.append(StringUtils.hexLower[(value shr 4) and 15])
sb.append(StringUtils.hexLower[value and 15])
sb.append(HEX_LOWER[(value shr 4) and 15])
sb.append(HEX_LOWER[value and 15])
}
return sb.toString()
}
@ -220,7 +222,7 @@ fun String.sanitizeBDI(): String {
// 文字列をスキャンしてBDI制御文字をスタックに入れていく
var stack: LinkedList<Char>? = null
for (c in this) {
val closer = StringUtils.sanitizeBdiMap[c]
val closer = SanitizeBdiMap[c]
if (closer != null) {
if (stack == null) stack = LinkedList()
stack.add(closer)

View File

@ -0,0 +1,174 @@
package jp.juggler.util.log
import android.util.Log
import jp.juggler.base.BuildConfig
import jp.juggler.util.data.notEmpty
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.math.min
object AdbLog {
private const val APP_TAG = "AppTag_PushReceiver"
private const val MAX_LOG_LENGTH = 4000
@Suppress("RegExpSimplifiable")
val reAnonymousClass = """(\.\$[0-9]+)+$""".toRegex()
private val callStackTag: String
get() {
// DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
// because Robolectric runs them on the JVM but on Android the elements are different.
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
val trace = Throwable().stackTrace
val stackTraceElement = trace.elementAtOrNull(4)
?: error("callStackTag: stacktrace didn't have enough elements: are you using proguard?")
stackTraceElement.fileName.notEmpty()
?.let {
when (val lastDotPos = it.lastIndexOf('.')) {
-1 -> it
else -> it.substring(0, lastDotPos)
}
}
?.let { return it }
return reAnonymousClass.replace(stackTraceElement.className, "")
.let { it.substring(it.lastIndexOf('.') + 1) }
}
private fun Throwable.dump(): String {
// Don't replace this with Log.getStackTraceString() - it hides
// UnknownHostException, which is not what we want.
val sw = StringWriter(256)
val pw = PrintWriter(sw, false)
this.printStackTrace(pw)
pw.flush()
return sw.toString()
}
private fun printlnOrWtf(priority: Int, message: String) {
try {
if (priority == Log.ASSERT) {
Log.wtf(APP_TAG, message)
} else {
Log.println(priority, APP_TAG, message)
}
} catch (ignored: Throwable) {
// 単体テストで呼ばれた?
println(message)
}
}
private inline fun splitLines(
prefix: String,
message: CharSequence,
block: (CharSequence) -> Unit,
) {
val limit = MAX_LOG_LENGTH - prefix.length
if (message.length < limit) {
block(prefix + message)
} else {
// Split by line, then ensure each line can fit into Log's maximum length.
val length = message.length
var i = 0
while (i < length) {
val newline = message.indexOf('\n', i).takeIf { it >= 0 } ?: length
do {
val end = min(newline, i + limit)
val part = message.subSequence(i, end)
block(prefix + part)
i = end
} while (i < newline)
// skip \n
++i
}
}
}
fun log(
priority: Int,
ex: Throwable? = null,
messageArg: String? = null,
prefixArg: String? = null,
) {
if (priority < Log.INFO && !BuildConfig.DEBUG) return
// 本文の先頭に付与するprefix
val tag = (prefixArg ?: callStackTag)
val prefix = when {
tag.isBlank() -> ""
else -> "$tag: "
}
val stackTrace = ex?.dump()
// 本文とスタックトレース
val sb = StringBuilder()
val message = messageArg?.takeIf { it.isNotEmpty() }?.also { sb.append(it) }
if (stackTrace != null) {
if (message != null) sb.append("\n")
sb.append(stackTrace)
}
splitLines(prefix, sb) {
printlnOrWtf(priority, it.toString())
}
// LogEntity.saveLog(priority, tag, message, stackTrace)
}
@JvmStatic
fun v(msg: String) = log(Log.VERBOSE, messageArg = msg)
@JvmStatic
fun d(msg: String) = log(Log.DEBUG, messageArg = msg)
@JvmStatic
fun i(msg: String) = log(Log.INFO, messageArg = msg)
@JvmStatic
fun w(msg: String) = log(Log.WARN, messageArg = msg)
@JvmStatic
fun e(msg: String) = log(Log.ERROR, messageArg = msg)
@JvmStatic
fun wtf(msg: String) = log(Log.ASSERT, messageArg = msg)
@JvmStatic
fun recordFirebaseCrashlytics(ex: Throwable?) {
try {
ex ?: return
// FirebaseCrashlytics.getInstance().recordException(ex)
} catch (ignored: Throwable) {
// 単体テストで FirebaseCrashlytics.getInstance() が例外を出す
}
}
@JvmStatic
@JvmOverloads
fun i(ex: Throwable?, msg: String? = null) {
recordFirebaseCrashlytics(ex)
log(Log.INFO, ex = ex, messageArg = msg)
}
@JvmStatic
@JvmOverloads
fun w(ex: Throwable?, msg: String? = null) {
recordFirebaseCrashlytics(ex)
log(Log.WARN, ex = ex, messageArg = msg)
}
@JvmStatic
@JvmOverloads
fun e(ex: Throwable?, msg: String? = null) {
recordFirebaseCrashlytics(ex)
log(Log.ERROR, ex = ex, messageArg = msg)
}
@JvmStatic
@JvmOverloads
fun wtf(ex: Throwable?, msg: String? = null) {
recordFirebaseCrashlytics(ex)
log(Log.ASSERT, ex = ex, messageArg = msg)
}
}

View File

@ -0,0 +1,31 @@
package jp.juggler.pushreceiverapp
import jp.juggler.util.data.Base128.decodeBase128
import jp.juggler.util.data.Base128.encodeBase128
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.io.ByteArrayOutputStream
class Base128Test {
@Test
fun useBase128() {
for (len in 0..20) {
for (i in 0 until 256) {
val orig = ByteArrayOutputStream(32)
.apply {
repeat(len) {
write(i)
}
}.toByteArray()
val encoded = orig.encodeBase128()
val decoded = encoded.decodeBase128()
assertArrayEquals(
"len=$len,i=$i",
orig,
decoded
)
}
}
}
}

View File

@ -0,0 +1,164 @@
package jp.juggler
import jp.juggler.util.data.BinPackList
import jp.juggler.util.data.BinPackMap
import jp.juggler.util.data.decodeBinPack
import jp.juggler.util.data.encodeBinPack
import org.junit.Assert.*
import org.junit.Test
class BinPackTest {
@Test
fun testTypes() {
fun ByteArray.dump() = joinToString(" ") { "%d".format(it) }
fun encodeDecode(v: Any?, expected: Any? = v) {
val encoded = v.encodeBinPack()
val decoded = encoded.decodeBinPack()
val message = "($v ${v?.javaClass?.simpleName}) dump=${encoded.dump()}"
when {
expected is ByteArray -> assertArrayEquals(
"${v?.javaClass?.simpleName} $v",
expected,
decoded as? ByteArray
)
v is Set<*> -> {
val decodedSet = (decoded as? BinPackList)?.toSet()
assertNotNull("$message decoded?", decodedSet)
assertEquals("$message same size", v.size, decodedSet!!.size)
assertTrue("$message containsAll 1", v.containsAll(decodedSet))
assertTrue("$message containsAll 2", decodedSet.containsAll(v))
}
else -> assertEquals(
message,
expected,
decoded
)
}
}
encodeDecode(null)
encodeDecode(true)
encodeDecode(false)
encodeDecode(ByteArray(0))
encodeDecode(ByteArray(1) { it.toByte() })
encodeDecode(ByteArray(3) { it.toByte() })
encodeDecode("")
encodeDecode("日本語")
encodeDecode(emptyArray<Any?>(), BinPackList())
encodeDecode(emptyList<Any?>(), BinPackList())
encodeDecode(emptySet<Any?>(), BinPackList())
encodeDecode(arrayOf<Any?>(null), BinPackList(null))
encodeDecode(listOf<Any?>(null), BinPackList(null))
encodeDecode(setOf<Any?>(null))
encodeDecode(arrayOf("a"), BinPackList("a"))
encodeDecode(listOf("a"), BinPackList("a"))
encodeDecode(setOf("a"))
encodeDecode(emptyMap<Any?, Any?>(), BinPackMap())
encodeDecode(mapOf<Any?, Any?>(null to null), BinPackMap(null to null))
encodeDecode(mapOf<Any?, Any?>(1 to 1), BinPackMap(1 to 1))
fun doubleStepSequence(start: Double, endInclusive: Double, step: Double) =
sequence {
var v = start
while (true) {
yield(v)
val newValue = v + step
// 範囲を超えたか、オーバーフローで同じ値になるか
if (newValue > endInclusive || newValue == v) break
v = newValue
}
}
fun floatStepSequence(start: Float, endInclusive: Float, step: Float) =
sequence {
var v = start
while (true) {
yield(v)
val newValue = v + step
// 範囲を超えたか、オーバーフローで同じ値になるか
if (newValue > endInclusive || newValue == v) break
v = newValue
}
}
// - ビット数の多い数値型は適当に端折るが、下位ビットが毎回同じにならないよう stepを工夫する
encodeDecode(0.0)
encodeDecode(Double.NaN)
encodeDecode(Double.NEGATIVE_INFINITY)
encodeDecode(Double.POSITIVE_INFINITY)
encodeDecode(Double.MIN_VALUE) // nealy 0 positive value
encodeDecode(-Double.MAX_VALUE) // negative min end
encodeDecode(Double.MAX_VALUE)
run {
var callCount = 0
for (n in doubleStepSequence(
-Double.MAX_VALUE,
Double.MAX_VALUE,
Double.MAX_VALUE / (256 - 7 - Double.MIN_VALUE)
)) {
encodeDecode(n)
++callCount
}
assertEquals("callCount", 498, callCount)
}
encodeDecode(0f)
encodeDecode(Float.NaN)
encodeDecode(Float.NEGATIVE_INFINITY)
encodeDecode(Float.POSITIVE_INFINITY)
encodeDecode(Float.MIN_VALUE) // nealy 0 positive value
encodeDecode(-Float.MAX_VALUE) // negative min end
encodeDecode(Float.MAX_VALUE)
run {
var callCount = 0
for (n in floatStepSequence(
-Float.MAX_VALUE,
Float.MAX_VALUE,
Float.MAX_VALUE / (256 - 7 - Float.MIN_VALUE)
)) {
encodeDecode(n)
++callCount
}
assertEquals("callCount", 499, callCount)
}
// - ByteとShortにはIntRangeと同等のクラスがない
encodeDecode(0.toByte())
encodeDecode(Byte.MIN_VALUE)
encodeDecode(Byte.MAX_VALUE)
for (n in Byte.MIN_VALUE.toInt()..Byte.MAX_VALUE.toInt()) {
encodeDecode(n.toByte())
}
encodeDecode(0.toShort())
encodeDecode(Short.MIN_VALUE)
encodeDecode(Short.MAX_VALUE)
for (n in Short.MIN_VALUE.toInt()..Short.MAX_VALUE.toInt() step 1.shl(Short.SIZE_BITS - 8) - 7) {
encodeDecode(n.toShort())
}
encodeDecode(0.toChar())
encodeDecode(Char.MIN_VALUE)
encodeDecode(Char.MAX_VALUE)
for (n in Char.MIN_VALUE..Char.MAX_VALUE step 1.shl(Char.SIZE_BITS - 8) - 7) {
encodeDecode(n)
}
encodeDecode(0)
encodeDecode(Int.MIN_VALUE)
encodeDecode(Int.MAX_VALUE)
for (n in Int.MIN_VALUE..Int.MAX_VALUE step 1.shl(Int.SIZE_BITS - 8) - 7) {
encodeDecode(n)
}
encodeDecode(0.toLong())
encodeDecode(Long.MIN_VALUE)
encodeDecode(Long.MAX_VALUE)
for (n in Long.MIN_VALUE..Long.MAX_VALUE step 1L.shl(Long.SIZE_BITS - 8) - 7) {
encodeDecode(n)
}
}
}

View File

@ -0,0 +1,292 @@
package jp.juggler
import jp.juggler.crypt.*
import jp.juggler.util.data.decodeBase64
import jp.juggler.util.data.decodeJsonObject
import jp.juggler.util.log.AdbLog
import org.junit.Assert.*
import org.junit.Test
import java.security.interfaces.ECPrivateKey
@Suppress("SpellCheckingInspection")
class WebPushCryptTest {
// https://developers.google.com/web/updates/2016/03/web-push-encryption
@Test
fun testGenerateKeyPair() {
val provider = defaultSecurityProvider
val pair = provider.generateKeyPair()
val privateKey = (pair.private as ECPrivateKey)
val bytes = encodePrivateKeyRaw(privateKey)
val newKey = provider.decodePrivateKeyRaw(bytes)
assertEquals(
"s is same?",
privateKey.s,
newKey.s,
)
}
@Test
fun decryptMessage() {
// Authentication secret (auth_secret)
// 購読時に指定する
val authSecret = "BTBZMqHH6r4Tts7J_aSIgg".decodeBase64()
// User agent public key (ua_public)
// 購読時に指定する
val receiverPublicBytes =
"BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4".decodeBase64()
// User agent private key (ua_private)
val receiverPrivateBytesShort = "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94".decodeBase64()
// encryption ヘッダから
val saltBytes = "pr1_1DFjrzX3RNvJPRngDA".decodeBase64()
// Application server public key (as_public)
// crypto-key ヘッダの前半、dh=xxx; の中味
// crypto-key: dh=BLJQjupjQyujhTC--_5xRUgcBfAP_zINsAGTlaDuEME7s9TVgQYsyrzrgbt1vqScmZkoj4BWfPit6EzzaXDW02I;p256ecdsa=BDmWlrZ3gvcv0R7sBhaSp_99FRSC3bBNn9CElRvbcviwYwVPL1Z-G9srAJS6lv_pMe5IkTmKgBWUCNefnN3QoeQ
val senderPublicBytes =
"BLJQjupjQyujhTC--_5xRUgcBfAP_zINsAGTlaDuEME7s9TVgQYsyrzrgbt1vqScmZkoj4BWfPit6EzzaXDW02I"
.decodeBase64()
val body =
"pTTuh1jT8KJ4zaGwIWjg417KTDzh+eIVe472nMgett3XyhoM5pAz8Yu2RPBXJHE/AojoMA1g+/uzbByu3d1/AygBh99qJ6Xtjya+XBSYoVrNJqT7vq0cKU9bZ8NrEepnaZUc2HjFUDDXNyHi2xBtJnMk/hSZTzyaiCQS2KssGAwixgdK/dTP8Yg+Pul3tgOQvq5CbYFd7iwBQntVv80vO8X+5hyIglA21+6/2fq5lCZSMri5K9/WbSb6erLkxO//A92KjZTnuufE4pUwtIdYW1bFnw5xu6ozjsCsDLbQTSo+JmghOzc/iYx5hG+y5YViC1UXue4eKKlmjbVDRLH6WkEEIKH2cwd4Gf9ewhYwhH7oKKIc4tjvRunq2gtBirQgRYJahgfwykdYA44iyogBc1rFZPGbxr1ph4RxVhdBmIZ+yMN6GQSiDCS+8jKGsc5xnjxrSXXdFva1a2xc1lpiReypZlTTXFmF16Cf+Z6B0UvFTa2AcqEDD0BBlhhbMBoG7n4CRjr5ObE2lG5PBg+gqitx/O1S+X8a4N78L+eK1upEVM+HRQAdCmiqDNJF0/N/VWSMrNCl7HNgnhmYU9Z1aYepiEioz1Tu14UzY/2NOx5z4h4szyJW8s/diAyOhnh+RBRM3QLHtygpLZ3i7o6vVUc="
.decodeBase64()
AesGcmDecoder(
// receiver private key in X509 format
receiverPrivateBytes =
defaultSecurityProvider.decodePrivateKeyRaw(receiverPrivateBytesShort).encoded,
// receiver public key in 65bytes X9.62 uncompressed format
receiverPublicBytes = receiverPublicBytes,
// sender public key in 65bytes X9.62 uncompressed format
senderPublicBytes = senderPublicBytes,
// auth secrets created at subscription
authSecret = authSecret,
// salt in HTTP header
saltBytes = saltBytes,
).run {
assertEquals(
"sharedKeyBytes",
"irnQ9JOfMP/kl/SB8LUHpvjmUjwlkYzypisDnlHVKSA=",
sharedKeyBytes.encodeBase64()
)
deriveKey()
assertEquals(
"ikm",
"Tq5bGvBQUWTQdqFfAK1WAE/etlIpcc07QLh+RNCAD9Y=",
ikm.encodeBase64()
)
assertEquals(
"contentEncryptionKey",
"patR3W6rY0PG/5YnwY3/kA==",
key.encodeBase64()
)
assertEquals(
"nonce",
"E/GxDDwW9lfa7/by",
nonce.encodeBase64()
)
decode(body.toByteRange())
}.decodeUTF8().let {
assertEquals(
"text",
"""{"title":"あなたのトゥートが tateisu 🤹 さんにお気に入り登録されました","image":null,"badge":"https://mastodon2.juggler.jp/badge.png","tag":84,"timestamp":"2018-05-11T17:06:42.887Z","icon":"/system/accounts/avatars/000/000/003/original/72f1da33539be11e.jpg","data":{"content":":enemy_bullet:","nsfw":null,"url":"https://mastodon2.juggler.jp/web/statuses/98793123081777841","actions":[],"access_token":null,"message":"%{count} 件の通知","dir":"ltr"}}""",
it
)
}
}
/**
* 今どきのMastodonはCrypto-Keyが異なる
* dh=XXX;p256ecdsa=XXX
*/
@Test
fun test2() {
// 購読時に分かった情報
val receiverPrivateBytes =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN1UVSn5P4XC9zuuT3sW8TTikA8AhsjZKp9W6BwzSlW2hRANCAAQSFVIOe_wsdFuDCKjrNRM1yWIkwlfx8ZoQ3OyYJ2K5oeXpmjo5EAimVq0Fs0NuxA8Y6F1hTB_Orc4gR1WOK-W4"
.decodeBase64()
val receiverPublicBytes =
"BBIVUg57_Cx0W4MIqOs1EzXJYiTCV_HxmhDc7JgnYrmh5emaOjkQCKZWrQWzQ27EDxjoXWFMH86tziBHVY4r5bg"
.decodeBase64()
val senderPublicBytesOld =
"BLzIgsz-VRhTxuVgNoQljTwAFzxbanfGxNk8tldaruBztvsK9elES_2lE_8c91-RNOInEBEFUrCzDw-60bzUCr8"
.decodeBase64()
val authSecret = "WrgqiWu8r3D9Ql3qYGkXTw"
.decodeBase64()
// プッシュメッセージに含まれる情報
val headerJson = """{
|"Digest":"SHA-256=kN1u9yotvg3utprFOaJ4qUGJ6cRhxGNfICXAGLjx7uM=",
|"Content-Encoding":"aesgcm",
|"Encryption":"salt=xtC3F3tO3UvQBtq-4Q38AQ",
|"Crypto-Key":"dh=BLivX-rukpGqww9YMqavS7o112MNTobqBqjzPX1ioUojdrDHKM1DwZKn6U2au0ddohfC4BGTDatjF96S7dOP2C8;p256ecdsa=BLzIgsz-VRhTxuVgNoQljTwAFzxbanfGxNk8tldaruBztvsK9elES_2lE_8c91-RNOInEBEFUrCzDw-60bzUCr8",
|"Authorization":"WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL21hc3RvZG9uLW1zZy5qdWdnbGVyLmpwIiwiZXhwIjoxNjc0ODQ3MjQ4LCJzdWIiOiJtYWlsdG86dGF0ZWlzdStqdWdnbGVyQGdtYWlsLmNvbSJ9.ksn_40KkHNcS0G3C8jMG-dsA6AcUydYYc1sA8JyXCCuDrNr-CLnl_-ezk1s-pC75yukNN0pIfqPJlJYtY_MQoQ",
|"<>cameFrom":"unifiedPush"
|}""".trimMargin().decodeJsonObject()
val body =
"4BVejMFrVADAEzkonO1QqUnDLiWCGAlzSbWg1KkG23BMZNLxUcQPJtULTTEMdoIzg4zZwDmsanDp5fepH8BDKpb5IoQlyFJjN0pq3tebWj79QL9h7QafxPByGx9HF9-zKlOUbmPMc3yS1tfccz30HvVSsMNUtcpuy1hsPkknwRBPI_SoLN20LEtAQ6IKLQGpiNMDx2rND9bd9wLz0Cr4pxUgZVxv0VjpiKHyTup0_BRotGd5e719uMmyDE-hzrqQYRr2QFrvHa1tNk41tpbPpeSAqhihsxU6bXxc4Lbws1Q6DyZBYHSUr6Av4SldA2Kmi3EjLijRvz3YEDWnSGv65V0SJv-O7tmFjexqFmFcF0qfv4Udzl9prjmutztcaWRAH-v9JdP3o2kthpK8RM4NC6Y0yU3GhEQ0z_1cfsuWsAvRe3H8pVcx3GXHVdWJ7VfqAwI-anSZz355-cuBbxAyrsBSp1Ysfru2D_5g53SZ7iNduHsXfKno9e6BkQr6u5pleXVHpRHYimzsjj46wbA0IfvEkhBpp9CoJ3JzPFkBowxpzuzmN1_IDiSDoYWVMYl1GXtwWOmKMKaKOCh3ULk2FH1WdUzNlvMd1N4Kf-MXjHFhkE33jSlLnKRLHqgzDEJgofWEzfoV-zruHFQfKwiN_7_NJdLYuGVV3z0IFiw6g9RdIvrNy7OEsTCaXbWC-xOEelJAvALvu308t_0PxxRdhSLVivrubyLZEKz1R8d6MzJ9b3f_mg6oYiGJ1u5oMrUvUUeNCERq4t9f0WMLo-zLFkNXvGe2TxnKMO9ZsWlXS1OnKnN6olNPKfVAFW50RxD6nBdP3S2a9uI58D9ycanCU_-i8osFrKg-GbYZ2tvRZw"
.decodeBase64()
// Encryption からsaltを読む
val saltBytes = headerJson.string("Encryption")?.parseSemicolon()
?.get("salt")?.decodeBase64()
?: error("missing Encryption.salt")
// Crypt-Key から dh と p256ecdsa を見る
val cryptKeys = headerJson.string("Crypto-Key")?.parseSemicolon()
?: error("missing Crypto-Key")
val senderPublicBytes = cryptKeys["dh"]?.decodeBase64()
?: senderPublicBytesOld
// JWT検証で使われるらしい
// val p256ecdsa = cryptKeys["p256ecdsa"]?.decodeBase64()
// ?: error("missing p256ecdsa")
AesGcmDecoder(
// receiver private key in X509 format
receiverPrivateBytes = receiverPrivateBytes,
// receiver public key in 65bytes X9.62 uncompressed format
receiverPublicBytes = receiverPublicBytes,
// sender public key in 65bytes X9.62 uncompressed format
senderPublicBytes = senderPublicBytes,
// auth secrets created at subscription
authSecret = authSecret,
// salt in HTTP header
saltBytes = saltBytes,
).run {
deriveKey()
decode(body.toByteRange())
}.decodeUTF8().let {
assertEquals(
"text",
"""{"access_token":"ON0yBbjmRS-QuSk5Uv7fjeKFVQESnYCZP39z71On68E","preferred_locale":"ja","notification_id":341159,"notification_type":"favourite","icon":"https://m1j.zzz.ac/accounts/avatars/000/008/939/original/112ed1e5343f2e7b.png","title":"tateisu⛏@テスト鯖 :ct080:さんにお気に入りに登録されました","body":"クライアントアプリにAPIキーとシークレットを持たせるという考え方は、クライアント側でシークレットが如何に漏洩しやすいかを考えると悪手としかいいようがない。簡単に漏洩して、アプリと無関係なbotに流用/悪用される"}""",
it
)
}
}
// https://github.com/web-push-libs/ecec/blob/master/src/decrypt.c
@Test
fun testRfc8188() {
val body = "I1BsxtFttlv3u_Oo94xnmwAAEAAA-NAVub2qFgBEuQKRapoZu-IxkIva3MEB1PD-ly8Thjg"
.decodeBase64()
val inputKey = "yqdlZ-tYemfogSmv7Ws5PQ"
.decodeBase64()
Aes128GcmDecoder(body.byteRangeReader()).run {
AdbLog.i("recordSize=$recordSize, keyId.size=${keyId.size}")
assertEquals(
"salt",
"I1BsxtFttlv3u_Oo94xnmw",
salt.encodeBase64Url()
)
assertEquals(
"keyId",
"",
keyId.encodeBase64Url()
)
deriveKeyRfc8188(inputKey.toByteRange())
assertEquals(
"prk",
"zyeH5phsIsgUyd4oiSEIy35x-gIi4aM7y0hCF8mwn9g",
prk.encodeBase64Url()
)
assertEquals(
"key",
"_wniytB-ofscZDh4tbSjHw",
key.encodeBase64Url()
)
assertEquals(
"NONCE",
"Bcs8gkIRKLI8GeI8",
nonce.encodeBase64Url()
)
decode()
}.decodeUTF8().let {
assertEquals(
"text",
"I am the walrus",
it
)
}
}
@Test
fun testRfc8188b() {
val inputKey = "BO3ZVPxUlnLORbVGMpbT1Q"
.decodeBase64()
val rawBody =
"uNCkWiNYzKTnBN9ji3-qWAAAABkCYTHOG8chz_gnvgOqdGYovxyjuqRyJFjEDyoF1Fvkj6hQPdPHI51OEUKEpgz3SsLWIqS_uA"
.decodeBase64()
Aes128GcmDecoder(rawBody.byteRangeReader()).run {
AdbLog.i("recordSize=$recordSize, keyId.size=${keyId.size}, encryptedContent.size=${encryptedContent.size}")
deriveKeyRfc8188(inputKey.toByteRange())
decode()
}.decodeUTF8().let {
assertEquals(
"text",
"I am the walrus",
it
)
}
}
@Test
fun testMisskey13() {
val receiverPrivateBytes =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgr18ZyNLsRg71vHNxBBGFxeIvV497YUaONPJL1EOKLGChRANCAAQUrMEJuCxzjD-S3xDlcgAvmnbKQZVQzkTCkOcKLSHgGopshtc3ETyrD7m7fhuZAlV1KXJVqHVBtMwrwvZ5xIyw"
.decodeBase64()
val receiverPublicBytes =
"BBSswQm4LHOMP5LfEOVyAC-adspBlVDORMKQ5wotIeAaimyG1zcRPKsPubt-G5kCVXUpclWodUG0zCvC9nnEjLA"
.decodeBase64()
// val senderPublicBytes =
// "BBtZLosjNMsuFAQg0QHYIgDNKG6IC_yxMYW1Tx_Cx20FbqEbAt_KN56zLc48yJxmOJhMkjR5Kf68bEIugNQ-wWU"
// .decodeBase64()
val authSecret = "x6yts9DKC4ZnnHdPHgiwkQ"
.decodeBase64()
// val headerJson = """{
// |"TTL":"2419200",
// |"Content-Encoding":"aes128gcm",
// |"Authorization":"vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL21hc3RvZG9uLW1zZy5qdWdnbGVyLmpwIiwiZXhwIjoxNjc0OTU4MTMyLCJzdWIiOiJodHRwczovL2RyZHIuY2x1YiJ9.I9xIycRJXiWYTZQlsTiS5RyceJNXGbrT8N-rac7Q5UouTWnZUmNbxEknOQigZV71qY3DJ_S7RomXJW3p1KwNmQ, k=BBtZLosjNMsuFAQg0QHYIgDNKG6IC_yxMYW1Tx_Cx20FbqEbAt_KN56zLc48yJxmOJhMkjR5Kf68bEIugNQ-wWU",
// |"<>cameFrom":"fcm"
// |}""".trimMargin()
val rawBody =
"0VUgniuATYrYU481rjSNBAAAEABBBK66EO_qZXsRw0Clzv0LF-SxMe5qRmCgKrZHApIc5ZX_XsXmzBg1rU827Hp9BSdXEWaIaBYAYROjTQfSuHtylP1H0_EOEMNdqswJpEVdVVQrfC_7JAxNAOlXUz4QU3oKl3yhSTlp6M4kFFaBfPrn2SyFI4f5w0wIRATH-Ck0shakAp5hKD7nVwvzEmh3wg8Vug8gPRmGhOPet6DzG5rhLtPng5OJ2eFQUB00duVZyW8TgZ8iPd0GYZkaHw2b16OBN3doLPKiYN_OL-JKVmWutMzivG52DDQ_XqbaFfu5dRLWHkyipq078-nDL6yw9_tNQCdDcVGKx1llxHI6FuPbYgF_RJ8PNZ1Sj-sMRdGtQqcsFH0WDWJu_JBqHSYvBxjEhhDv4qepQTYDl3MYVI-EIbQLIWuCZRo4FRkd594endP6IQ2LH950EE4AMeVHmdxENGELJidrF4eE8reHWf5We0_Jj1m92L8ZJ--z_FMHdOEGlCHwFp1IQPNS-SF6CPZVdrzxjLMo12CELNjh1g9bKxA2ld7OBYcVb3nt6uWBBgiJEqSXEVjzv2JhoHMh3lHLyFngoddfL55aDEVe4gTOwhflPSiKO-8nQd87KLQ_vi4r-SItRzQXhXcI0v7ryFubBwWQrRmRaIaDrMgheEJ50caWgeaDoTXtgkorG63q6KIfkpBlyDyq3YaKpXHJ7rmr-1BYP0Z8PS2O5bqRFyLgtLXIn5a3da_XlTnjdHJH7_N0c8WxVRow5Z5Qzlj790FuGrMQQ8c6SrAC7lTmt6_wq-Ej3SH53jN0HumZ7v4pmkqCR5o7SJYHAgMx9KUeW-Rwr9k3XKVS9MMP9wPhke0Iy51LOfDZ_U6tHVQ_QEkEb1lk1eNSNo0ZSna1vhXh5WB8Pnm3Skxa1k4qvQFKjdNFOZZ57lXqG42oEmplZirhiBgvf40sWmFs_s6_u4tTkD1jagHlrus8l7U5T-34RpzOeqM3z5wqjylBJyubwfEA_7C3WWYOw5U0bduP7QLgFGGzVmu3qAAcQgfHOOehgWXc95j50jXjfV9XD3znXX3WLY0R4qdkWcivQMSxL3PATh1a9fgZJs-SRvH7fW5SrEK3Cj9wAXLfOOcL9gP519eW4sPN-P3xupAfgxXblolBvx1wmdtL51ey1X02cOK_EyFS0pl5D_1vaL6ncq6RDfP2emD-U4jSBEOJbWpzINr9xUnRBLxGYbRZfSSqjdFZGO49JKOW_h9vtZT-NAuPaar0v0Qfz_qF5_-pyo8dQpfNGxZ1ZTzlMwy4v6l18RRAZx2smbgNVmSe2uUCDhcFhO_Y9a9wlbbhYUesenR0g-fbUvVGSdz3V2hoTanQJLwOSbigIFmgMTUnOGPs4uv4ynXleDR6h6zoZ5WwuR8i06AjYq9M5E3nlmxoGKebX_6r1p0Fg0R8QRiz9UmRtxn-G72eLEy5351ZA4xEZrvm4eYoyS7t3BRta0R_4qKFdNONv46ISIj3TU3sUNo2ktIbZE8VjDomxqfgLugbx1Oyvipa9xTFyaJBzhSxxwfdzLlfxdLdrGoh2OseBY56_WR99yh5x6q2UQDo6lyqjzIa8nY_EptfqQmzgCvUJYGmRqXi6Cp5bEyuUvxZ_W-paJoMT5ZW_IjVrSkqYm-oxOkGzKsaAjLmA0e9Qek7uZlLZ9KPl56ON7AHqli0qHi5ZaoosIMVMd6GN0DUfiFHpimGmHw63W0xwArEr0sWf3Wqclk0b3BS7o4oRPMjtjersoFtqxaJ8xJpV4c3UwNjXz9eBQFtJZ_NR1jv_4Kuet3d78RojWEo4OGP9Y7VbPc77yekqm1f1Fxi68w-aZFgL6ClDsy1eAH5HQmYa5KzylTYOt3ZQDXGARiFXR3N0isKPsBBcXZkIzcrKEccfBhYxm2ru3ruNnwMZ4XKzmq5SLRfhxvsCspPaiW3N7oKEbX86ONoAL_i6kcpxAn8iY_XZ36ZyOD_cDkG0OesIdbc4hRcXaLSWmvknXy1KUCbwbe9pMx7HiYn5u_8vgtSELlWQdagc2T3SiD8-gqXTp3RmwwAJSizme2XL9RcjkZOtQnItpNbCwON5afYM85OEKFQkatnt6d8rm2WhoZZ8lU03zD9Kt2Tn9vbkhDYcy_ZE0RHERQLtl5Q4GT81J8RpOEjeJTt2egGFI0PC_tx5MNl_Piy6ows7ly24TsEjNoDJQpnowk_S4OLbN2fhpr1-yhfhbQ-j447FcYkEACjW5t74X4ey8iT88rVMmgefDx7o7NYWW7aKd6vfPpe7EmYiERxCDPYhzQhuKk8vm3FoWM2C6cuuzq1ElUraA70SM9smuwOBJFqgs7ZosIjhb7BdrMiy8lILejHKkTyl-lbhKNzGvO4EIgeHZRn8vzdDmwIINQskT3KgPMpXw"
.decodeBase64()
Aes128GcmDecoder(rawBody.byteRangeReader()).run {
deriveKeyWebPush(
// receiver private key in X509 format
receiverPrivateBytes,
// receiver public key in 65bytes X9.62 uncompressed format
receiverPublicBytes,
// auth secrets created at subscription
authSecret,
)
decode()
}.decodeUTF8().let {
assertEquals(
"text",
"""{"type":"notification","body":{"id":"9ajlf15org","createdAt":"2023-01-28T14:08:50.844Z","type":"reaction","isRead":false,"userId":"8fhziu1e3v","user":{"id":"8fhziu1e3v","name":"tateisu⛏@テスト鯖 :ct080:","username":"tateisu","host":"mastodon2.juggler.jp","avatarUrl":"https://m2j.zzz.ac/accounts/avatars/000/000/001/original/e75c1f2674f44551.png","avatarBlurhash":"yDHUW|V]02t30otQ_H?XoeD,WBt7jbt2tMocN2Rn%Fk9nTE1kB%2e:X3bFRVR:ayWBj@%0fkRR?Ej[E3axt7j]ob9Oay%Fj[xajGIW","isBot":true,"isCat":false,"instance":{"name":"ジャグ鯖(テスト用)","softwareName":"mastodon","softwareVersion":"3.5.3","iconUrl":"https://mastodon2.juggler.jp/packs/media/icons/android-chrome-36x36-d64a2e909b4574fb02214762c513032d.png","faviconUrl":"https://mastodon2.juggler.jp/packs/media/icons/favicon-48x48-28b6709639acd18287a854f974311336.png","themeColor":"#6364ff"},"emojis":{"ctf00":"https://m2j.zzz.ac/custom_emojis/images/000/007/937/original/ctf00.png","ct080":"https://m2j.zzz.ac/custom_emojis/images/000/004/225/original/ct080.png","ctff0":"https://m2j.zzz.ac/custom_emojis/images/000/008/177/original/ctff0.png"},"onlineStatus":"unknown"},"note":{"id":"99tzth0zfn","createdAt":"2023-01-10T16:09:58.643Z","userId":"99tcttijnu","text":":thinking_woozy_portal_orange::thinking_garbled:","visibility":"public","localOnly":false,"renoteCount":0,"repliesCount":2,"reactions":{"👍":1,":revbunhdthinking@.:":1,":ainers_misskeyio@misskey.io:":1},"reactionEmojis":{"ainers_misskeyio@misskey.io":"https://s3.arkjp.net/misskey/ea176d9b-a92a-4ec2-b11c-defdac551156.apng"},"fileIds":[],"files":[],"replyId":null,"renoteId":null,"myReaction":":revbunhdthinking@.:"},"reaction":"👍"},"userId":"99tcttijnu","dateTime":1674914932947}""",
it,
)
}
}
}

View File

@ -1,37 +1,36 @@
buildscript {
ext.jvm_target = "1.8"
ext.min_sdk_version = 26
ext.target_sdk_version = 33
ext.compile_sdk_version = 33
ext.build_tools_version = "33.0.1"
ext.desugar_lib_bersion = "1.2.0"
ext.startup_version = "1.1.1"
ext.roomVersion = "2.5.0"
ext.workVersion = "2.7.1"
ext.conscryptVersion = "2.5.2"
ext.androidxTestVersion = "1.5.0"
ext.ankoVersion = "0.10.8"
ext.appcompatVersion = "1.6.0"
ext.archVersion = "2.1.0"
ext.composeVersion = "1.0.5"
ext.conscryptVersion = "2.5.2"
ext.desugarLibVersion = "1.2.0"
ext.detektVersion = "1.22.0"
ext.glideVersion = "4.14.2"
ext.appcompat_version = "1.6.0"
ext.lifecycle_version = "2.5.1"
ext.arch_version = "2.1.0"
ext.junitVersion = "4.13.2"
ext.kotlinJvmTarget = "1.8"
ext.koinVersion = "3.1.3"
ext.kotlinVersion = "1.8.0"
ext.kotlinxCoroutinesVersion = "1.6.4"
ext.lifecycleVersion = "2.5.1"
ext.okhttpVersion = "4.10.0"
ext.kotlin_version = "1.7.21"
ext.kotlinx_coroutines_version = "1.6.4"
ext.anko_version = "0.10.8"
ext.junit_version = "4.13.2"
ext.detekt_version = "1.22.0"
ext.compose_version = "1.0.5"
ext.koin_version = "3.1.3"
ext.androidx_test_version = "1.5.0"
ext.roomVersion = "2.5.0"
ext.stBuildToolsVersion = "33.0.1"
ext.stCompileSdkVersion = 33
ext.stMinSdkVersion = 26
ext.stTargetSdkVersion = 33
ext.startupVersion = "1.1.1"
ext.workVersion = "2.7.1"
ext.coreKtxVersion = "1.9.0"
ext.testKtxVersion = "1.5.0"
ext.materialVersion = "1.8.0"
ext.androidxTestExtJunitVersion = "1.1.5"
ext.androidxTestEspressoCoreVersion = "3.5.1"
ext.androidxAnnotationVersion = "1.5.0"
ext.preferenceVersion = "1.2.0"
repositories {
google()
@ -44,12 +43,13 @@ buildscript {
// room google-services
classpath "com.google.gms:google-services:4.3.15"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
//noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
classpath "com.github.bjoernq:unmockplugin:0.7.6"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion"
}
}

View File

@ -2,12 +2,12 @@ apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
compileSdkVersion stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
defaultConfig {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
targetSdkVersion stTargetSdkVersion
minSdkVersion stMinSdkVersion
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@ -21,7 +21,7 @@ android {
}
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
freeCompilerArgs += [
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
@ -37,6 +37,6 @@ android {
}
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
implementation project(":base")
}

View File

@ -2,8 +2,8 @@ apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
compileSdkVersion stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -11,8 +11,8 @@ android {
}
defaultConfig {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
targetSdkVersion stTargetSdkVersion
minSdkVersion stMinSdkVersion
vectorDrawables.useSupportLibrary = true
}
@ -24,7 +24,7 @@ android {
}
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
}
namespace 'jp.juggler.emoji'
}

View File

@ -5,10 +5,11 @@ plugins {
android {
namespace "jp.juggler.icon_material_symbols"
compileSdk compile_sdk_version
compileSdk stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
defaultConfig {
minSdk min_sdk_version
targetSdk target_sdk_version
minSdk stMinSdkVersion
targetSdk stTargetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
@ -24,6 +25,6 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = kotlinJvmTarget
}
}

View File

@ -2,8 +2,8 @@ apply plugin: "com.android.application"
apply plugin: "kotlin-android"
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
compileSdkVersion stCompileSdkVersion
buildToolsVersion stBuildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -11,8 +11,8 @@ android {
}
defaultConfig {
targetSdkVersion target_sdk_version
minSdkVersion min_sdk_version
targetSdkVersion stTargetSdkVersion
minSdkVersion stMinSdkVersion
applicationId "jp.juggler.apng.sample"
versionCode 1
@ -36,7 +36,7 @@ android {
kotlinOptions {
jvmTarget = jvm_target
jvmTarget = kotlinJvmTarget
freeCompilerArgs += [
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
@ -49,7 +49,7 @@ android {
}
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
implementation project(":base")
implementation project(":apng_android")
}

View File

@ -1,3 +1,4 @@
include ':app', ':sample_apng', ':apng_android', ':apng', ':colorpicker', ':emoji'
include ':icon_material_symbols'
include ':base'
include ':anko'