317 lines
12 KiB
Kotlin
317 lines
12 KiB
Kotlin
/*
|
|
* 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
|
|
}
|
|
}
|