init
This commit is contained in:
commit
2c88732787
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
gradle/wrapper/
|
||||
36
README.en.md
Normal file
36
README.en.md
Normal file
@ -0,0 +1,36 @@
|
||||
# 谷歌输入法项目
|
||||
|
||||
#### Description
|
||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# 谷歌输入法项目
|
||||
|
||||
#### 介绍
|
||||
{**以下是 Gitee 平台说明,您可以替换此简介**
|
||||
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
|
||||
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
|
||||
|
||||
#### 安装教程
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 使用说明
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
|
||||
|
||||
#### 特技
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
151
app/build.gradle
Normal file
151
app/build.gradle
Normal file
@ -0,0 +1,151 @@
|
||||
plugins {
|
||||
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.gms.google-services'
|
||||
id 'com.google.firebase.crashlytics'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
static def releaseTime() {
|
||||
return new Date().format("MM.dd.HH.mm", TimeZone.getTimeZone("GMT+8"))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.colorful.keyboard.theme'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.colorful.keyboard.theme"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 3
|
||||
versionName "1.0.3"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled= true
|
||||
setProperty("archivesBaseName", "ColorfulKeyboard_V" + defaultConfig.versionName + "(${defaultConfig.versionCode})"+"-"+releaseTime())
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
implementation"androidx.recyclerview:recyclerview:1.3.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "de.hdodenhof:circleimageview:3.1.0"
|
||||
implementation"androidx.lifecycle:lifecycle-process:2.6.1"
|
||||
implementation "com.android.support:multidex:1.0.3"
|
||||
implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.2"
|
||||
implementation ("com.github.bumptech.glide:glide:4.11.0")
|
||||
annotationProcessor ("com.github.bumptech.glide:compiler:4.11.0")
|
||||
implementation ("com.android.support:multidex:1.0.3")
|
||||
implementation ("com.squareup.retrofit2:retrofit:2.6.1")
|
||||
implementation ("com.squareup.retrofit2:converter-gson:2.5.0")
|
||||
implementation ("com.squareup.retrofit2:adapter-rxjava:2.1.0")
|
||||
implementation ("io.reactivex.rxjava2:rxandroid:2.1.1")
|
||||
implementation ("io.reactivex:rxjava:1.2.7")
|
||||
implementation ("io.reactivex:rxjava:1.2.7")
|
||||
implementation ("io.reactivex:rxandroid:1.2.0")
|
||||
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
//noinspection KaptUsageInsteadOfKsp
|
||||
kapt("androidx.room:room-compiler:2.6.1")
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
|
||||
|
||||
implementation ("com.squareup.okhttp3:okhttp:4.9.0")
|
||||
implementation ("com.squareup.okhttp3:logging-interceptor:3.12.2")
|
||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||
//7z
|
||||
implementation 'com.github.omicronapps:7-Zip-JBinding-4Android:Release-16.02-2.02'
|
||||
//沉浸式
|
||||
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
|
||||
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2'
|
||||
// firebase
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.1.2"))// Import the Firebase BoM
|
||||
implementation("com.google.firebase:firebase-analytics")
|
||||
implementation("com.google.firebase:firebase-crashlytics")
|
||||
implementation("com.google.firebase:firebase-config")
|
||||
implementation("com.google.firebase:firebase-analytics-ktx")
|
||||
implementation("com.google.firebase:firebase-crashlytics-ktx")
|
||||
|
||||
|
||||
|
||||
//Anythink (Necessary)
|
||||
implementation("com.anythink.sdk:core-tpn:6.3.68")
|
||||
implementation("com.anythink.sdk:nativead-tpn:6.3.68")
|
||||
implementation("com.anythink.sdk:banner-tpn:6.3.68")
|
||||
implementation("com.anythink.sdk:interstitial-tpn:6.3.68")
|
||||
implementation("com.anythink.sdk:rewardedvideo-tpn:6.3.68")
|
||||
implementation("com.anythink.sdk:splash-tpn:6.3.68")
|
||||
|
||||
//Androidx (Necessary)
|
||||
implementation("androidx.appcompat:appcompat:1.1.0")
|
||||
implementation("androidx.browser:browser:1.4.0")
|
||||
|
||||
//Vungle
|
||||
implementation("com.anythink.sdk:adapter-tpn-vungle:6.3.68")
|
||||
implementation("com.vungle:vungle-ads:7.3.2")
|
||||
implementation("com.google.android.gms:play-services-basement:18.1.0")
|
||||
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||
|
||||
//UnityAds
|
||||
implementation("com.anythink.sdk:adapter-tpn-unityads:6.3.68")
|
||||
implementation("com.unity3d.ads:unity-ads:4.9.3")
|
||||
|
||||
//Ironsource
|
||||
implementation("com.anythink.sdk:adapter-tpn-ironsource:6.3.68")
|
||||
implementation("com.ironsource.sdk:mediationsdk:8.1.0")
|
||||
implementation("com.google.android.gms:play-services-appset:16.0.2")
|
||||
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||
implementation("com.google.android.gms:play-services-basement:18.1.0")
|
||||
|
||||
//Bigo
|
||||
implementation("com.anythink.sdk:adapter-tpn-bigo:6.3.68")
|
||||
implementation("com.bigossp:bigo-ads:4.7.4")
|
||||
|
||||
//Pangle
|
||||
implementation("com.anythink.sdk:adapter-tpn-pangle-nonchina:6.3.68.1")
|
||||
implementation("com.pangle.global:ads-sdk:6.0.0.3")
|
||||
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||
|
||||
//Mintegral
|
||||
implementation("com.anythink.sdk:adapter-tpn-mintegral-nonchina:6.3.68")
|
||||
implementation("com.mbridge.msdk.oversea:reward:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:newinterstitial:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:mbnative:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:mbnativeadvanced:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:mbsplash:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:mbbanner:16.7.51")
|
||||
implementation("com.mbridge.msdk.oversea:mbbid:16.7.51")
|
||||
implementation("androidx.recyclerview:recyclerview:1.1.0")
|
||||
}
|
||||
29
app/google-services.json
Normal file
29
app/google-services.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "987938744902",
|
||||
"project_id": "colorful-keyboard-de3d4",
|
||||
"storage_bucket": "colorful-keyboard-de3d4.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:987938744902:android:d05e8cf3ac15ff540bb847",
|
||||
"android_client_info": {
|
||||
"package_name": "com.colorful.keyboard.theme"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAS_P0_s948qN8GMPoXnWeWvlD2G_yChCQ"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
9
app/key.properties
Normal file
9
app/key.properties
Normal file
@ -0,0 +1,9 @@
|
||||
# ?????
|
||||
storePassword=theme123
|
||||
# key????
|
||||
keyPassword=theme1
|
||||
# key??
|
||||
keyAlias=theme
|
||||
# key??
|
||||
storeFile=..\\themes.jks
|
||||
|
||||
BIN
app/private_key.pepk
Normal file
BIN
app/private_key.pepk
Normal file
Binary file not shown.
137
app/proguard-rules.pro
vendored
Normal file
137
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
# 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
|
||||
|
||||
|
||||
#---------------------------------TopOn 聚合
|
||||
# Vungle
|
||||
-dontwarn com.vungle.ads.**
|
||||
-keepclassmembers class com.vungle.ads.** {
|
||||
*;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# Google
|
||||
-keep class com.google.android.gms.** { *; }
|
||||
-dontwarn com.google.android.gms.**
|
||||
|
||||
|
||||
|
||||
|
||||
# START OkHttp + Okio
|
||||
# JSR 305 annotations are for embedding nullability information.
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
|
||||
|
||||
|
||||
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
|
||||
|
||||
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
|
||||
-dontwarn okhttp3.internal.platform.**
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.openjsse.**
|
||||
|
||||
|
||||
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
|
||||
|
||||
# END OkHttp + Okio
|
||||
|
||||
|
||||
# START Protobuf
|
||||
-dontwarn com.google.protobuf.**
|
||||
-keepclassmembers class com.google.protobuf.** {
|
||||
*;
|
||||
}
|
||||
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
|
||||
|
||||
|
||||
# END Protobuf
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep class com.mbridge.** {*; }
|
||||
-keep interface com.mbridge.** {*; }
|
||||
-keep class android.support.v4.** { *; }
|
||||
-dontwarn com.mbridge.**
|
||||
-keep class **.R$* { public static final int mbridge*; }
|
||||
-keep public class com.mbridge.* extends androidx.** { *; }
|
||||
-keep public class androidx.viewpager.widget.PagerAdapter{ *; }
|
||||
-keep public class androidx.viewpager.widget.ViewPager.OnPageChangeListener{ *; }
|
||||
-keep interface androidx.annotation.IntDef{ *; }
|
||||
-keep interface androidx.annotation.Nullable{ *; }
|
||||
-keep interface androidx.annotation.CheckResult{ *; }
|
||||
-keep interface androidx.annotation.NonNull{ *; }
|
||||
-keep public class androidx.fragment.app.Fragment{ *; }
|
||||
-keep public class androidx.core.content.FileProvider{ *; }
|
||||
-keep public class androidx.core.app.NotificationCompat{ *; }
|
||||
-keep public class androidx.appcompat.widget.AppCompatImageView { *; }
|
||||
-keep public class androidx.recyclerview.*{ *; }
|
||||
|
||||
#---------------------------------TopOn 聚合
|
||||
|
||||
-keep class org.libpag.** {*;}
|
||||
-keep class androidx.exifinterface.** {*;}
|
||||
-keep class com.omicronapplications.** { *; }
|
||||
-keep class net.sf.sevenzipjbinding.** { *; }
|
||||
|
||||
|
||||
# Retrofit
|
||||
-dontnote retrofit2.Platform
|
||||
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
-keepattributes Signature
|
||||
-keepattributes Exceptions
|
||||
|
||||
# okhttp
|
||||
-dontwarn okio.**
|
||||
|
||||
# 保留RxJava相关的类和方法
|
||||
-keep class rx.** { *; }
|
||||
-keepclassmembers class rx.** { *; }
|
||||
|
||||
# 保留Retrofit相关的类和方法
|
||||
-keep class retrofit2.** { *; }
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface retrofit2.** { *; }
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit2.http.* <methods>;
|
||||
}
|
||||
|
||||
# 保留RxJava的CallAdapter
|
||||
-keep class retrofit2.adapter.rxjava2.** { *; }
|
||||
|
||||
# 保留返回Observable的Retrofit接口方法
|
||||
-keepclassmembers,allowobfuscation interface * {
|
||||
@retrofit2.http.* <methods>;
|
||||
rx.Observable *;
|
||||
}
|
||||
|
||||
-keep class com.colorful.keyboard.theme.net.** { *; }
|
||||
-keep class com.colorful.keyboard.theme.model.** { *; }
|
||||
@ -0,0 +1,26 @@
|
||||
package com.colorful.keyboard.theme;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.colorful.keyboard.theme", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
105
app/src/main/AndroidManifest.xml
Normal file
105
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="DiscouragedApi,LockedOrientationActivity">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupOnly="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/english_ime_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:resizeableActivity="true"
|
||||
android:maxAspectRatio="2.4"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/LaunchTheme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="2.4" />
|
||||
|
||||
<!--适配华为(huawei)刘海屏-->
|
||||
<meta-data
|
||||
android:name="android.notch_support"
|
||||
android:value="true"/>
|
||||
<!--适配小米(xiaomi)刘海屏-->
|
||||
<meta-data
|
||||
android:name="notch.config"
|
||||
android:value="portrait|landscape" />
|
||||
|
||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||
|
||||
<activity
|
||||
android:name=".LaunchActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".ThemeSettingsActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name="ColorfulDetailsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".PreviewActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".SettingActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".LocalPrivacyAct"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity android:name=".DownloadsActivity"
|
||||
android:screenOrientation="portrait"/>
|
||||
|
||||
<service
|
||||
android:name=".KeyboardService"
|
||||
android:exported="true"
|
||||
android:label="@string/english_ime_name"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||
<meta-data
|
||||
android:name="android.view.im"
|
||||
android:resource="@xml/key_method" />
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".latin.SystemBroadcastReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.colorful.keyboard.theme"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
tools:replace="android:authorities">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_path" />
|
||||
</provider>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
111
app/src/main/assets/PrivacyPolicy.html
Normal file
111
app/src/main/assets/PrivacyPolicy.html
Normal file
@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- saved from url=(0041)https://jeasona.bitbucket.io/privacy.html -->
|
||||
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Privacy Policy</title>
|
||||
<style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding:1em; } </style>
|
||||
</head>
|
||||
<body>
|
||||
<strong>Privacy Policy</strong>
|
||||
<p>
|
||||
|
||||
</p> <p>
|
||||
This page is used to inform visitors regarding our
|
||||
policies with the collection, use, and disclosure of Personal
|
||||
Information if anyone decided to use our Service.
|
||||
</p> <p>
|
||||
If you choose to use our Service, then you agree to
|
||||
the collection and use of information in relation to this
|
||||
policy. The Personal Information that We collect is
|
||||
used for providing and improving the Service. We will not use or share your information with
|
||||
anyone except as described in this Privacy Policy.
|
||||
</p> <p>
|
||||
The terms used in this Privacy Policy have the same meanings
|
||||
as in our Terms and Conditions, which are accessible at
|
||||
unless otherwise defined in this Privacy Policy.
|
||||
</p> <p><strong>Information Collection and Use</strong></p> <p>
|
||||
For a better experience, while using our Service, We
|
||||
may require you to provide us with certain personally
|
||||
identifiable information add whatever else you collect here, e.g. users name, address, location, pictures The information that
|
||||
We request will be retained on your device and is not collected in any way retained by us and used as described in this privacy policy.
|
||||
</p> <div><p>
|
||||
The app does use third-party services that may collect
|
||||
information used to identify you.
|
||||
</p> <p>
|
||||
Link to the privacy policy of third-party service providers used
|
||||
by the app
|
||||
</p> <ul><li><a href="https://www.google.com/policies/privacy/" target="_blank" rel="noopener noreferrer">Google Play Services</a></li><li><a href="https://support.google.com/admob/answer/6128543?hl=en" target="_blank" rel="noopener noreferrer">AdMob</a></li><li><a href="https://firebase.google.com/support/privacy" target="_blank" rel="noopener noreferrer">Google Analytics for Firebase</a></li><li><a href="https://firebase.google.com/support/privacy/" target="_blank" rel="noopener noreferrer">Firebase Crashlytics</a></li><!----><!----><!----><!----><li><a href="https://privacy.oath.com/" target="_blank" rel="noopener noreferrer">Flurry Analytics</a></li><!----><!----><li><a href="https://unity3d.com/legal/privacy-policy" target="_blank" rel="noopener noreferrer">Unity</a></li><!----><!----><!----><!----><!----><li><a href="https://www.applovin.com/privacy/" target="_blank" rel="noopener noreferrer">AppLovin</a></li><li><a href="https://vungle.com/privacy/" target="_blank" rel="noopener noreferrer">Vungle</a></li><!----><!----><!----><!----><!----><!----><!----><!----><!----><!----></ul></div> <p><strong>Log Data</strong></p> <p>
|
||||
We want to inform you that whenever you
|
||||
use our Service, in a case of an error in the app
|
||||
We collect data and information (through third-party
|
||||
products) on your phone called Log Data. This Log Data may
|
||||
include information such as your device Internet Protocol
|
||||
(“IP”) address, device name, operating system version, the
|
||||
configuration of the app when utilizing our Service,
|
||||
the time and date of your use of the Service, and other
|
||||
statistics.
|
||||
</p> <p><strong>Cookies</strong></p> <p>
|
||||
Cookies are files with a small amount of data that are
|
||||
commonly used as anonymous unique identifiers. These are sent
|
||||
to your browser from the websites that you visit and are
|
||||
stored on your device's internal memory.
|
||||
|
||||
</p> <p>
|
||||
This Service does not use these “cookies” explicitly. However,
|
||||
the app may use third-party code and libraries that use
|
||||
“cookies” to collect information and improve their services.
|
||||
You have the option to either accept or refuse these cookies
|
||||
and know when a cookie is being sent to your device. If you
|
||||
choose to refuse our cookies, you may not be able to use some
|
||||
portions of this Service.
|
||||
</p> <p><strong>Service Providers</strong></p> <p>
|
||||
We may employ third-party companies and
|
||||
individuals due to the following reasons:
|
||||
</p> <ul><li>To facilitate our Service;</li> <li>To provide the Service on our behalf;</li> <li>To perform Service-related services; or</li> <li>To assist us in analyzing how our Service is used.</li></ul> <p>
|
||||
We want to inform users of this Service
|
||||
that these third parties have access to their Personal
|
||||
Information. The reason is to perform the tasks assigned to
|
||||
them on our behalf. However, they are obligated not to
|
||||
disclose or use the information for any other purpose.
|
||||
</p> <p><strong>Security</strong></p> <p>
|
||||
We value your trust in providing us your
|
||||
Personal Information, thus we are striving to use commercially
|
||||
acceptable means of protecting it. But remember that no method
|
||||
of transmission over the internet, or method of electronic
|
||||
storage is 100% secure and reliable, and We cannot
|
||||
guarantee its absolute security.
|
||||
</p> <p><strong>Links to Other Sites</strong></p> <p>
|
||||
This Service may contain links to other sites. If you click on
|
||||
a third-party link, you will be directed to that site. Note
|
||||
that these external sites are not operated by us.
|
||||
Therefore, We strongly advise you to review the
|
||||
Privacy Policy of these websites. We have
|
||||
no control over and assume no responsibility for the content,
|
||||
privacy policies, or practices of any third-party sites or
|
||||
services.
|
||||
</p> <p><strong>Children’s Privacy</strong></p> <div><p>
|
||||
These Services do not address anyone under the age of 13.
|
||||
We do not knowingly collect personally
|
||||
identifiable information from children under 13 years of age. In the case
|
||||
We discover that a child under 13 has provided
|
||||
us with personal information, We immediately
|
||||
delete this from our servers. If you are a parent or guardian
|
||||
and you are aware that your child has provided us with
|
||||
personal information, please contact us so that
|
||||
We will be able to do the necessary actions.
|
||||
</p></div> <!----> <p><strong>Changes to This Privacy Policy</strong></p> <p>
|
||||
We may update our Privacy Policy from
|
||||
time to time. Thus, you are advised to review this page
|
||||
periodically for any changes. We will
|
||||
notify you of any changes by posting the new Privacy Policy on
|
||||
this page.
|
||||
</p> <p>This policy is effective as of 2024-1-8</p> <p><strong>Contact Us</strong></p> <p>
|
||||
If you have any questions or suggestions about our
|
||||
Privacy Policy, do not hesitate to contact us at JaysonNazaireMtfWaOIe@gmail.com.
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
31
app/src/main/java/com/colorful/keyboard/theme/App.kt
Normal file
31
app/src/main/java/com/colorful/keyboard/theme/App.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.app.Application
|
||||
import com.anythink.core.api.ATSDK
|
||||
import com.colorful.keyboard.theme.firebase.RemoteConfig
|
||||
import com.colorful.keyboard.theme.myutil.Utils
|
||||
import com.colorful.keyboard.theme.room.DownloadManager
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class App : Application() {
|
||||
companion object {
|
||||
lateinit var app: App
|
||||
private set
|
||||
|
||||
lateinit var downloadManager: DownloadManager
|
||||
private set
|
||||
}
|
||||
|
||||
var lastAdDisplayTime: AtomicLong = AtomicLong(0L)//上次广告展示的时间
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
app = this
|
||||
Utils.init(this)
|
||||
downloadManager = DownloadManager.getInstance(this)
|
||||
RemoteConfig.instance.init(this)
|
||||
ATSDK.init(this, "h66b1cef2a9a51", "a90d2376d3e1dd2bd9bb5cb4f8296085f")
|
||||
// ATSDK.integrationChecking(this)
|
||||
// ATSDK.setNetworkLogDebug(true)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.colorful.keyboard.theme.ad.AdsInsUtil
|
||||
import com.colorful.keyboard.theme.bean.DetailsBean
|
||||
import com.colorful.keyboard.theme.bean.DownloadBean
|
||||
import com.colorful.keyboard.theme.databinding.ColorfulDetailsActivityBinding
|
||||
import com.colorful.keyboard.theme.myutil.AppSharedPreferences
|
||||
import com.colorful.keyboard.theme.myutil.NetworkCallback
|
||||
import com.colorful.keyboard.theme.myutil.NetworkUtil
|
||||
import com.colorful.keyboard.theme.myutil.OnDownloadListener
|
||||
import com.colorful.keyboard.theme.myutil.ResourceDownloadUtil
|
||||
import com.colorful.keyboard.theme.myutil.fileIsDownload
|
||||
import com.colorful.keyboard.theme.myutil.loadRoundedImage
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class ColorfulDetailsActivity : AppCompatActivity(), OnDownloadListener {
|
||||
private var key: String? = null
|
||||
private var title: String? = null
|
||||
private lateinit var binding: ColorfulDetailsActivityBinding
|
||||
private var detailsBean: DetailsBean? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ColorfulDetailsActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
title = intent.getStringExtra("title")
|
||||
key = intent.getStringExtra("key")
|
||||
if (key.isNullOrEmpty()) {
|
||||
finish()
|
||||
}
|
||||
initBar()
|
||||
initView()
|
||||
initData()
|
||||
AdsInsUtil.loadAllAdIsNotCached(this)
|
||||
}
|
||||
|
||||
private fun initBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(true)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
AdsInsUtil.showAdRandomModeAndTiming(this)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.titleTv.text = title
|
||||
binding.backBtn.setOnClickListener { onBackPressed() }
|
||||
|
||||
binding.downBtn.setOnClickListener {
|
||||
detailsBean?.let {
|
||||
updateDownloadBtn(it)
|
||||
val resourceHandler = ResourceDownloadUtil(this) // 传入当前上下文
|
||||
resourceHandler.setOnDownloadListener(this)
|
||||
val imageUrl = it.themeContent.androidRawZipUrl
|
||||
val b = fileIsDownload(this, imageUrl)
|
||||
if (b) {
|
||||
AppSharedPreferences(this).setCurrentlyThemeUrl(imageUrl)
|
||||
|
||||
val intent = Intent(this, PreviewActivity::class.java)
|
||||
intent.putExtra(PreviewActivity.KEY_PREVIEW_URL, imageUrl)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
} else {
|
||||
binding.loadingBar.visibility = View.VISIBLE
|
||||
binding.downIcon.visibility = View.GONE
|
||||
resourceHandler.downloadAndExtractResources(imageUrl)//文件不存在则下载
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
loadingPlay()
|
||||
NetworkUtil().getResourceRequest(key!!, object : NetworkCallback<DetailsBean> {
|
||||
override fun onSuccess(data: DetailsBean) {
|
||||
detailsBean = data
|
||||
updateUi(data)
|
||||
}
|
||||
|
||||
override fun onFailure(errorMessage: String) {
|
||||
runOnUiThread {
|
||||
if (!isFinishing && !isDestroyed) {
|
||||
loadingClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateUi(data: DetailsBean) {
|
||||
runOnUiThread {
|
||||
if (!isFinishing && !isDestroyed) {
|
||||
loadingClose()
|
||||
|
||||
updateDownloadBtn(data)
|
||||
|
||||
var url = data.themeContent.imgGif
|
||||
if (url.isEmpty()) {
|
||||
url = data.themeContent.img
|
||||
}
|
||||
loadRoundedImage(this, url, binding.imageView, 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadingPlay() {
|
||||
binding.loadingLayout.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun loadingClose() {
|
||||
binding.loadingLayout.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun updateDownloadBtn(data: DetailsBean) {
|
||||
if (fileIsDownload(this, data.themeContent.androidRawZipUrl)) {
|
||||
if (AppSharedPreferences(this).getCurrentlyThemeUrl() == data.themeContent.androidRawZipUrl) {
|
||||
binding.downTv.text = getString(R.string.applied)
|
||||
binding.downBtn.isClickable = false
|
||||
binding.downBtn.isFocusable = false
|
||||
binding.downIcon.visibility = View.GONE
|
||||
} else {
|
||||
binding.downTv.text = getString(R.string.apply_it)
|
||||
binding.downBtn.isClickable = true
|
||||
binding.downBtn.isFocusable = true
|
||||
binding.downIcon.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
binding.downTv.text = getString(R.string.download)
|
||||
binding.downBtn.isClickable = true
|
||||
binding.downBtn.isFocusable = true
|
||||
binding.downIcon.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDownloadComplete(isDownloaded: Boolean) {
|
||||
|
||||
binding.loadingBar.visibility = View.GONE
|
||||
binding.downIcon.visibility = View.GONE
|
||||
|
||||
updateUi(detailsBean!!)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
var url = detailsBean?.themeContent?.imgGif
|
||||
if (url.isNullOrEmpty()) {
|
||||
url = detailsBean?.themeContent?.img
|
||||
}
|
||||
val bean = DownloadBean(
|
||||
title = detailsBean?.title ?: "", key = detailsBean?.key ?: "", imgUrl = url ?: ""
|
||||
)
|
||||
App.downloadManager.insertDownloadBean(bean)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.colorful.keyboard.theme.ad.AdsInsUtil
|
||||
import com.colorful.keyboard.theme.config_adpter.DownloadAdapter
|
||||
import com.colorful.keyboard.theme.databinding.ColorfulDownloadActivityBinding
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class DownloadsActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ColorfulDownloadActivityBinding
|
||||
private var downloadAdapter = DownloadAdapter()
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ColorfulDownloadActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
AdsInsUtil.showAdRandomModeAndTiming(this)
|
||||
initBar()
|
||||
initView()
|
||||
initData()
|
||||
|
||||
AdsInsUtil.loadAllAdIsNotCached(this)
|
||||
}
|
||||
|
||||
private fun initBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(true)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.backBtn.setOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
binding.loadingLayout.visibility = View.VISIBLE
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val downloads = App.downloadManager.getAllDownloadBeans()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.loadingLayout.visibility = View.GONE
|
||||
if (downloads.isNotEmpty()) {
|
||||
binding.noDataLayout.visibility = View.GONE
|
||||
downloadAdapter.submitList(downloads)
|
||||
binding.rvPhotoIst.layoutManager = GridLayoutManager(this@DownloadsActivity, 3)
|
||||
binding.rvPhotoIst.adapter = downloadAdapter
|
||||
downloadAdapter.setOnItemClickListener { baseQuickAdapter, _, i ->
|
||||
startActivity(
|
||||
Intent(this@DownloadsActivity, ColorfulDetailsActivity::class.java)
|
||||
.putExtra("title", baseQuickAdapter.getItem(i)!!.title)
|
||||
.putExtra("key", baseQuickAdapter.getItem(i)!!.key)
|
||||
.putExtra("url", baseQuickAdapter.getItem(i)!!.imgUrl)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.noDataLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
359
app/src/main/java/com/colorful/keyboard/theme/KeyboardService.kt
Normal file
359
app/src/main/java/com/colorful/keyboard/theme/KeyboardService.kt
Normal file
@ -0,0 +1,359 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.widget.VideoView
|
||||
import com.colorful.keyboard.theme.myutil.AppSharedPreferences
|
||||
import com.colorful.keyboard.theme.myutil.colorsXmlPullParser
|
||||
import com.colorful.keyboard.theme.myutil.currentlyThemeUFileString
|
||||
import com.colorful.keyboard.theme.myutil.getBitmapDrawable
|
||||
import com.colorful.keyboard.theme.myutil.getPath
|
||||
import com.colorful.keyboard.theme.myutil.getRawVideoPath
|
||||
import com.colorful.keyboard.theme.myutil.getStateDrawable
|
||||
import com.colorful.keyboard.theme.view.JourneyKeyboardView
|
||||
import com.colorful.keyboard.theme.view.Keyboard
|
||||
import com.colorful.keyboard.theme.view.KeyboardView
|
||||
import com.colorful.keyboard.theme.view.JourneyKeyboard
|
||||
import java.io.File
|
||||
|
||||
class KeyboardService : InputMethodService() {
|
||||
|
||||
private lateinit var keyboardView: JourneyKeyboardView
|
||||
private lateinit var currentKeyboard: Keyboard
|
||||
private var isAllCaps = false
|
||||
private var isCap = false
|
||||
private lateinit var rootView: View
|
||||
|
||||
override fun onCreateInputView(): View {
|
||||
rootView = layoutInflater.inflate(R.layout.custom_keyboard, null) // 这里使用了一个容器布局
|
||||
keyboardView = rootView.findViewById(R.id.keyboardView) // 在容器布局中找到 KeyboardView 的引用
|
||||
currentKeyboard = Keyboard(this, R.xml.my_custom_keyboard_layout)
|
||||
keyboardView.keyboard = currentKeyboard
|
||||
keyboardView.setOnKeyboardActionListener(onKeyboardActionListener)
|
||||
|
||||
isCap = keyboardView.isCap()
|
||||
isAllCaps = keyboardView.isAllCaps()
|
||||
|
||||
return rootView // 返回包含 KeyboardView 的容器布局
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
super.onWindowShown()
|
||||
updateConfig()
|
||||
}
|
||||
|
||||
private fun updateConfig() {
|
||||
val currentlyUrl = AppSharedPreferences(this).getCurrentlyThemeUrl()
|
||||
if (currentlyUrl.isNotEmpty()) {
|
||||
val currentlyThemeUFile = currentlyThemeUFileString(this, currentlyUrl)
|
||||
val config = JourneyKeyboardView.Config(this)
|
||||
config.keyBackground = getStateDrawable(this,
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_key_normal_normal.9.png"),
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_key_normal_pressed.9.png")
|
||||
)
|
||||
config.specialKeyBackground = getStateDrawable(this,
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_spacekey_normal_normal.9.png"),
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_spacekey_normal_pressed.9.png")
|
||||
)
|
||||
config.deleteDrawable = getStateDrawable(this,
|
||||
getPath(currentlyThemeUFile, "sym_keyboard_delete_normal.png"),
|
||||
getPath(currentlyThemeUFile, "sym_keyboard_delete_pressed.png")
|
||||
)
|
||||
config.toggleKeyBackground = getStateDrawable(this,
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_key_toggle_normal_on.9.png"),
|
||||
getPath(currentlyThemeUFile, "btn_keyboard_key_toggle_pressed_on.9.png")
|
||||
)
|
||||
|
||||
val colorMap = colorsXmlPullParser(currentlyThemeUFile)
|
||||
colorMap.forEach { (name, value) ->
|
||||
if (name == "key_text_color_normal") {
|
||||
config.keyTextColor = value
|
||||
config.keySpecialTextColor = value
|
||||
}
|
||||
if (name == "key_text_color_functional") {
|
||||
config.toggleKeyTextColor = value
|
||||
}
|
||||
}
|
||||
config.capitalDrawable =
|
||||
getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift_locked.png")
|
||||
config.lowerDrawable = getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift.png")
|
||||
config.capitalLockDrawable =
|
||||
getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift_locked.png")
|
||||
|
||||
val videoView = rootView.findViewById<VideoView>(R.id.videoView)
|
||||
val videoPath = getRawVideoPath(currentlyThemeUFile)
|
||||
if (File(videoPath).exists()) {
|
||||
// 设置视频路径并启动播放
|
||||
videoView.setVideoURI(Uri.parse(videoPath))
|
||||
videoView.setOnPreparedListener { mp ->
|
||||
mp.isLooping = true // 循环播放
|
||||
videoView.start()
|
||||
}
|
||||
videoView.visibility = View.VISIBLE
|
||||
} else {
|
||||
val imagePath =
|
||||
"$currentlyThemeUFile/res/drawable-xxhdpi-v4/keyboard_background.jpg"
|
||||
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||
val background = BitmapDrawable(resources, bitmap)
|
||||
rootView.background = background
|
||||
videoView.visibility = View.GONE
|
||||
}
|
||||
keyboardView.setConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
private val onKeyboardActionListener = object : KeyboardView.OnKeyboardActionListener {
|
||||
override fun onKey(primaryCode: Int, keyCodes: IntArray?) {
|
||||
val ic = currentInputConnection
|
||||
performKey(ic, primaryCode, keyCodes)
|
||||
}
|
||||
|
||||
override fun onPress(primaryCode: Int) {}
|
||||
|
||||
override fun onRelease(primaryCode: Int) {}
|
||||
|
||||
override fun onText(text: CharSequence?) {}
|
||||
|
||||
override fun swipeLeft() {}
|
||||
|
||||
override fun swipeRight() {}
|
||||
|
||||
override fun swipeDown() {}
|
||||
|
||||
override fun swipeUp() {}
|
||||
}
|
||||
|
||||
private var keyboardType = KeyboardType.NORMAL
|
||||
|
||||
private val keyboardNormal by lazy { Keyboard(this, R.xml.my_custom_keyboard_layout) }
|
||||
private val keyboardNormalModeChange by lazy { Keyboard(this, R.xml.keyboard_mode_change) }
|
||||
private val keyboardNormalMore by lazy { Keyboard(this, R.xml.keyboard_more_symbol) }
|
||||
|
||||
/**
|
||||
* 根据 primaryCode去做相应的处理
|
||||
*/
|
||||
private fun performKey(ic: InputConnection, primaryCode: Int, keyCodes: IntArray?) {
|
||||
when (primaryCode) {
|
||||
JourneyKeyboard.KEYCODE_SHIFT -> keyShift()
|
||||
JourneyKeyboard.KEYCODE_MODE_CHANGE -> keyModeChange()
|
||||
JourneyKeyboard.KEYCODE_CANCEL -> keyCancel(primaryCode)
|
||||
JourneyKeyboard.KEYCODE_DONE -> keyDone(primaryCode)
|
||||
JourneyKeyboard.KEYCODE_DELETE -> keyDelete(ic)
|
||||
JourneyKeyboard.KEYCODE_MODE_BACK -> keyBack(false)
|
||||
JourneyKeyboard.KEYCODE_BACK -> keyBack(true)
|
||||
JourneyKeyboard.KEYCODE_MORE -> keyMore()
|
||||
//无效的按键值,打印相关日志
|
||||
else -> commitText(ic, primaryCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun commitText(ic: InputConnection, primaryCode: Int) {
|
||||
val code = primaryCode.toChar().toString()
|
||||
ic.commitText(code, 1)
|
||||
|
||||
if (isCap && !isAllCaps) {//如果当前是大写键盘,并且并且没有锁定,则自动变换成小写键盘
|
||||
isCap = false
|
||||
isAllCaps = false
|
||||
toLowerCaseKey(currentKeyboard)
|
||||
|
||||
keyboardView.run {
|
||||
setCap(isCap)
|
||||
setAllCaps(isAllCaps)
|
||||
keyboard = currentKeyboard
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 触发删除
|
||||
*/
|
||||
private fun keyDelete(ic: InputConnection) {
|
||||
ic.deleteSurroundingText(1, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发Shift,切换大小字母键盘
|
||||
*/
|
||||
private fun keyShift() {
|
||||
//将键盘进行大小写键盘切换
|
||||
if (isAllCaps) {//上次状态为大写锁定时,转换为小写
|
||||
toLowerCaseKey(currentKeyboard)
|
||||
} else {//反之上次状态即为小写时,转换为大写
|
||||
toUpperCaseKey(currentKeyboard)
|
||||
}
|
||||
|
||||
when {
|
||||
isAllCaps -> {//上次状态为锁定时,此次状态将改变为小写,将变量状态改变
|
||||
isAllCaps = false
|
||||
isCap = false
|
||||
}
|
||||
|
||||
isCap -> {//上次状态为非锁定,此次状态改变为锁定
|
||||
isAllCaps = true
|
||||
}
|
||||
|
||||
else -> {//上次状态为小写(默认),此次状态改变为大写
|
||||
isCap = true
|
||||
isAllCaps = false
|
||||
}
|
||||
}
|
||||
|
||||
keyboardView.let {
|
||||
it.setCap(isCap)
|
||||
it.setAllCaps(isAllCaps)
|
||||
it.keyboard = currentKeyboard
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为大写
|
||||
*/
|
||||
private fun toUpperCaseKey(keyboard: Keyboard) {
|
||||
keyboard.run {
|
||||
for (key in keys) {
|
||||
if (key.label?.length == 1) {// 一个字符
|
||||
var c = key.label.toString()[0]
|
||||
if (c.isLowerCase()) { //是小写字母
|
||||
//转换为大写
|
||||
val letter = c.toUpperCase()
|
||||
key.label = letter.toString()
|
||||
key.codes[0] = letter.toInt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为小写
|
||||
*/
|
||||
private fun toLowerCaseKey(keyboard: Keyboard) {
|
||||
keyboard.run {
|
||||
for (key in keys) {
|
||||
if (key.label?.length == 1) {// 一个字符
|
||||
var c = key.label.toString()[0]
|
||||
if (c.isUpperCase()) { //是大写字母
|
||||
//转换为小写
|
||||
val letter = c.toLowerCase()
|
||||
key.label = letter.toString()
|
||||
key.codes[0] = letter.toInt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 模式改变,切换键盘
|
||||
*/
|
||||
private fun keyModeChange() {
|
||||
when (keyboardType) {
|
||||
JourneyKeyboard.KeyboardType.NORMAL -> {
|
||||
keyboardType = KeyboardType.NORMAL_MODE_CHANGE
|
||||
}
|
||||
}
|
||||
|
||||
switchKeyboard()
|
||||
}
|
||||
|
||||
private fun switchKeyboard() {
|
||||
when (keyboardType) {
|
||||
KeyboardType.NORMAL -> {
|
||||
currentKeyboard = keyboardNormal
|
||||
}
|
||||
|
||||
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||
currentKeyboard = keyboardNormalModeChange
|
||||
}
|
||||
|
||||
KeyboardType.NORMAL_MORE -> {
|
||||
currentKeyboard = keyboardNormalMore
|
||||
}
|
||||
}
|
||||
|
||||
keyboardView.run {
|
||||
keyboard = currentKeyboard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消,关闭键盘
|
||||
*/
|
||||
private fun keyCancel(primaryCode: Int) {
|
||||
val ic = currentInputConnection
|
||||
ic?.performEditorAction(EditorInfo.IME_ACTION_DONE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成
|
||||
*/
|
||||
private fun keyDone(primaryCode: Int) {
|
||||
val ic = currentInputConnection
|
||||
ic?.performEditorAction(EditorInfo.IME_ACTION_DONE)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回
|
||||
*/
|
||||
private fun keyBack(isBack: Boolean) {
|
||||
when (keyboardType) {
|
||||
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||
keyboardType = KeyboardType.NORMAL
|
||||
}
|
||||
|
||||
KeyboardType.NORMAL_MORE -> {
|
||||
keyboardType = if (isBack) KeyboardType.NORMAL else KeyboardType.NORMAL_MODE_CHANGE
|
||||
}
|
||||
}
|
||||
|
||||
switchKeyboard()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更多
|
||||
*/
|
||||
private fun keyMore() {
|
||||
when (keyboardType) {
|
||||
KeyboardType.NORMAL -> {
|
||||
keyboardType = KeyboardType.NORMAL_MORE
|
||||
}
|
||||
|
||||
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||
keyboardType = KeyboardType.NORMAL_MORE
|
||||
}
|
||||
}
|
||||
|
||||
switchKeyboard()
|
||||
}
|
||||
|
||||
|
||||
object KeyboardType {
|
||||
/**
|
||||
* 默认键盘 - 字母带符号
|
||||
*/
|
||||
const val NORMAL = 0x00000001
|
||||
|
||||
/**
|
||||
* 默认键盘 - 切换键盘
|
||||
*/
|
||||
internal const val NORMAL_MODE_CHANGE = 0x00000002
|
||||
|
||||
/**
|
||||
* 默认键盘 - 更多
|
||||
*/
|
||||
internal const val NORMAL_MORE = 0x00000003
|
||||
}
|
||||
|
||||
}
|
||||
116
app/src/main/java/com/colorful/keyboard/theme/LaunchActivity.kt
Normal file
116
app/src/main/java/com/colorful/keyboard/theme/LaunchActivity.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.anythink.core.api.ATAdInfo
|
||||
import com.anythink.core.api.ATSDK
|
||||
import com.colorful.keyboard.theme.ad.AdShowFailed
|
||||
import com.colorful.keyboard.theme.ad.AdsInsUtil
|
||||
import com.colorful.keyboard.theme.ad.ShowListener
|
||||
import com.colorful.keyboard.theme.databinding.ActivitySplashBinding
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
|
||||
class LaunchActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val MSG_PROGRESS = 0
|
||||
private const val MSG_LOAD_AD_LOADED = 1
|
||||
private const val MSG_LOAD_AD_FAIL = 2
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivitySplashBinding
|
||||
private val mMaxLoading = 10000L//最大超时
|
||||
private var progress = 0f
|
||||
private val delayMillis = mMaxLoading / 100
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivitySplashBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
loadAd()
|
||||
handler.sendEmptyMessageDelayed(MSG_PROGRESS, delayMillis)
|
||||
immersionBar {
|
||||
fullScreen(true)
|
||||
statusBarDarkFont(true)
|
||||
}
|
||||
}
|
||||
|
||||
private val handler = object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
val progressBar = binding.customProgressBar
|
||||
if (!isFinishing && !isDestroyed) {
|
||||
when (msg.what) {
|
||||
MSG_PROGRESS -> {
|
||||
if (progress >= 100f) {//超时
|
||||
toMainActivity()
|
||||
return
|
||||
}
|
||||
val cacheAd = AdsInsUtil.canShowAd(AdsInsUtil.Placement.TOP_ON_AD_ONE)
|
||||
if (cacheAd) {
|
||||
sendEmptyMessageDelayed(MSG_LOAD_AD_LOADED, delayMillis)
|
||||
removeMessages(MSG_PROGRESS)
|
||||
return
|
||||
}
|
||||
progress += 1
|
||||
progressBar.setProgress(progress)
|
||||
sendEmptyMessageDelayed(MSG_PROGRESS, delayMillis)
|
||||
}
|
||||
|
||||
MSG_LOAD_AD_LOADED -> {
|
||||
if (progress >= 100f) {
|
||||
showAd()
|
||||
removeMessages(MSG_LOAD_AD_LOADED)
|
||||
return
|
||||
}
|
||||
progress += 1
|
||||
progressBar.setProgress(progress)
|
||||
sendEmptyMessageDelayed(MSG_LOAD_AD_LOADED, delayMillis)
|
||||
}
|
||||
|
||||
MSG_LOAD_AD_FAIL -> {
|
||||
sendEmptyMessageDelayed(MSG_LOAD_AD_LOADED, delayMillis)
|
||||
removeMessages(MSG_LOAD_AD_FAIL)
|
||||
removeMessages(MSG_PROGRESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun toMainActivity() {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun loadAd() {
|
||||
AdsInsUtil.loadAllAdIsNotCached(this)
|
||||
}
|
||||
|
||||
private fun showAd() {
|
||||
AdsInsUtil.showAd(this, AdsInsUtil.Placement.TOP_ON_AD_ONE, object : ShowListener {
|
||||
override fun onAdClosed() {
|
||||
toMainActivity()
|
||||
}
|
||||
|
||||
override fun onAdShowFailed(error: AdShowFailed?) {
|
||||
toMainActivity()
|
||||
}
|
||||
|
||||
override fun onAdShown(ad: ATAdInfo?) {
|
||||
AdsInsUtil.loadAllAdIsNotCached(this@LaunchActivity)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
override fun onBackPressed() {
|
||||
|
||||
}
|
||||
}
|
||||
121
app/src/main/java/com/colorful/keyboard/theme/LocalPrivacyAct.kt
Normal file
121
app/src/main/java/com/colorful/keyboard/theme/LocalPrivacyAct.kt
Normal file
@ -0,0 +1,121 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.colorful.keyboard.theme.databinding.ActivityWebPrivacyBinding
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
|
||||
class LocalPrivacyAct : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityWebPrivacyBinding
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityWebPrivacyBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
initBar()
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(true)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.backBtn.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
initWebView()
|
||||
loadUrl()
|
||||
}
|
||||
|
||||
private fun loadUrl() {
|
||||
binding.webView.loadUrl("file:///android_asset/PrivacyPolicy.html")
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun initWebView() {
|
||||
// 设置背景色
|
||||
binding.webView.setBackgroundColor(0);
|
||||
// 设置填充透明度
|
||||
binding.webView.background.alpha = 0
|
||||
|
||||
binding.webView.settings.javaScriptEnabled = true
|
||||
binding.webView.settings.domStorageEnabled = true
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
binding.webView.settings.mixedContentMode = 0
|
||||
}
|
||||
|
||||
binding.webView.settings.apply {
|
||||
javaScriptEnabled = true
|
||||
loadWithOverviewMode = true
|
||||
useWideViewPort = true
|
||||
setSupportZoom(true)
|
||||
loadWithOverviewMode = true
|
||||
javaScriptCanOpenWindowsAutomatically = true
|
||||
}
|
||||
binding.webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onReceivedTitle(view: WebView?, title: String?) {
|
||||
}
|
||||
}
|
||||
binding.webView.webViewClient = object : WebViewClient() {
|
||||
private var mLoadError = false
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView, request: WebResourceRequest?
|
||||
): Boolean {
|
||||
val url = request?.url?.toString()
|
||||
return overrideUrlLoading(view, url)
|
||||
}
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
|
||||
return overrideUrlLoading(view, url)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
binding.progressBar.visibility = View.GONE
|
||||
if (mLoadError) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
mLoadError = false
|
||||
super.onPageStarted(view, url, favicon)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
override fun onReceivedError(
|
||||
view: WebView?, request: WebResourceRequest?, error: WebResourceError?
|
||||
) {
|
||||
error(error?.errorCode, error?.description, request?.url?.toString())
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
|
||||
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
|
||||
override fun onReceivedError(
|
||||
view: WebView?, errorCode: Int, description: String?, failingUrl: String?
|
||||
) {
|
||||
error(errorCode, description, failingUrl)
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
}
|
||||
|
||||
private fun error(code: Int?, desc: CharSequence?, url: String?) {
|
||||
}
|
||||
|
||||
private fun overrideUrlLoading(view: WebView, url: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
303
app/src/main/java/com/colorful/keyboard/theme/MainActivity.java
Normal file
303
app/src/main/java/com/colorful/keyboard/theme/MainActivity.java
Normal file
@ -0,0 +1,303 @@
|
||||
package com.colorful.keyboard.theme;
|
||||
|
||||
import static com.colorful.keyboard.theme.myutil.AppUtilKt.isMyInputMethodDefault;
|
||||
import static com.colorful.keyboard.theme.myutil.AppUtilKt.isMyInputMethodEnabled;
|
||||
import static com.colorful.keyboard.theme.net.BaseInitApiRequest.getNetData;
|
||||
import static com.colorful.keyboard.theme.net.BaseInitApiRequest.initHHYApiStores;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
|
||||
import com.anythink.core.api.ATSDK;
|
||||
import com.colorful.keyboard.theme.ad.AdsInsUtil;
|
||||
import com.colorful.keyboard.theme.adapter.ViewPager2Adapter;
|
||||
import com.colorful.keyboard.theme.fragment.KeyboardListFragment;
|
||||
import com.colorful.keyboard.theme.model.ThemeListBean;
|
||||
import com.colorful.keyboard.theme.myutil.AppUtilKt;
|
||||
import com.colorful.keyboard.theme.myutil.ConfigBitmapUtils;
|
||||
import com.colorful.keyboard.theme.myutil.ImmUtils;
|
||||
import com.colorful.keyboard.theme.myutil.SPUtils;
|
||||
import com.colorful.keyboard.theme.net.ApiCallbackCommon;
|
||||
import com.colorful.keyboard.theme.net.ApiException;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 viewPager;
|
||||
private List<Fragment> fragmentList = new ArrayList<>();
|
||||
|
||||
private LinearLayout loadingLayout;
|
||||
|
||||
private DrawerLayout drawerLayout;
|
||||
private LinearLayout navLayout;
|
||||
private TextView tvVersion;
|
||||
|
||||
private RelativeLayout keyboardSettingLayout;
|
||||
private TextView keyboardNumber;
|
||||
|
||||
private RelativeLayout dialog_step_layout;
|
||||
private TextView step1Btn;
|
||||
private TextView step2Btn;
|
||||
private LinearLayout no_data_layout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
registerReceiver();
|
||||
// AppLovinSdk.getInstance( this ).showMediationDebugger();
|
||||
ImmUtils.setStatusBar(this, false, false);
|
||||
tabLayout = findViewById(R.id.tabLayout);
|
||||
viewPager = findViewById(R.id.viewPager);
|
||||
no_data_layout = findViewById(R.id.no_data_layout);
|
||||
loadingLayout = findViewById(R.id.loadingLayout);
|
||||
drawerLayout = findViewById(R.id.drawerLayout);
|
||||
navLayout = findViewById(R.id.nav_view);
|
||||
ImageView ivSetting = findViewById(R.id.iv_setting);
|
||||
backToast = Toast.makeText(getBaseContext(), "Press again to exit", Toast.LENGTH_SHORT);
|
||||
tvVersion = findViewById(R.id.tv_version);
|
||||
keyboardSettingLayout = findViewById(R.id.keyboard_setting);
|
||||
dialog_step_layout = findViewById(R.id.dialog_step_layout);
|
||||
keyboardSettingLayout.setOnClickListener(v -> {
|
||||
dialog_step_layout.setVisibility(View.VISIBLE);
|
||||
});
|
||||
dialog_step_layout.setOnClickListener(v -> {
|
||||
dialog_step_layout.setVisibility(View.GONE);
|
||||
});
|
||||
step1Btn = findViewById(R.id.step_1_btn);
|
||||
step2Btn = findViewById(R.id.step_2_btn);
|
||||
keyboardNumber = findViewById(R.id.keyboardNumber);
|
||||
try {
|
||||
PackageManager packageManager = getPackageManager();
|
||||
String packageName = getPackageName();
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
tvVersion.setText("v" + packageInfo.versionName);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (SPUtils.getInstance().getString("bg").isEmpty()) {
|
||||
ConfigBitmapUtils.saveConfigImgToLocal(this, "https://cdn.kikakeyboard.com/terminal/online/20200331/Us21R82MAD-1585651198.jpg.webp");
|
||||
}
|
||||
|
||||
findViewById(R.id.nav_view).setOnClickListener(v -> {
|
||||
});
|
||||
ivSetting.setOnClickListener(view -> {
|
||||
if (!drawerLayout.isDrawerOpen(findViewById(R.id.nav_view))) {
|
||||
drawerLayout.openDrawer(findViewById(R.id.nav_view));
|
||||
}
|
||||
});
|
||||
LinearLayout llYinsi = findViewById(R.id.ll_yinsi);
|
||||
llYinsi.setOnClickListener(view -> {
|
||||
startActivity(new Intent(MainActivity.this, LocalPrivacyAct.class));
|
||||
drawerLayout.close();
|
||||
});
|
||||
LinearLayout llDownload = findViewById(R.id.ll_download);
|
||||
llDownload.setOnClickListener(v -> {
|
||||
drawerLayout.close();
|
||||
startActivity(new Intent(MainActivity.this, DownloadsActivity.class));
|
||||
});
|
||||
LinearLayout ll_share = findViewById(R.id.ll_share);
|
||||
ll_share.setOnClickListener(v -> {
|
||||
drawerLayout.close();
|
||||
AppUtilKt.shareAppInfo(this);
|
||||
});
|
||||
step1Btn.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
|
||||
startActivity(intent);
|
||||
|
||||
});
|
||||
|
||||
step2Btn.setOnClickListener(v -> {
|
||||
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
imeManager.showInputMethodPicker();
|
||||
});
|
||||
|
||||
TextView textView = findViewById(R.id.try_again); // 你的TextView的ID
|
||||
String text = "Load error, try again!";
|
||||
// 创建一个SpannableString并应用下划线
|
||||
SpannableString spannableString = new SpannableString(text);
|
||||
spannableString.setSpan(new UnderlineSpan(), 0, text.length(), 0);
|
||||
// 将带有下划线的文本设置给TextView
|
||||
textView.setText(spannableString);
|
||||
textView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
initData();
|
||||
}
|
||||
});
|
||||
initData();
|
||||
}
|
||||
|
||||
private void initData(){
|
||||
loadingLayout.setVisibility(View.VISIBLE);
|
||||
no_data_layout.setVisibility(View.GONE);
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
hashMap.put("offset", 0);
|
||||
hashMap.put("fetch_size", 100);
|
||||
hashMap.put("sign", "a28aadc61c76c754944f6ddb48962c9c");
|
||||
getNetData(initHHYApiStores().getCategories(hashMap), new ApiCallbackCommon<ThemeListBean>(this, false) {
|
||||
@Override
|
||||
public void onSuccess(ThemeListBean model) {
|
||||
loadingLayout.setVisibility(View.GONE);
|
||||
no_data_layout.setVisibility(View.GONE);
|
||||
fragmentList.clear();
|
||||
for (int i = 0; i < model.getData().getSections().size(); i++) {
|
||||
tabLayout.addTab(tabLayout.newTab());
|
||||
KeyboardListFragment photoFragment = new KeyboardListFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", model.getData().getSections().get(i).getKey());
|
||||
photoFragment.setArguments(bundle);
|
||||
fragmentList.add(photoFragment);
|
||||
}
|
||||
|
||||
ViewPager2Adapter homeTalentViewPager2Adapter = new ViewPager2Adapter(MainActivity.this, fragmentList);
|
||||
viewPager.setAdapter(homeTalentViewPager2Adapter);
|
||||
viewPager.setCurrentItem(0);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
|
||||
TabLayoutMediator mediator = new TabLayoutMediator(tabLayout, viewPager, new TabLayoutMediator.TabConfigurationStrategy() {
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
|
||||
String title = model.getData().getSections().get(position).getTitle().trim().toLowerCase();
|
||||
tab.setText(title);
|
||||
}
|
||||
});
|
||||
|
||||
mediator.attach();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ApiException ex) {
|
||||
loadingLayout.setVisibility(View.GONE);
|
||||
no_data_layout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updateKeyboardSettingLayout();
|
||||
AdsInsUtil.INSTANCE.loadAllAdIsNotCached(this);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void updateKeyboardSettingLayout() {
|
||||
boolean enabledSet = isMyInputMethodEnabled(this);
|
||||
boolean defaultSet = isMyInputMethodDefault(this);
|
||||
|
||||
if (enabledSet) {
|
||||
step1Btn.setBackground(getDrawable(R.drawable.drw_gray_select_bg));
|
||||
step1Btn.setText("Step 1:Enabled");
|
||||
step1Btn.setTextColor(Color.parseColor("#999999"));
|
||||
} else {
|
||||
step1Btn.setBackground(getDrawable(R.drawable.drw_80_btn));
|
||||
step1Btn.setText("Step 1:Select");
|
||||
step1Btn.setTextColor(Color.parseColor("#000000"));
|
||||
}
|
||||
if (defaultSet) {
|
||||
step2Btn.setBackground(getDrawable(R.drawable.drw_gray_select_bg));
|
||||
step2Btn.setText("Step 2:Enabled");
|
||||
step1Btn.setTextColor(Color.parseColor("#999999"));
|
||||
} else {
|
||||
step2Btn.setBackground(getDrawable(R.drawable.drw_80_btn));
|
||||
step2Btn.setText("Step 2:Select");
|
||||
step1Btn.setTextColor(Color.parseColor("#000000"));
|
||||
}
|
||||
|
||||
int number = 0;
|
||||
if (!enabledSet) {
|
||||
number += 1;
|
||||
}
|
||||
if (!defaultSet) {
|
||||
number += 1;
|
||||
}
|
||||
|
||||
keyboardNumber.setText("" + number);
|
||||
|
||||
if (enabledSet && defaultSet) {
|
||||
keyboardSettingLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
keyboardSettingLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
private void registerReceiver() {
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED);
|
||||
receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateKeyboardSettingLayout();
|
||||
boolean enabledSet = isMyInputMethodEnabled(MainActivity.this);
|
||||
boolean defaultSet = isMyInputMethodDefault(MainActivity.this);
|
||||
if (enabledSet && defaultSet) {
|
||||
dialog_step_layout.setVisibility(View.GONE);
|
||||
} else {
|
||||
dialog_step_layout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterReceiver() {
|
||||
if (receiver != null) {
|
||||
unregisterReceiver(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
unregisterReceiver();
|
||||
}
|
||||
|
||||
|
||||
private long backPressedTime = 0;
|
||||
private Toast backToast;
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (backPressedTime + 2000 > System.currentTimeMillis()) {
|
||||
super.onBackPressed();
|
||||
backToast.cancel();
|
||||
} else {
|
||||
backToast.show();
|
||||
}
|
||||
backPressedTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
174
app/src/main/java/com/colorful/keyboard/theme/PreviewActivity.kt
Normal file
174
app/src/main/java/com/colorful/keyboard/theme/PreviewActivity.kt
Normal file
@ -0,0 +1,174 @@
|
||||
package com.colorful.keyboard.theme
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.colorful.keyboard.theme.ad.AdsInsUtil
|
||||
import com.colorful.keyboard.theme.databinding.ColorfulPreviewActivityBinding
|
||||
import com.colorful.keyboard.theme.myutil.currentlyThemeUFileString
|
||||
import com.colorful.keyboard.theme.myutil.getBitmapXXDrawable
|
||||
import com.colorful.keyboard.theme.myutil.isMyInputMethodDefault
|
||||
import com.colorful.keyboard.theme.myutil.isMyInputMethodEnabled
|
||||
import com.colorful.keyboard.theme.myutil.loadAndBlurImage
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
|
||||
class PreviewActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
const val KEY_PREVIEW_URL = "key_preview_url"
|
||||
}
|
||||
|
||||
private lateinit var binding: ColorfulPreviewActivityBinding
|
||||
|
||||
private var themeUrl = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ColorfulPreviewActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
AdsInsUtil.showAdRandomModeAndTiming(this)
|
||||
initBar()
|
||||
themeUrl = intent.getStringExtra(KEY_PREVIEW_URL).toString()
|
||||
registerReceiver()
|
||||
initView()
|
||||
AdsInsUtil.loadAllAdIsNotCached(this)
|
||||
}
|
||||
|
||||
private fun initBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(true)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateSetMyInputMethod()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
val currentlyThemeUFile = currentlyThemeUFileString(this, themeUrl)
|
||||
loadAndBlurImage(
|
||||
this,
|
||||
getBitmapXXDrawable(this, currentlyThemeUFile, "keyboard_preview_screenshot.jpg"),
|
||||
binding.previewIv
|
||||
)
|
||||
|
||||
val imagePath =
|
||||
"$currentlyThemeUFile/res/drawable-xxhdpi-v4/keyboard_preview.jpg"
|
||||
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||
val background = BitmapDrawable(resources, bitmap)
|
||||
binding.keyboardIv.background = background
|
||||
|
||||
binding.backBtn.setOnClickListener { onBackPressed() }
|
||||
|
||||
binding.activateBtn.setOnClickListener {
|
||||
binding.dialogStepLayout.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
binding.step1Btn.setOnClickListener {
|
||||
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.step2Btn.setOnClickListener {
|
||||
val imeManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imeManager.showInputMethodPicker()
|
||||
}
|
||||
|
||||
binding.dialogStepLayout.setOnClickListener {
|
||||
binding.dialogStepLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSetMyInputMethod() {
|
||||
val enabled = isMyInputMethodEnabled(this)
|
||||
val default = isMyInputMethodDefault(this)
|
||||
if (enabled) {
|
||||
binding.step1Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||
binding.step1Btn.text = "Step 1:Enabled"
|
||||
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||
} else {
|
||||
binding.step1Btn.background = getDrawable(R.drawable.drw_80_btn)
|
||||
binding.step1Btn.text = "Step 1:Select"
|
||||
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||
}
|
||||
if (default) {
|
||||
binding.step2Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||
binding.step2Btn.text = "Step 2:Enabled"
|
||||
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||
} else {
|
||||
binding.step2Btn.background = getDrawable(R.drawable.drw_80_btn)
|
||||
binding.step2Btn.text = "Step 2:Select"
|
||||
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||
}
|
||||
|
||||
if (enabled && default) {
|
||||
binding.edit.visibility = View.VISIBLE
|
||||
binding.tv.visibility = View.GONE
|
||||
} else {
|
||||
binding.edit.visibility = View.GONE
|
||||
binding.tv.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (enabled && default) {
|
||||
binding.unauthorizedLayout.visibility = View.INVISIBLE
|
||||
|
||||
showKeyboard(binding.edit)
|
||||
}
|
||||
}
|
||||
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
private fun registerReceiver() {
|
||||
val filter = IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)
|
||||
receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
updateSetMyInputMethod()
|
||||
val enabled = isMyInputMethodEnabled(this@PreviewActivity)
|
||||
val default = isMyInputMethodDefault(this@PreviewActivity)
|
||||
if (enabled && default) {
|
||||
binding.dialogStepLayout.visibility = View.GONE
|
||||
binding.edit.visibility = View.VISIBLE
|
||||
binding.tv.visibility = View.GONE
|
||||
showKeyboard(binding.edit)
|
||||
} else {
|
||||
binding.dialogStepLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
private fun unregisterReceiver() {
|
||||
if (receiver != null) {
|
||||
unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver()
|
||||
}
|
||||
|
||||
private fun showKeyboard(view: View) {
|
||||
view.postDelayed({
|
||||
view.requestFocus()
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 1000) // 设置延迟时间
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.colorful.keyboard.theme;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.colorful.keyboard.theme.myutil.ImmUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
|
||||
public class SettingActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_setting);
|
||||
ImmUtils.setStatusBar(this, false, false);
|
||||
TextView tvVersion = findViewById(R.id.tv_version);
|
||||
ImageView ivBack = findViewById(R.id.iv_back_setting);
|
||||
LinearLayout llyinsi = findViewById(R.id.ll_yinsi);
|
||||
PackageManager packageManager = getPackageManager();
|
||||
String packageName = getPackageName();
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
tvVersion.setText("v" + packageInfo.versionName);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
llyinsi.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(SettingActivity.this, LocalPrivacyAct.class));
|
||||
}
|
||||
});
|
||||
ivBack.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.colorful.keyboard.theme;
|
||||
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
|
||||
public class TestFragment extends PreferenceFragment {
|
||||
}
|
||||
@ -0,0 +1,286 @@
|
||||
package com.colorful.keyboard.theme;
|
||||
|
||||
import static com.colorful.keyboard.theme.net.BaseInitApiRequest.getNetData;
|
||||
import static com.colorful.keyboard.theme.net.BaseInitApiRequest.initHHYApiStores;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.colorful.keyboard.theme.constant.ConfigConstant;
|
||||
import com.colorful.keyboard.theme.latin.LatinIME;
|
||||
import com.colorful.keyboard.theme.model.ThemeDetailsBean;
|
||||
import com.colorful.keyboard.theme.myutil.ConfigFileUtils;
|
||||
import com.colorful.keyboard.theme.myutil.ImmUtils;
|
||||
import com.colorful.keyboard.theme.myutil.SPUtils;
|
||||
import com.colorful.keyboard.theme.net.ApiCallbackCommon;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ThemeSettingsActivity extends AppCompatActivity {
|
||||
|
||||
private String key;
|
||||
private String title;
|
||||
private TextView tvStepOne;
|
||||
private TextView tvStepTwo;
|
||||
private LinearLayout ll_setting;
|
||||
private LinearLayout ll_step;
|
||||
private TextView tv_to_setting;
|
||||
ThemeDetailsBean themeDetailsBean;
|
||||
boolean isCurrentSetting = false;
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_setting_theme);
|
||||
ImmUtils.setStatusBar(this,false,false);
|
||||
title = getIntent().getStringExtra("title");
|
||||
key = getIntent().getStringExtra("key");
|
||||
String url = getIntent().getStringExtra("url");
|
||||
|
||||
ImageView imageView = findViewById(R.id.iv_back_setting);
|
||||
ll_setting = findViewById(R.id.ll_setting);
|
||||
ll_step = findViewById(R.id.ll_step);
|
||||
tv_to_setting = findViewById(R.id.tv_to_setting);
|
||||
|
||||
|
||||
tvStepOne = findViewById(R.id.tv_step_one);
|
||||
tvStepTwo = findViewById(R.id.tv_step_two);
|
||||
|
||||
ImageView iv_theme = findViewById(R.id.iv_theme);
|
||||
Glide.with(this).load(url).into(iv_theme);
|
||||
TextView tv_setting_title = findViewById(R.id.tv_setting_title);
|
||||
tv_setting_title.setText(title);
|
||||
|
||||
imageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
getInfo();
|
||||
|
||||
/* new MaxAdInputManager(this, Constant.adIdSee, new AdInputListener() {
|
||||
@Override
|
||||
public void adShow() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adClose() {
|
||||
}
|
||||
});*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if(isInputMethodOfThisImeEnabled()){
|
||||
tvStepOne.setBackground(getResources().getDrawable(R.drawable.shape_green));
|
||||
}else {
|
||||
tvStepOne.setBackground(getResources().getDrawable(R.drawable.shape_grey));
|
||||
}
|
||||
String string = Settings.Secure.getString(getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
|
||||
|
||||
boolean isMy = string.contains("com.colorful.keyboard.theme");
|
||||
|
||||
if(isMy){
|
||||
tvStepTwo.setBackground(getResources().getDrawable(R.drawable.shape_green));
|
||||
}else {
|
||||
tvStepTwo.setBackground(getResources().getDrawable(R.drawable.shape_grey));
|
||||
}
|
||||
Log.e("666",string);
|
||||
|
||||
tvStepOne.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(!isInputMethodOfThisImeEnabled()){
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_INPUT_METHOD_SETTINGS);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tvStepTwo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(!isMy){
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(INPUT_METHOD_ACTION);
|
||||
registerReceiver(inputMethodChangeReceiver, intentFilter);
|
||||
|
||||
try {
|
||||
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).showInputMethodPicker();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(isMy && isInputMethodOfThisImeEnabled()){
|
||||
ll_step.setVisibility(View.GONE);
|
||||
ll_setting.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
ll_step.setVisibility(View.VISIBLE);
|
||||
ll_setting.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
tv_to_setting.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(!isCurrentSetting){
|
||||
if (null!=themeDetailsBean){
|
||||
SPUtils.getInstance().put("id",themeDetailsBean.getData().getItem().getKey());
|
||||
/* new MaxAdInputManager(ThemeSettingsActivity.this, Constant.adIdSetting, new AdInputListener() {
|
||||
@Override
|
||||
public void adShow() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adClose() {
|
||||
saveConfigImgToLocal(ThemeSettingsActivity.this,themeDetailsBean.getData().getItem().getThemeContent().getImgBanner());
|
||||
}
|
||||
});*/
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void saveConfigImgToLocal(Context context, String url) {
|
||||
Glide.with(context)
|
||||
.downloadOnly()
|
||||
.load(url)
|
||||
.listener(new RequestListener<File>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
||||
configSaveToAlbum(context, resource.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.preload();
|
||||
|
||||
}
|
||||
|
||||
public void configSaveToAlbum(Context context, String srcPath) {
|
||||
String imageSrc = context.getExternalFilesDir("input").getAbsolutePath() +"image/"+ System.currentTimeMillis() + ".png";
|
||||
File file = new File(imageSrc);
|
||||
SPUtils.getInstance().put("bg",imageSrc);
|
||||
|
||||
App.Companion.getApp().stopService(new Intent(App.Companion.getApp(), LatinIME.class));
|
||||
App.Companion.getApp().startService(new Intent(App.Companion.getApp(),LatinIME.class));
|
||||
ConfigConstant.isNeedSetting = true;
|
||||
Toast.makeText(ThemeSettingsActivity.this,"Success",Toast.LENGTH_SHORT).show();
|
||||
if(SPUtils.getInstance().getString("id") .equals(themeDetailsBean.getData().getItem().getKey()) ){
|
||||
isCurrentSetting = true;
|
||||
tv_to_setting.setBackground(getResources().getDrawable(R.drawable.shape_green));
|
||||
}else{
|
||||
tv_to_setting.setBackground(getResources().getDrawable(R.drawable.shape_grey));
|
||||
isCurrentSetting = false;
|
||||
}
|
||||
boolean isCopySuccess = false;
|
||||
try {
|
||||
isCopySuccess = ConfigFileUtils.configCopy(srcPath, file.getAbsolutePath(),false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
/*if (isCopySuccess) {
|
||||
try {
|
||||
MediaStore.Images.Media.insertImage(context.getContentResolver(),
|
||||
file.getAbsolutePath(),file.getName() , null);
|
||||
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(file.getAbsolutePath())));
|
||||
Toast.makeText(context,"successfully",Toast.LENGTH_SHORT).show();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
private static final String INPUT_METHOD_ACTION = "android.intent.action.INPUT_METHOD_CHANGED";
|
||||
|
||||
private BroadcastReceiver inputMethodChangeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(INPUT_METHOD_ACTION)) {
|
||||
//监听到输入法发生改变
|
||||
onResume();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private boolean isInputMethodOfThisImeEnabled() {
|
||||
final InputMethodManager imm =
|
||||
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
final String imePackageName = getPackageName();
|
||||
for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
|
||||
if (imi.getPackageName().equals(imePackageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getInfo(){
|
||||
HashMap<String,String> hashMap = new HashMap<>();
|
||||
hashMap.put("sign","a28aadc61c76c754944f6ddb48962c9c");
|
||||
getNetData(initHHYApiStores().getDetailsTheme(key,hashMap),
|
||||
new ApiCallbackCommon<ThemeDetailsBean>(this, false) {
|
||||
@Override
|
||||
public void onSuccess(ThemeDetailsBean model) {
|
||||
themeDetailsBean = model;
|
||||
Log.e("666",SPUtils.getInstance().getString("id"));
|
||||
Log.e("666",model.getData().getItem().getKey());
|
||||
Log.e("666",model.getData().getItem().getKey());
|
||||
Log.e("666",(SPUtils.getInstance().getString("id") == model.getData().getItem().getKey())+"");
|
||||
if(SPUtils.getInstance().getString("id") .equals(model.getData().getItem().getKey()) ){
|
||||
isCurrentSetting = true;
|
||||
tv_to_setting.setBackground(getResources().getDrawable(R.drawable.shape_green));
|
||||
}else{
|
||||
tv_to_setting.setBackground(getResources().getDrawable(R.drawable.shape_grey));
|
||||
isCurrentSetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(com.colorful.keyboard.theme.net.ApiException ex) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import com.anythink.core.api.ATAdInfo
|
||||
import com.anythink.core.api.AdError
|
||||
import com.anythink.interstitial.api.ATInterstitial
|
||||
import com.anythink.interstitial.api.ATInterstitialListener
|
||||
|
||||
class AdInstLoad {
|
||||
private var mPlace: String
|
||||
private var adLoadListener: LoadListener? = null
|
||||
private var activity: Activity? = null
|
||||
|
||||
constructor(activity: Activity, place: String, listener: LoadListener?) {
|
||||
this.mPlace = place
|
||||
this.adLoadListener = listener
|
||||
this.activity = activity
|
||||
init()
|
||||
}
|
||||
|
||||
constructor(place: String, listener: LoadListener?) {
|
||||
this.mPlace = place
|
||||
this.adLoadListener = listener
|
||||
init()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
|
||||
val atInterstitial = ATInterstitial(activity, mPlace)
|
||||
atInterstitial.setAdListener(object : ATInterstitialListener {
|
||||
override fun onInterstitialAdLoaded() {
|
||||
val info = atInterstitial.checkAdStatus().atTopAdInfo
|
||||
|
||||
InstAdCacheManager.instance.setAdCache(mPlace, atInterstitial)
|
||||
adLoadListener?.loaded(info)
|
||||
}
|
||||
|
||||
override fun onInterstitialAdLoadFail(adError: AdError?) {
|
||||
adLoadListener?.loadFailed(adError)
|
||||
|
||||
Log.d("ocean","onInterstitialAdLoadFail->${adError} ")
|
||||
|
||||
Log.d("ocean","AdError.getFullErrorInfo()->${adError?.fullErrorInfo}")
|
||||
}
|
||||
|
||||
override fun onInterstitialAdClicked(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdShow(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdClose(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoStart(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoEnd(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoError(p0: AdError?) {
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
atInterstitial.load()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import android.app.Activity
|
||||
import com.anythink.core.api.ATAdInfo
|
||||
import com.anythink.core.api.AdError
|
||||
import com.anythink.interstitial.api.ATInterstitialListener
|
||||
import com.colorful.keyboard.theme.App
|
||||
|
||||
class AdInstShower {
|
||||
private var mPlace: String
|
||||
private var showListener: ShowListener? = null
|
||||
private var activity: Activity? = null
|
||||
|
||||
constructor(activity: Activity, place: String, showListener: ShowListener?) {
|
||||
this.mPlace = place
|
||||
this.showListener = showListener
|
||||
this.activity = activity
|
||||
init()
|
||||
}
|
||||
|
||||
constructor(place: String, showListener: ShowListener?) {
|
||||
this.mPlace = place
|
||||
this.showListener = showListener
|
||||
init()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
val atInterstitial = InstAdCacheManager.instance.getAdCache(mPlace)
|
||||
atInterstitial?.setAdListener(object : ATInterstitialListener {
|
||||
override fun onInterstitialAdLoaded() {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdLoadFail(p0: AdError?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdClicked(p0: ATAdInfo?) {
|
||||
showListener?.onAdClicked()
|
||||
}
|
||||
|
||||
override fun onInterstitialAdShow(p0: ATAdInfo?) {
|
||||
showListener?.onAdShown(p0)
|
||||
App.app.lastAdDisplayTime.set(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun onInterstitialAdClose(p0: ATAdInfo?) {
|
||||
showListener?.onAdClosed()
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoStart(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoEnd(p0: ATAdInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterstitialAdVideoError(adError: AdError?) {
|
||||
showListener?.onAdShowFailed(AdShowFailed(adError.toString()))
|
||||
}
|
||||
|
||||
})
|
||||
if (atInterstitial?.isAdReady == true) {
|
||||
atInterstitial.show(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
data class AdShowFailed(
|
||||
val msg: String = "",
|
||||
)
|
||||
@ -0,0 +1,90 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import android.app.Activity
|
||||
import com.colorful.keyboard.theme.App
|
||||
import com.colorful.keyboard.theme.myutil.AppSharedPreferences
|
||||
import com.unity3d.services.ads.api.Show
|
||||
import java.util.Random
|
||||
|
||||
object AdsInsUtil {
|
||||
|
||||
object Placement {
|
||||
const val TOP_ON_AD_ONE = "n66b1cf2f2c70c"
|
||||
const val TOP_ON_AD_TOW = "n66b1cf2f04a38"
|
||||
const val TOP_ON_AD_THREE = "n66b1cf2ecd817"
|
||||
|
||||
val adPlaceAllList = listOf(
|
||||
TOP_ON_AD_ONE,
|
||||
TOP_ON_AD_TOW,
|
||||
TOP_ON_AD_THREE
|
||||
)
|
||||
}
|
||||
|
||||
fun loadSplashAllAd(act: Activity) {
|
||||
Placement.adPlaceAllList.drop(1).forEach { placement ->
|
||||
loadAdIfNotCached(act, placement)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAllAdIsNotCached(act: Activity) {
|
||||
Placement.adPlaceAllList.forEach { placement ->
|
||||
loadAdIfNotCached(act, placement)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAdIfNotCached(act: Activity, placement: String, listener: LoadListener? = null) {
|
||||
if (act.isFinishing || canShowAd(placement)) return
|
||||
loadAd(act, placement, listener)
|
||||
}
|
||||
|
||||
fun showAdRandomModeAndTiming(act: Activity, listener: ShowListener? = null) {
|
||||
val adPlace: MutableList<String> = ArrayList()
|
||||
Placement.adPlaceAllList.forEach { placement ->
|
||||
if (canShowAd(placement)) {
|
||||
adPlace.add(placement)
|
||||
}
|
||||
}
|
||||
if (adPlace.isNotEmpty()) {
|
||||
val placeId = Random().nextInt(adPlace.size)
|
||||
val place = adPlace[placeId]
|
||||
showAd(act, place, listener)
|
||||
}
|
||||
}
|
||||
|
||||
fun canShowAd(adID: String): Boolean {
|
||||
InstAdCacheManager.instance.getAdCache(adID)?.let {
|
||||
return it.isAdReady
|
||||
} ?: let {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAd(
|
||||
act: Activity,
|
||||
adID: String,
|
||||
loadListener: LoadListener?
|
||||
): AdInstLoad {
|
||||
return AdInstLoad(act, adID, loadListener)
|
||||
}
|
||||
|
||||
fun showAd(
|
||||
act: Activity,
|
||||
adID: String,
|
||||
listener: ShowListener?
|
||||
): AdInstShower {
|
||||
return AdInstShower(act, adID, listener)
|
||||
}
|
||||
|
||||
fun showAdTiming(act: Activity, placement: String, listener: ShowListener? = null) {
|
||||
//当前时间减去旧时间,才展示广告(满足间隔时间才show广告)
|
||||
val showAdIntervalTime = AppSharedPreferences(App.app).getShowAdIntervalTime()
|
||||
if (System.currentTimeMillis() - App.app.lastAdDisplayTime.get() >= showAdIntervalTime) {
|
||||
//判断当前广告位是否可以show,不能show则判断补位广告是否可以show。
|
||||
if (act.isFinishing || !canShowAd(placement)) {
|
||||
showAd(act, placement, listener)
|
||||
} else {
|
||||
showAd(act, placement, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import com.anythink.interstitial.api.ATInterstitial
|
||||
|
||||
class InstAdCacheManager {
|
||||
private val mAdCacheDict: MutableMap<String, ATInterstitial> = mutableMapOf()
|
||||
|
||||
companion object {
|
||||
val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
InstAdCacheManager()
|
||||
}
|
||||
}
|
||||
|
||||
fun setAdCache(place: String, adCache: ATInterstitial) {
|
||||
mAdCacheDict[place] = adCache
|
||||
}
|
||||
|
||||
fun getAdCache(place: String): ATInterstitial? {
|
||||
return mAdCacheDict[place]
|
||||
}
|
||||
|
||||
fun getLoadedInstCount(): Int {
|
||||
var count = 0
|
||||
try {
|
||||
mAdCacheDict.forEach { (key, value) ->
|
||||
if (value.isAdReady) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import com.anythink.core.api.ATAdInfo
|
||||
import com.anythink.core.api.AdError
|
||||
|
||||
|
||||
interface LoadListener {
|
||||
fun loadFailed(error: AdError?) {}
|
||||
fun loaded(ad: ATAdInfo) {}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.colorful.keyboard.theme.ad
|
||||
|
||||
import com.anythink.core.api.ATAdInfo
|
||||
import com.colorful.keyboard.theme.ad.AdShowFailed
|
||||
|
||||
interface ShowListener {
|
||||
fun onAdShown(ad: ATAdInfo?) {}
|
||||
fun onAdShowFailed(error: AdShowFailed?) {}
|
||||
fun onAdClosed() {}
|
||||
fun onAdClicked() {}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class Async {
|
||||
private static ThreadPoolExecutorWrapper sThreadPoolExecutorWrapper;
|
||||
|
||||
private static ThreadPoolExecutorWrapper getThreadPoolExecutorWrapper() {
|
||||
if (sThreadPoolExecutorWrapper == null) {
|
||||
synchronized (Async.class) {
|
||||
if (sThreadPoolExecutorWrapper == null) {
|
||||
sThreadPoolExecutorWrapper = new ThreadPoolExecutorWrapper(12, 12, 10);
|
||||
// if (BuildConfig.DEBUG)
|
||||
// LogUtil.d(LogFilterDef.APP_INIT, LogHelper.getFileLineMethod(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sThreadPoolExecutorWrapper;
|
||||
}
|
||||
|
||||
public static void run(Runnable task) {
|
||||
getThreadPoolExecutorWrapper().executeTask(task);
|
||||
}
|
||||
|
||||
public static <T> Future<T> submit(Callable<T> task) {
|
||||
return getThreadPoolExecutorWrapper().submitTask(task);
|
||||
}
|
||||
|
||||
public static boolean isMainThread() {
|
||||
return Thread.currentThread() == Looper.getMainLooper().getThread();
|
||||
}
|
||||
|
||||
public static void schedule(long delay, Runnable task) {
|
||||
getThreadPoolExecutorWrapper().scheduleTask(delay, task);
|
||||
}
|
||||
|
||||
public static void scheduleTaskAtFixedRateIgnoringTaskRunningTime(long initialDelay, long period, Runnable task) {
|
||||
getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIgnoringTaskRunningTime(initialDelay, period, task);
|
||||
}
|
||||
|
||||
public static void scheduleTaskAtFixedRateIncludingTaskRunningTime(long initialDelay, long period, Runnable task) {
|
||||
getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIncludingTaskRunningTime(initialDelay, period, task);
|
||||
}
|
||||
|
||||
public static boolean removeScheduledTask(Runnable task) {
|
||||
return getThreadPoolExecutorWrapper().removeScheduledTask(task);
|
||||
}
|
||||
|
||||
public static void scheduleTaskOnUiThread(long delay, Runnable task) {
|
||||
getThreadPoolExecutorWrapper().scheduleTaskOnUiThread(delay, task);
|
||||
}
|
||||
|
||||
public static void removeScheduledTaskOnUiThread(Runnable task) {
|
||||
getThreadPoolExecutorWrapper().removeScheduledTaskOnUiThread(task);
|
||||
}
|
||||
|
||||
public static void runOnUiThread(Runnable task) {
|
||||
getThreadPoolExecutorWrapper().runTaskOnUiThread(task);
|
||||
}
|
||||
|
||||
public static Handler getMainHandler() {
|
||||
return getThreadPoolExecutorWrapper().getMainHandler();
|
||||
}
|
||||
|
||||
/*
|
||||
single background thread to execute Job
|
||||
*/
|
||||
public static void scheduleInQueue(Runnable task) {
|
||||
getThreadPoolExecutorWrapper().scheduleOnQueue(task);
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if (sThreadPoolExecutorWrapper != null) {
|
||||
sThreadPoolExecutorWrapper.shutdown();
|
||||
sThreadPoolExecutorWrapper = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
public class HandlerPoster extends Handler {
|
||||
|
||||
private final int ASYNC = 1;
|
||||
|
||||
private final int SYNC = 2;
|
||||
|
||||
private final Queue<Runnable> asyncPool;
|
||||
|
||||
private final Queue<SyncPost> syncPool;
|
||||
|
||||
private final int maxMillisInsideHandleMessage;
|
||||
|
||||
private boolean asyncActive;//执行状态。避免重复发送消息导致消息队列过多
|
||||
|
||||
private boolean syncActive;//执行状态。避免重复发送消息导致消息队列过多
|
||||
|
||||
HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
|
||||
super(looper);
|
||||
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
|
||||
asyncPool = new LinkedList<Runnable>();
|
||||
syncPool = new LinkedList<SyncPost>();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
this.removeCallbacksAndMessages(null);
|
||||
this.asyncPool.clear();
|
||||
this.syncPool.clear();
|
||||
}
|
||||
|
||||
void async(Runnable runnable) throws Exception {
|
||||
synchronized (asyncPool) {
|
||||
asyncPool.offer(runnable);
|
||||
//判断当前是否有异步任务正在执行
|
||||
if (!asyncActive) {
|
||||
asyncActive = true;
|
||||
if (!sendMessage(obtainMessage(ASYNC))) {
|
||||
throw new Exception("Could not send handler message");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sync(SyncPost post) throws Exception {
|
||||
synchronized (syncPool) {
|
||||
syncPool.offer(post);
|
||||
//判断当前是否有同步任务正在执行
|
||||
if (!syncActive) {
|
||||
syncActive = true;
|
||||
if (!sendMessage(obtainMessage(SYNC))) {
|
||||
throw new Exception("Could not send handler message");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == ASYNC) {
|
||||
boolean rescheduled = false;
|
||||
try {
|
||||
//当执行完一个任务后就判断一次是否超过时间限制,如果超过,那么不管队列中的任务是否执行完成都退出,同时发起一个新的消息到Handler循环队列
|
||||
//在while部分,使用poll从队列取出一个任务,判断是否为空,如果为空进入队列同步块;然后再取一次,再次判断。
|
||||
//如果恰巧在进入同步队列之前有新的任务来了,那么第二次取到的当然就不是 NULL也就会继续执行下去。
|
||||
//反之,如果还是为空;那么重置当前队列的状态为false,同时跳出循环。
|
||||
long started = SystemClock.uptimeMillis();
|
||||
while (true) {
|
||||
Runnable runnable = null;
|
||||
synchronized (asyncPool){
|
||||
runnable = asyncPool.size()==0?null:asyncPool.poll();//2018.12.24 add by xw 如果为空就去null
|
||||
}
|
||||
if (runnable == null) {
|
||||
synchronized (asyncPool) {
|
||||
// Check again, this time in synchronized
|
||||
runnable = asyncPool.poll();
|
||||
if (runnable == null) {
|
||||
asyncActive = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
runnable.run();
|
||||
long timeInMethod = SystemClock.uptimeMillis() - started;
|
||||
if (timeInMethod >= maxMillisInsideHandleMessage) {
|
||||
if (!sendMessage(obtainMessage(ASYNC))) {
|
||||
throw new Exception("Could not send handler message");
|
||||
}
|
||||
rescheduled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
asyncActive = rescheduled;
|
||||
}
|
||||
} else if (msg.what == SYNC) {
|
||||
boolean rescheduled = false;
|
||||
try {
|
||||
long started = SystemClock.uptimeMillis();
|
||||
while (true) {
|
||||
SyncPost post = syncPool.poll();
|
||||
if (post == null) {
|
||||
synchronized (syncPool) {
|
||||
// Check again, this time in synchronized
|
||||
post = syncPool.poll();
|
||||
if (post == null) {
|
||||
syncActive = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
post.run();
|
||||
long timeInMethod = SystemClock.uptimeMillis() - started;
|
||||
if (timeInMethod >= maxMillisInsideHandleMessage) {
|
||||
if (!sendMessage(obtainMessage(SYNC))) {
|
||||
throw new Exception("Could not send handler message");
|
||||
}
|
||||
rescheduled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
syncActive = rescheduled;
|
||||
}
|
||||
} else
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* 网上找到的关于子线程切换到主线程的代码,实现子线程任意时刻切换到主线程,并可选择地阻塞子线程。
|
||||
* 此方案避免handler满天飞的情况。
|
||||
* 参考资料:http://c.jinhusns.com/cms/c-884
|
||||
* Created by DonWZ on 2016-10-18
|
||||
*/
|
||||
public class MainThreadSwitcher {
|
||||
private static HandlerPoster mainPoster = null;
|
||||
|
||||
private static HandlerPoster getMainPoster() {
|
||||
if (mainPoster == null) {
|
||||
synchronized (MainThreadSwitcher.class) {
|
||||
if (mainPoster == null) {
|
||||
mainPoster = new HandlerPoster(Looper.getMainLooper(), 500);//限制主线程单次运行时间
|
||||
}
|
||||
}
|
||||
}
|
||||
return mainPoster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously.
|
||||
* The child thread asynchronous run relative to the main thread,
|
||||
* not blocking the child thread
|
||||
* @param runnable
|
||||
* Runnable Interface
|
||||
*/
|
||||
public static void runOnMainThreadAsync(Runnable runnable) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
getMainPoster().async(runnable);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Synchronously.
|
||||
* The child thread relative thread synchronization operation,
|
||||
* blocking the child thread,
|
||||
* thread for the main thread to complete
|
||||
* @param runnable
|
||||
* Runnable Interface
|
||||
*/
|
||||
public static void runOnMainThreadSync(Runnable runnable) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
SyncPost poster = new SyncPost(runnable);
|
||||
try {
|
||||
getMainPoster().sync(poster);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
poster.waitRun();
|
||||
}
|
||||
|
||||
public static void dispose() {
|
||||
if (mainPoster != null) {
|
||||
mainPoster.dispose();
|
||||
mainPoster = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
public class SyncPost {
|
||||
|
||||
boolean end = false;
|
||||
|
||||
Runnable runnable;
|
||||
|
||||
SyncPost(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
//进入同步块,然后调用Runnable接口的run方法。同时在执行完成后将end重置为true;
|
||||
//然后调用this.notifyAll();通知等待的部分可以继续了,当然有这样的情况;假如在进入该同步块的时候子线程还未执行到this.wait();部分呢?
|
||||
//所以我们为此准备了end和try。
|
||||
synchronized (this) {
|
||||
runnable.run();
|
||||
end = true;
|
||||
try {
|
||||
this.notifyAll();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void waitRun() {
|
||||
//首先判断状态,如果状态已经变了,那么证明子线程执行到此处时,主线程已经执行了void_run()。
|
||||
//所以也就不用进入同步块进行等待了。反之进入等待直到主线程调用this.notifyAll();
|
||||
if (!end) {
|
||||
synchronized (this) {
|
||||
if (!end) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class TaskQueue extends Thread{
|
||||
private BlockingQueue<Object> mQueue;
|
||||
|
||||
public TaskQueue() {
|
||||
mQueue = new LinkedBlockingQueue<Object>();
|
||||
}
|
||||
|
||||
public TaskQueue(String name) {
|
||||
this();
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public void stopTaskQueue() {
|
||||
// use 'Poison Pill Shutdown' to stop the task queue
|
||||
// add a non-Runnable object, which will be recognized as the command
|
||||
// by the thread to break the infinite loop
|
||||
mQueue.add(new Object());
|
||||
}
|
||||
|
||||
public void scheduleTask(Runnable task) {
|
||||
mQueue.add(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Object obj = mQueue.take();
|
||||
|
||||
if (obj instanceof Runnable) {
|
||||
((Runnable) obj).run();
|
||||
obj = null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
package com.colorful.keyboard.theme.ad.async;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThreadPoolExecutorWrapper {
|
||||
private static final Long IDLE_THREAD_KEEP_ALIVE_TIME = 60L;
|
||||
private ExecutorService mThreadPoolExecutor;//normal thread pool
|
||||
private ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor;//can schedule task thread pool
|
||||
private Handler mMainHandler;
|
||||
private TaskQueue mActionQueue;
|
||||
private Map<Integer, Object> mScheduledJobRecord = new HashMap<>();//ScheduledThreadPoolExecutor will wrap Runnable, so we record this
|
||||
private Object mScheduledJobRecordMutex = new Object();
|
||||
|
||||
/*
|
||||
maxThreadCount:thread pool max thread count
|
||||
activeThreadCount:thread pool min thread count even if all thread is idle
|
||||
IDLE_THREAD_KEEP_ALIVE_TIME:IDLE thread will be shutdown when TIME_OUT if (maxThreadCount - activeThreadCount) > 0
|
||||
*/
|
||||
public ThreadPoolExecutorWrapper(int activeThreadCount, int maxThreadCount, int maxScheTaskThread) {
|
||||
mThreadPoolExecutor = new ThreadPoolExecutor(activeThreadCount, maxThreadCount,
|
||||
IDLE_THREAD_KEEP_ALIVE_TIME, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
if (maxScheTaskThread > 0) {
|
||||
mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(maxScheTaskThread);
|
||||
}
|
||||
|
||||
mMainHandler = new Handler(Looper.getMainLooper());
|
||||
mActionQueue = new TaskQueue(Async.class.getName());
|
||||
mActionQueue.start();
|
||||
}
|
||||
|
||||
public void executeTask(Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mThreadPoolExecutor.execute(task);
|
||||
}
|
||||
|
||||
public <T> Future<T> submitTask(Callable<T> task) {
|
||||
return mThreadPoolExecutor.submit(task);
|
||||
}
|
||||
|
||||
public void scheduleTask(long delay, Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mScheduledThreadPoolExecutor.schedule(task, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void scheduleTaskAtFixedRateIgnoringTaskRunningTime(long initialDelay, long period, Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mScheduledJobRecordMutex) {
|
||||
if (mScheduledJobRecord.containsKey(task.hashCode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
mScheduledJobRecord.put(task.hashCode(), mScheduledThreadPoolExecutor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleTaskAtFixedRateIncludingTaskRunningTime(long initialDelay, long period, Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mScheduledJobRecordMutex) {
|
||||
if (mScheduledJobRecord.containsKey(task.hashCode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
mScheduledJobRecord.put(task.hashCode(), mScheduledThreadPoolExecutor.scheduleWithFixedDelay(task, initialDelay, period, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeScheduledTask(Runnable task) {
|
||||
if (task == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized (mScheduledJobRecordMutex) {
|
||||
if (!mScheduledJobRecord.containsKey(task.hashCode())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScheduledFuture<?> internalJob = (ScheduledFuture<?>) mScheduledJobRecord.get(task.hashCode());
|
||||
internalJob.cancel(true);
|
||||
mScheduledJobRecord.remove(task.hashCode());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleTaskOnUiThread(long delay, Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mMainHandler.postDelayed(task, delay);
|
||||
}
|
||||
|
||||
public void removeScheduledTaskOnUiThread(Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mMainHandler.removeCallbacks(task);
|
||||
}
|
||||
|
||||
public void runTaskOnUiThread(Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMainHandler.post(task);
|
||||
}
|
||||
|
||||
public Handler getMainHandler() {
|
||||
return mMainHandler;
|
||||
}
|
||||
|
||||
public void scheduleOnQueue(Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mActionQueue.scheduleTask(task);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (mThreadPoolExecutor != null) {
|
||||
mThreadPoolExecutor.shutdown();
|
||||
mThreadPoolExecutor = null;
|
||||
}
|
||||
|
||||
if (mScheduledThreadPoolExecutor != null) {
|
||||
mScheduledThreadPoolExecutor.shutdown();
|
||||
mScheduledThreadPoolExecutor = null;
|
||||
}
|
||||
|
||||
if (mActionQueue != null) {
|
||||
mActionQueue.stopTaskQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.colorful.keyboard.theme.adapter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ViewPager2Adapter extends FragmentStateAdapter {
|
||||
List<Fragment> fragmentList;
|
||||
public ViewPager2Adapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragmentList) {
|
||||
super(fragmentActivity);
|
||||
this.fragmentList = fragmentList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
}
|
||||
10
app/src/main/java/com/colorful/keyboard/theme/bean/Author.kt
Normal file
10
app/src/main/java/com/colorful/keyboard/theme/bean/Author.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class Author(
|
||||
val name: String,
|
||||
val key: String,
|
||||
val photoUrl: String,
|
||||
val homeUrl: String
|
||||
):Serializable
|
||||
@ -0,0 +1,12 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class CategoryDataBean(
|
||||
val layout: Int,
|
||||
val grid: Int,
|
||||
val type: Int,
|
||||
val title: String,
|
||||
val key: String,
|
||||
var items: List<ItemDataBean>
|
||||
) : Serializable
|
||||
@ -0,0 +1,7 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class Content(
|
||||
val imageUrl: String
|
||||
) : Serializable
|
||||
@ -0,0 +1,16 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class DetailsBean(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val type: Int,
|
||||
val thumbUrl: String,
|
||||
val pkgName: String,
|
||||
val thumbUrlGif: String,
|
||||
val content: Content,
|
||||
val themeContent: ThemeDetailsContent,
|
||||
val author: Author,
|
||||
val lock: LockBean
|
||||
) : Serializable
|
||||
@ -0,0 +1,18 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
@Entity
|
||||
data class DownloadBean(
|
||||
@ColumnInfo(name = "title") var title: String,
|
||||
@ColumnInfo(name = "key") var key: String,
|
||||
@ColumnInfo(name = "imgUrl") var imgUrl: String,
|
||||
) : Serializable {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class ItemDataBean(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val type: Int,
|
||||
val thumbUrl: String,
|
||||
val thumbUrlGif: String,
|
||||
val themeContentBean: ThemeContentBean,
|
||||
val lockBean: LockBean
|
||||
) : Serializable
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class LockBean(
|
||||
val type: Int
|
||||
) : Serializable
|
||||
@ -0,0 +1,13 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class MainDataBean(
|
||||
val layout: Int,
|
||||
val grid: Int,
|
||||
val type: Int,
|
||||
var title: String,
|
||||
val key: String,
|
||||
val mainItemDataBean: MainItemDataBean,
|
||||
var items: List<ItemDataBean>? = null,
|
||||
) : Serializable
|
||||
@ -0,0 +1,11 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class MainItemDataBean(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val type: Int,
|
||||
val thumbUrl: String
|
||||
) : Serializable
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class ThemeContentBean(
|
||||
val pushIcon: String
|
||||
) : Serializable
|
||||
@ -0,0 +1,13 @@
|
||||
package com.colorful.keyboard.theme.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class ThemeDetailsContent(
|
||||
val img: String,
|
||||
val imgBanner: String,
|
||||
val imgPreviewGif: String,
|
||||
val pushIcon: String,
|
||||
val pushBanner: String,
|
||||
val androidRawZipUrl: String,
|
||||
val imgGif: String
|
||||
) : Serializable
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.compat;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class EditorInfoCompatUtils {
|
||||
private EditorInfoCompatUtils() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
public static String imeActionName(final int imeOptions) {
|
||||
final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
|
||||
switch (actionId) {
|
||||
case EditorInfo.IME_ACTION_UNSPECIFIED:
|
||||
return "actionUnspecified";
|
||||
case EditorInfo.IME_ACTION_NONE:
|
||||
return "actionNone";
|
||||
case EditorInfo.IME_ACTION_GO:
|
||||
return "actionGo";
|
||||
case EditorInfo.IME_ACTION_SEARCH:
|
||||
return "actionSearch";
|
||||
case EditorInfo.IME_ACTION_SEND:
|
||||
return "actionSend";
|
||||
case EditorInfo.IME_ACTION_NEXT:
|
||||
return "actionNext";
|
||||
case EditorInfo.IME_ACTION_DONE:
|
||||
return "actionDone";
|
||||
case EditorInfo.IME_ACTION_PREVIOUS:
|
||||
return "actionPrevious";
|
||||
default:
|
||||
return "actionUnknown(" + actionId + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static Locale getPrimaryHintLocale(final EditorInfo editorInfo) {
|
||||
if (editorInfo == null) {
|
||||
return null;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
LocaleList localeList = editorInfo.hintLocales;
|
||||
if (localeList != null && !localeList.isEmpty())
|
||||
return localeList.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Raimondas Rimkus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.compat;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MenuItemIconColorCompat {
|
||||
/**
|
||||
* Set a menu item's icon to matching text color.
|
||||
* @param menuItem the menu item that should change colors.
|
||||
* @param actionBar target ActionBar.
|
||||
*/
|
||||
public static void matchMenuIconColor(final View view, final MenuItem menuItem, final ActionBar actionBar) {
|
||||
ArrayList<View> views = new ArrayList<>();
|
||||
|
||||
view.getRootView().findViewsWithText(views, actionBar.getTitle(), View.FIND_VIEWS_WITH_TEXT);
|
||||
if (views.size() == 1 && views.get(0) instanceof TextView) {
|
||||
int color = ((TextView) views.get(0)).getCurrentTextColor();
|
||||
setIconColor(menuItem, color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a menu item's icon to specific color.
|
||||
* @param menuItem the menu item that should change colors.
|
||||
* @param color the color that the icon should be changed to.
|
||||
*/
|
||||
private static void setIconColor(final MenuItem menuItem, final int color) {
|
||||
if (menuItem != null) {
|
||||
Drawable drawable = menuItem.getIcon();
|
||||
if (drawable != null) {
|
||||
drawable.mutate();
|
||||
drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Raimondas Rimkus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.compat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class PreferenceManagerCompat {
|
||||
public static Context getDeviceContext(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return context.createDeviceProtectedStorageContext();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public static SharedPreferences getDeviceSharedPreferences(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(getDeviceContext(context));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.compat;
|
||||
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
public class ViewOutlineProviderCompatUtils {
|
||||
private ViewOutlineProviderCompatUtils() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
public interface InsetsUpdater {
|
||||
void setInsets(final InputMethodService.Insets insets);
|
||||
}
|
||||
|
||||
private static final InsetsUpdater EMPTY_INSETS_UPDATER = new InsetsUpdater() {
|
||||
@Override
|
||||
public void setInsets(final InputMethodService.Insets insets) {}
|
||||
};
|
||||
|
||||
public static InsetsUpdater setInsetsOutlineProvider(final View view) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return EMPTY_INSETS_UPDATER;
|
||||
}
|
||||
return ViewOutlineProviderCompatUtilsLXX.setInsetsOutlineProvider(view);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Outline;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class ViewOutlineProviderCompatUtilsLXX {
|
||||
private ViewOutlineProviderCompatUtilsLXX() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
static ViewOutlineProviderCompatUtils.InsetsUpdater setInsetsOutlineProvider(final View view) {
|
||||
final InsetsOutlineProvider provider = new InsetsOutlineProvider(view);
|
||||
view.setOutlineProvider(provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static class InsetsOutlineProvider extends ViewOutlineProvider
|
||||
implements ViewOutlineProviderCompatUtils.InsetsUpdater {
|
||||
private final View mView;
|
||||
private static final int NO_DATA = -1;
|
||||
private int mLastVisibleTopInsets = NO_DATA;
|
||||
|
||||
public InsetsOutlineProvider(final View view) {
|
||||
mView = view;
|
||||
view.setOutlineProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInsets(final InputMethodService.Insets insets) {
|
||||
final int visibleTopInsets = insets.visibleTopInsets;
|
||||
if (mLastVisibleTopInsets != visibleTopInsets) {
|
||||
mLastVisibleTopInsets = visibleTopInsets;
|
||||
mView.invalidateOutline();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(final View view, final Outline outline) {
|
||||
if (mLastVisibleTopInsets == NO_DATA) {
|
||||
// Call default implementation.
|
||||
ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
|
||||
return;
|
||||
}
|
||||
// TODO: Revisit this when floating/resize keyboard is supported.
|
||||
outline.setRect(
|
||||
view.getLeft(), mLastVisibleTopInsets, view.getRight(), view.getBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.colorful.keyboard.theme.config_adpter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.chad.library.adapter4.BaseQuickAdapter;
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder;
|
||||
import com.colorful.keyboard.theme.App;
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.bean.DownloadBean;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class DownloadAdapter extends BaseQuickAdapter<DownloadBean, QuickViewHolder> {
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(@NonNull QuickViewHolder quickViewHolder, int i, @Nullable DownloadBean itemsBean) {
|
||||
ImageView imageView = quickViewHolder.getView(R.id.iv_photo);
|
||||
|
||||
String url = itemsBean.getImgUrl();
|
||||
|
||||
Glide.with(App.Companion.getApp())
|
||||
.load(url)
|
||||
.placeholder(R.drawable.placeholder)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected QuickViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
|
||||
return new QuickViewHolder(R.layout.item_info, viewGroup);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.colorful.keyboard.theme.config_adpter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.chad.library.adapter4.BaseQuickAdapter;
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder;
|
||||
import com.colorful.keyboard.theme.App;
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.model.ThemePhotoListBean;
|
||||
|
||||
public class PhotoListAdapter extends BaseQuickAdapter<ThemePhotoListBean.DataBean.SectionsBean.ItemsBean, QuickViewHolder> {
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(@NonNull QuickViewHolder quickViewHolder, int i, @Nullable ThemePhotoListBean.DataBean.SectionsBean.ItemsBean itemsBean) {
|
||||
ImageView imageView = quickViewHolder.getView(R.id.iv_photo);
|
||||
|
||||
String url = itemsBean.getThumbUrlGif();
|
||||
if(url.isEmpty()){
|
||||
url = itemsBean.getThumbUrl();
|
||||
}
|
||||
|
||||
Glide.with(App.Companion.getApp())
|
||||
.load(url)
|
||||
.placeholder(R.drawable.placeholder)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected QuickViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
|
||||
return new QuickViewHolder(R.layout.item_info, viewGroup);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.colorful.keyboard.theme.config_adpter;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.chad.library.adapter4.BaseQuickAdapter;
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder;
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.model.ThemeListModel;
|
||||
|
||||
public class TestAdapter extends BaseQuickAdapter<ThemeListModel, QuickViewHolder> {
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(@NonNull QuickViewHolder quickViewHolder, int i, @Nullable ThemeListModel s) {
|
||||
TextView textView = quickViewHolder.getView(R.id.tv_theme);
|
||||
textView.setText(s.getName());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected QuickViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) {
|
||||
return new QuickViewHolder(R.layout.item_photo_info,viewGroup);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.colorful.keyboard.theme.constant;
|
||||
|
||||
public class ConfigConstant {
|
||||
public static boolean isNeedSetting = false;
|
||||
}
|
||||
161
app/src/main/java/com/colorful/keyboard/theme/event/Event.java
Normal file
161
app/src/main/java/com/colorful/keyboard/theme/event/Event.java
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.event;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
|
||||
/**
|
||||
* Class representing a generic input event as handled by Latin IME.
|
||||
*
|
||||
* This contains information about the origin of the event, but it is generalized and should
|
||||
* represent a software keypress, hardware keypress, or d-pad move alike.
|
||||
* Very importantly, this does not necessarily result in inputting one character, or even anything
|
||||
* at all - it may be a dead key, it may be a partial input, it may be a special key on the
|
||||
* keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the
|
||||
* user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting
|
||||
* for example.
|
||||
* The combiner should figure out what to do with this.
|
||||
*/
|
||||
public class Event {
|
||||
// Should the types below be represented by separate classes instead? It would be cleaner
|
||||
// but probably a bit too much
|
||||
// An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
|
||||
final public static int EVENT_TYPE_NOT_HANDLED = 0;
|
||||
// A key press that is part of input, for example pressing an alphabetic character on a
|
||||
// hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
|
||||
// through combination.
|
||||
final public static int EVENT_TYPE_INPUT_KEYPRESS = 1;
|
||||
// A toggle event is triggered by a key that affects the previous character. An example would
|
||||
// be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
|
||||
// repeated presses.
|
||||
final public static int EVENT_TYPE_TOGGLE = 2;
|
||||
// A mode event instructs the combiner to change modes. The canonical example would be the
|
||||
// hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
|
||||
// if handled at the combiner level.
|
||||
final public static int EVENT_TYPE_MODE_KEY = 3;
|
||||
// An event corresponding to a string generated by some software process.
|
||||
final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6;
|
||||
// An event corresponding to a cursor move
|
||||
final public static int EVENT_TYPE_CURSOR_MOVE = 7;
|
||||
|
||||
// 0 is a valid code point, so we use -1 here.
|
||||
final public static int NOT_A_CODE_POINT = -1;
|
||||
// -1 is a valid key code, so we use 0 here.
|
||||
final public static int NOT_A_KEY_CODE = 0;
|
||||
|
||||
final private static int FLAG_NONE = 0;
|
||||
// This event is coming from a key repeat, software or hardware.
|
||||
final private static int FLAG_REPEAT = 0x2;
|
||||
// This event has already been consumed.
|
||||
final private static int FLAG_CONSUMED = 0x4;
|
||||
|
||||
final private int mEventType; // The type of event - one of the constants above
|
||||
// The code point associated with the event, if relevant. This is a unicode code point, and
|
||||
// has nothing to do with other representations of the key. It is only relevant if this event
|
||||
// is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
|
||||
// associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
|
||||
// it's not relevant.
|
||||
final public int mCodePoint;
|
||||
|
||||
final public CharSequence mText;
|
||||
|
||||
// The key code associated with the event, if relevant. This is relevant whenever this event
|
||||
// has been triggered by a key press, but not for a gesture for example. This has conceptually
|
||||
// no link to the code point, although keys that enter a straight code point may often set
|
||||
// this to be equal to mCodePoint for convenience. If this is not a key, this must contain
|
||||
// NOT_A_KEY_CODE.
|
||||
final public int mKeyCode;
|
||||
|
||||
// Coordinates of the touch event, if relevant. If useful, we may want to replace this with
|
||||
// a MotionEvent or something in the future. This is only relevant when the keypress is from
|
||||
// a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
|
||||
// future or some other awesome sauce.
|
||||
final public int mX;
|
||||
final public int mY;
|
||||
|
||||
// Some flags that can't go into the key code. It's a bit field of FLAG_*
|
||||
final private int mFlags;
|
||||
|
||||
// The next event, if any. Null if there is no next event yet.
|
||||
final public Event mNextEvent;
|
||||
|
||||
// This method is private - to create a new event, use one of the create* utility methods.
|
||||
private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
|
||||
final int x, final int y, final int flags,
|
||||
final Event next) {
|
||||
mEventType = type;
|
||||
mText = text;
|
||||
mCodePoint = codePoint;
|
||||
mKeyCode = keyCode;
|
||||
mX = x;
|
||||
mY = y;
|
||||
mFlags = flags;
|
||||
mNextEvent = next;
|
||||
}
|
||||
|
||||
public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
|
||||
final int x, final int y, final boolean isKeyRepeat) {
|
||||
return new Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, x, y,
|
||||
isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input event with a CharSequence. This is used by some software processes whose
|
||||
* output is a string, possibly with styling. Examples include press on a multi-character key,
|
||||
* or combination that outputs a string.
|
||||
* @param text the CharSequence associated with this event.
|
||||
* @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
|
||||
* @return an event for this text.
|
||||
*/
|
||||
public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
|
||||
return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
|
||||
FLAG_NONE, null /* next */);
|
||||
}
|
||||
|
||||
// Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys
|
||||
// that result in input like letters or space.
|
||||
public boolean isFunctionalKeyEvent() {
|
||||
// This logic may need to be refined in the future
|
||||
return NOT_A_CODE_POINT == mCodePoint;
|
||||
}
|
||||
|
||||
public boolean isKeyRepeat() {
|
||||
return 0 != (FLAG_REPEAT & mFlags);
|
||||
}
|
||||
|
||||
public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); }
|
||||
|
||||
public CharSequence getTextToCommit() {
|
||||
if (isConsumed()) {
|
||||
return ""; // A consumed event should input no text.
|
||||
}
|
||||
switch (mEventType) {
|
||||
case EVENT_TYPE_MODE_KEY:
|
||||
case EVENT_TYPE_NOT_HANDLED:
|
||||
case EVENT_TYPE_TOGGLE:
|
||||
case EVENT_TYPE_CURSOR_MOVE:
|
||||
return "";
|
||||
case EVENT_TYPE_INPUT_KEYPRESS:
|
||||
return StringUtils.newSingleCodePointString(mCodePoint);
|
||||
case EVENT_TYPE_SOFTWARE_GENERATED_STRING:
|
||||
return mText;
|
||||
}
|
||||
throw new RuntimeException("Unknown event type: " + mEventType);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.event;
|
||||
|
||||
|
||||
import com.colorful.keyboard.theme.latin.settings.SettingsValues;
|
||||
|
||||
/**
|
||||
* An object encapsulating a single transaction for input.
|
||||
*/
|
||||
public class InputTransaction {
|
||||
// UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later,
|
||||
// it's because something will change that we can't evaluate now, which means that even if we
|
||||
// re-evaluate now we'll have to do it again later. The only case where that wouldn't apply
|
||||
// would be if we needed to update now to find out the new state right away, but then we
|
||||
// can't do it with this deferred mechanism anyway.
|
||||
public static final int SHIFT_NO_UPDATE = 0;
|
||||
public static final int SHIFT_UPDATE_NOW = 1;
|
||||
public static final int SHIFT_UPDATE_LATER = 2;
|
||||
|
||||
// Initial conditions
|
||||
public final SettingsValues mSettingsValues;
|
||||
|
||||
// Outputs
|
||||
private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
|
||||
|
||||
public InputTransaction(final SettingsValues settingsValues) {
|
||||
mSettingsValues = settingsValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this transaction requires some type of shift update.
|
||||
* @param updateType What type of shift update this requires.
|
||||
*/
|
||||
public void requireShiftUpdate(final int updateType) {
|
||||
mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets what type of shift update this transaction requires.
|
||||
* @return The shift update type.
|
||||
*/
|
||||
public int getRequiredShiftUpdate() {
|
||||
return mRequiredShiftUpdate;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.colorful.keyboard.theme.firebase
|
||||
|
||||
object Constants {
|
||||
const val KEY_SHOW_AD_INTERVAL_TIME = "key_ad_show_interval"
|
||||
const val DEFAULT_SHOW_AD_INTERVAL_TIME = 1000 * 30L
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
package com.colorful.keyboard.theme.firebase
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.colorful.keyboard.theme.App
|
||||
import com.colorful.keyboard.theme.BuildConfig
|
||||
import com.colorful.keyboard.theme.myutil.AppSharedPreferences
|
||||
import com.google.firebase.remoteconfig.ConfigUpdate
|
||||
import com.google.firebase.remoteconfig.ConfigUpdateListener
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class RemoteConfig {
|
||||
|
||||
private var ctx: Context? = null
|
||||
private var mFirebaseRemoteConfig: FirebaseRemoteConfig? = null
|
||||
|
||||
//配置是否初始化成功
|
||||
private var isInit = false
|
||||
|
||||
//上次获取数据的时间
|
||||
private var lastFetchTime: Long = 0
|
||||
private val handler = MHandler(this)
|
||||
|
||||
companion object {
|
||||
const val MSG_REFRESH_CONFIG = 1
|
||||
val instance: RemoteConfig by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
RemoteConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun init(ctx: Application) {
|
||||
this.ctx = ctx
|
||||
initConfig()
|
||||
fetchConfig()
|
||||
onConfigUpdate()
|
||||
}
|
||||
|
||||
private fun initConfig() {
|
||||
var intervalTime = (60 * 10).toLong()
|
||||
//如果是开发状态,则将提取时间缩短
|
||||
if (BuildConfig.DEBUG) {
|
||||
intervalTime = (60 * 5).toLong()
|
||||
}
|
||||
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
|
||||
val configSettings =
|
||||
FirebaseRemoteConfigSettings.Builder() //默认值12小时的最短提取间隔,如果在间隔内取值,则优先取上次的结果
|
||||
.setMinimumFetchIntervalInSeconds(intervalTime).build()
|
||||
mFirebaseRemoteConfig!!.setConfigSettingsAsync(configSettings)
|
||||
}
|
||||
|
||||
private fun onConfigUpdate() {
|
||||
mFirebaseRemoteConfig!!.addOnConfigUpdateListener(object : ConfigUpdateListener {
|
||||
override fun onUpdate(configUpdate: ConfigUpdate) {
|
||||
try {
|
||||
// if (configUpdate.updatedKeys.contains(Constants.KEY_SHOULD_ENTER_MUSIC_JSON)) {
|
||||
//
|
||||
// }
|
||||
mFirebaseRemoteConfig!!.activate().addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
updateData("onConfigUpdate", mFirebaseRemoteConfig!!.all)
|
||||
}
|
||||
}
|
||||
} catch (ignore: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: FirebaseRemoteConfigException) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun fetchConfig() {
|
||||
//这里可能会抛出异常 FirebaseRemoteConfigFetchThrottledException
|
||||
try {
|
||||
mFirebaseRemoteConfig!!.fetchAndActivate().addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
isInit = true
|
||||
lastFetchTime = System.currentTimeMillis()
|
||||
updateData("fetchAndActivate", mFirebaseRemoteConfig!!.all)
|
||||
//24小时后,重新再去获取
|
||||
handler.removeMessages(MSG_REFRESH_CONFIG)
|
||||
handler.sendEmptyMessageDelayed(
|
||||
MSG_REFRESH_CONFIG, (1000 * 60 * 60 * 24).toLong()
|
||||
)
|
||||
} else {
|
||||
//这里需要重新再去获取
|
||||
handler.removeMessages(MSG_REFRESH_CONFIG)
|
||||
handler.sendEmptyMessageDelayed(MSG_REFRESH_CONFIG, (1000 * 60 * 15).toLong())
|
||||
}
|
||||
}
|
||||
} catch (ignore: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(from: String, all: Map<String, FirebaseRemoteConfigValue>) {
|
||||
val appSharedPreferences = AppSharedPreferences(App.app)
|
||||
for ((key, value) in all) {
|
||||
try {
|
||||
Log.d("colorful-config","from->$from key->$key value->${value.asString()}")
|
||||
if (TextUtils.equals(Constants.KEY_SHOW_AD_INTERVAL_TIME, key)) {
|
||||
val t = value.asString()
|
||||
appSharedPreferences.setShowAdIntervalTime(t.toLong())
|
||||
}
|
||||
} catch (ignore: Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MHandler(remoteConfig: RemoteConfig) : Handler() {
|
||||
private val weakReference: WeakReference<RemoteConfig> = WeakReference(remoteConfig)
|
||||
|
||||
override fun handleMessage(msg: Message) {
|
||||
super.handleMessage(msg)
|
||||
val remoteConfig = weakReference.get()
|
||||
if (remoteConfig?.ctx != null) {
|
||||
if (msg.what == MSG_REFRESH_CONFIG) {
|
||||
remoteConfig.fetchConfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package com.colorful.keyboard.theme.fragment
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.colorful.keyboard.theme.ColorfulDetailsActivity
|
||||
import com.colorful.keyboard.theme.R
|
||||
import com.colorful.keyboard.theme.config_adpter.PhotoListAdapter
|
||||
import com.colorful.keyboard.theme.model.ThemePhotoListBean
|
||||
import com.colorful.keyboard.theme.net.ApiCallbackCommon
|
||||
import com.colorful.keyboard.theme.net.ApiException
|
||||
import com.colorful.keyboard.theme.net.BaseInitApiRequest
|
||||
|
||||
class KeyboardListFragment : Fragment() {
|
||||
private var swipeRefreshLayout: SwipeRefreshLayout? = null
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private var catid: String? = ""
|
||||
private var photoListAdapter: PhotoListAdapter? = null
|
||||
private var loadingLayout: LinearLayout? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (arguments != null) {
|
||||
val arguments = arguments
|
||||
catid = arguments!!.getString("id")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val inflate = inflater.inflate(R.layout.fragment_photo_list, container, false)
|
||||
swipeRefreshLayout = inflate.findViewById(R.id.swiper)
|
||||
recyclerView = inflate.findViewById(R.id.rv_photo_ist)
|
||||
loadingLayout = inflate.findViewById(R.id.loadingLayout)
|
||||
return inflate
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
loadingLayout?.visibility = View.VISIBLE
|
||||
//recyclerView.setAdapter(new PhotoListAdapter());
|
||||
swipeRefreshLayout!!.isEnabled = false
|
||||
photoListAdapter = PhotoListAdapter()
|
||||
recyclerView!!.layoutManager = GridLayoutManager(activity, 3)
|
||||
recyclerView!!.adapter = photoListAdapter
|
||||
photoListAdapter!!.setOnItemClickListener { baseQuickAdapter, view, i ->
|
||||
startActivity(
|
||||
Intent(activity, ColorfulDetailsActivity::class.java)
|
||||
.putExtra("title", baseQuickAdapter.getItem(i)!!.title)
|
||||
.putExtra("key", baseQuickAdapter.getItem(i)!!.key).putExtra(
|
||||
"url", baseQuickAdapter.getItem(i)!!
|
||||
.thumbUrl
|
||||
)
|
||||
.putExtra("",baseQuickAdapter.getItem(i)?.thumbUrlGif)
|
||||
)
|
||||
}
|
||||
info
|
||||
}
|
||||
|
||||
private val info: Unit
|
||||
private get() {
|
||||
val hashMap = HashMap<String, String>()
|
||||
hashMap["offset"] = "0"
|
||||
hashMap["fetch_size"] = "100"
|
||||
hashMap["sign"] = "a28aadc61c76c754944f6ddb48962c9c"
|
||||
BaseInitApiRequest.getNetData(BaseInitApiRequest.initHHYApiStores()
|
||||
.getThemeList(catid, hashMap),
|
||||
object : ApiCallbackCommon<ThemePhotoListBean?>(activity, false) {
|
||||
|
||||
|
||||
override fun onSuccess(model: ThemePhotoListBean?) {
|
||||
photoListAdapter!!.submitList(model?.data?.sections!![0]?.items)
|
||||
|
||||
loadingLayout?.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onFailure(ex: ApiException) {
|
||||
loadingLayout?.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
909
app/src/main/java/com/colorful/keyboard/theme/keyboard/Key.java
Normal file
909
app/src/main/java/com/colorful/keyboard/theme/keyboard/Key.java
Normal file
@ -0,0 +1,909 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_OUTPUT_TEXT;
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_SHIFT;
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_SWITCH_ALPHA_SYMBOL;
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_UNSPECIFIED;
|
||||
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyDrawParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeySpecParser;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyStyle;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyVisualAttributes;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardIconsSet;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardRow;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.MoreKeySpec;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* Class for describing the position and characteristics of a single key in the keyboard.
|
||||
*/
|
||||
public class Key implements Comparable<Key> {
|
||||
/**
|
||||
* The key code (unicode or custom code) that this key generates.
|
||||
*/
|
||||
private final int mCode;
|
||||
|
||||
/** Label to display */
|
||||
private final String mLabel;
|
||||
/** Hint label to display on the key in conjunction with the label */
|
||||
private final String mHintLabel;
|
||||
/** Flags of the label */
|
||||
private final int mLabelFlags;
|
||||
private static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02;
|
||||
private static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04;
|
||||
private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08;
|
||||
// Font typeface specification.
|
||||
private static final int LABEL_FLAGS_FONT_MASK = 0x30;
|
||||
private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
|
||||
private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
|
||||
private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
|
||||
// Start of key text ratio enum values
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
|
||||
// End of key text ratio mask enum values
|
||||
private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
|
||||
private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
|
||||
// The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
|
||||
// and autoYScale bit is off, the key label may be shrunk only for X-direction.
|
||||
// If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
|
||||
private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
|
||||
private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
|
||||
private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
|
||||
| LABEL_FLAGS_AUTO_Y_SCALE;
|
||||
private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
|
||||
private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
|
||||
private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
|
||||
private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
|
||||
private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
|
||||
private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
|
||||
|
||||
/** Icon to display instead of a label. Icon takes precedence over a label */
|
||||
private final int mIconId;
|
||||
|
||||
/** Width of the key, excluding the padding */
|
||||
private final int mWidth;
|
||||
/** Height of the key, excluding the padding */
|
||||
private final int mHeight;
|
||||
/** Exact theoretical width of the key, excluding the padding */
|
||||
private final float mDefinedWidth;
|
||||
/** Exact theoretical height of the key, excluding the padding */
|
||||
private final float mDefinedHeight;
|
||||
/** X coordinate of the top-left corner of the key in the keyboard layout, excluding the
|
||||
* padding. */
|
||||
private final int mX;
|
||||
/** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the
|
||||
* padding. */
|
||||
private final int mY;
|
||||
/** Hit bounding box of the key */
|
||||
private final Rect mHitbox = new Rect();
|
||||
|
||||
/** More keys. It is guaranteed that this is null or an array of one or more elements */
|
||||
private final MoreKeySpec[] mMoreKeys;
|
||||
/** More keys column number and flags */
|
||||
private final int mMoreKeysColumnAndFlags;
|
||||
private static final int MORE_KEYS_COLUMN_NUMBER_MASK = 0x000000ff;
|
||||
// If this flag is specified, more keys keyboard should have the specified number of columns.
|
||||
// Otherwise more keys keyboard should have less than or equal to the specified maximum number
|
||||
// of columns.
|
||||
private static final int MORE_KEYS_FLAGS_FIXED_COLUMN = 0x00000100;
|
||||
// If this flag is specified, the order of more keys is determined by the order in the more
|
||||
// keys' specification. Otherwise the order of more keys is automatically determined.
|
||||
private static final int MORE_KEYS_FLAGS_FIXED_ORDER = 0x00000200;
|
||||
private static final int MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER = 0;
|
||||
private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER =
|
||||
MORE_KEYS_FLAGS_FIXED_COLUMN;
|
||||
private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER =
|
||||
(MORE_KEYS_FLAGS_FIXED_COLUMN | MORE_KEYS_FLAGS_FIXED_ORDER);
|
||||
private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
|
||||
private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
|
||||
// TODO: Rename these specifiers to !autoOrder! and !fixedOrder! respectively.
|
||||
private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
|
||||
private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
|
||||
private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
|
||||
private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
|
||||
|
||||
/** Background type that represents different key background visual than normal one. */
|
||||
private final int mBackgroundType;
|
||||
public static final int BACKGROUND_TYPE_EMPTY = 0;
|
||||
public static final int BACKGROUND_TYPE_NORMAL = 1;
|
||||
public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
|
||||
public static final int BACKGROUND_TYPE_ACTION = 5;
|
||||
public static final int BACKGROUND_TYPE_SPACEBAR = 6;
|
||||
|
||||
private final int mActionFlags;
|
||||
private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
|
||||
private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
|
||||
private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
|
||||
private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
|
||||
|
||||
private final KeyVisualAttributes mKeyVisualAttributes;
|
||||
private final OptionalAttributes mOptionalAttributes;
|
||||
|
||||
private static final class OptionalAttributes {
|
||||
/** Text to output when pressed. This can be multiple characters, like ".com" */
|
||||
public final String mOutputText;
|
||||
public final int mAltCode;
|
||||
|
||||
private OptionalAttributes(final String outputText, final int altCode) {
|
||||
mOutputText = outputText;
|
||||
mAltCode = altCode;
|
||||
}
|
||||
|
||||
public static OptionalAttributes newInstance(final String outputText, final int altCode) {
|
||||
if (outputText == null && altCode == CODE_UNSPECIFIED) {
|
||||
return null;
|
||||
}
|
||||
return new OptionalAttributes(outputText, altCode);
|
||||
}
|
||||
}
|
||||
|
||||
private final int mHashCode;
|
||||
|
||||
/** The current pressed state of this key */
|
||||
private boolean mPressed;
|
||||
|
||||
/**
|
||||
* Constructor for a key on <code>MoreKeyKeyboard</code>.
|
||||
*/
|
||||
public Key(final String label, final int iconId, final int code, final String outputText,
|
||||
final String hintLabel, final int labelFlags, final int backgroundType,
|
||||
final float x, final float y, final float width, final float height,
|
||||
final float leftPadding, final float rightPadding, final float topPadding,
|
||||
final float bottomPadding) {
|
||||
mHitbox.set(Math.round(x - leftPadding), Math.round(y - topPadding),
|
||||
Math.round(x + width + rightPadding), Math.round(y + height + bottomPadding));
|
||||
mX = Math.round(x);
|
||||
mY = Math.round(y);
|
||||
mWidth = Math.round(x + width) - mX;
|
||||
mHeight = Math.round(y + height) - mY;
|
||||
mDefinedWidth = width;
|
||||
mDefinedHeight = height;
|
||||
mHintLabel = hintLabel;
|
||||
mLabelFlags = labelFlags;
|
||||
mBackgroundType = backgroundType;
|
||||
// TODO: Pass keyActionFlags as an argument.
|
||||
mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW;
|
||||
mMoreKeys = null;
|
||||
mMoreKeysColumnAndFlags = 0;
|
||||
mLabel = label;
|
||||
mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED);
|
||||
mCode = code;
|
||||
mIconId = iconId;
|
||||
mKeyVisualAttributes = null;
|
||||
|
||||
mHashCode = computeHashCode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a key with the given top-left coordinate and extract its attributes from a key
|
||||
* specification string, Key attribute array, key style, and etc.
|
||||
*
|
||||
* @param keySpec the key specification.
|
||||
* @param keyAttr the Key XML attributes array.
|
||||
* @param style the {@link KeyStyle} of this key.
|
||||
* @param params the keyboard building parameters.
|
||||
* @param row the row that this key belongs to. row's x-coordinate will be the right edge of
|
||||
* this key.
|
||||
*/
|
||||
public Key(final String keySpec, final TypedArray keyAttr,
|
||||
final KeyStyle style, final KeyboardParams params,
|
||||
final KeyboardRow row) {
|
||||
// Update the row to work with the new key
|
||||
row.setCurrentKey(keyAttr, isSpacer());
|
||||
|
||||
mDefinedWidth = row.getKeyWidth();
|
||||
mDefinedHeight = row.getKeyHeight();
|
||||
|
||||
final float keyLeft = row.getKeyX();
|
||||
final float keyTop = row.getKeyY();
|
||||
final float keyRight = keyLeft + mDefinedWidth;
|
||||
final float keyBottom = keyTop + mDefinedHeight;
|
||||
|
||||
final float leftPadding = row.getKeyLeftPadding();
|
||||
final float topPadding = row.getKeyTopPadding();
|
||||
final float rightPadding = row.getKeyRightPadding();
|
||||
final float bottomPadding = row.getKeyBottomPadding();
|
||||
|
||||
mHitbox.set(Math.round(keyLeft - leftPadding), Math.round(keyTop - topPadding),
|
||||
Math.round(keyRight + rightPadding), Math.round(keyBottom + bottomPadding));
|
||||
mX = Math.round(keyLeft);
|
||||
mY = Math.round(keyTop);
|
||||
mWidth = Math.round(keyRight) - mX;
|
||||
mHeight = Math.round(keyBottom) - mY;
|
||||
|
||||
mBackgroundType = style.getInt(keyAttr,
|
||||
R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
|
||||
|
||||
mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
|
||||
| row.getDefaultKeyLabelFlags();
|
||||
final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
|
||||
final Locale localeForUpcasing = params.mId.getLocale();
|
||||
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
|
||||
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
|
||||
|
||||
// Get maximum column order number and set a relevant mode value.
|
||||
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER
|
||||
| style.getInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn,
|
||||
params.mMaxMoreKeysKeyboardColumn);
|
||||
int value;
|
||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
|
||||
// Override with fixed column order number and set a relevant mode value.
|
||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER
|
||||
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||
}
|
||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
|
||||
// Override with fixed column order number and set a relevant mode value.
|
||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER
|
||||
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||
}
|
||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
|
||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
|
||||
}
|
||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
|
||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
|
||||
}
|
||||
mMoreKeysColumnAndFlags = moreKeysColumnAndFlags;
|
||||
|
||||
final String[] additionalMoreKeys;
|
||||
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
|
||||
additionalMoreKeys = null;
|
||||
} else {
|
||||
additionalMoreKeys = style.getStringArray(keyAttr,
|
||||
R.styleable.Keyboard_Key_additionalMoreKeys);
|
||||
}
|
||||
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
|
||||
if (moreKeys != null) {
|
||||
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
|
||||
mMoreKeys = new MoreKeySpec[moreKeys.length];
|
||||
for (int i = 0; i < moreKeys.length; i++) {
|
||||
mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpcase, localeForUpcasing);
|
||||
}
|
||||
} else {
|
||||
mMoreKeys = null;
|
||||
}
|
||||
mActionFlags = actionFlags;
|
||||
|
||||
mIconId = KeySpecParser.getIconId(keySpec);
|
||||
|
||||
final int code = KeySpecParser.getCode(keySpec);
|
||||
if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
|
||||
mLabel = params.mId.mCustomActionLabel;
|
||||
} else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||
// This is a workaround to have a key that has a supplementary code point in its label.
|
||||
// Because we can put a string in resource neither as a XML entity of a supplementary
|
||||
// code point nor as a surrogate pair.
|
||||
mLabel = new StringBuilder().appendCodePoint(code).toString();
|
||||
} else {
|
||||
final String label = KeySpecParser.getLabel(keySpec);
|
||||
mLabel = needsToUpcase
|
||||
? StringUtils.toTitleCaseOfKeyLabel(label, localeForUpcasing)
|
||||
: label;
|
||||
}
|
||||
if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
|
||||
mHintLabel = null;
|
||||
} else {
|
||||
final String hintLabel = style.getString(
|
||||
keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
|
||||
mHintLabel = needsToUpcase
|
||||
? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing)
|
||||
: hintLabel;
|
||||
}
|
||||
String outputText = KeySpecParser.getOutputText(keySpec);
|
||||
if (needsToUpcase) {
|
||||
outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing);
|
||||
}
|
||||
// Choose the first letter of the label as primary code if not specified.
|
||||
if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
|
||||
&& !TextUtils.isEmpty(mLabel)) {
|
||||
if (StringUtils.codePointCount(mLabel) == 1) {
|
||||
// Use the first letter of the hint label if shiftedLetterActivated flag is
|
||||
// specified.
|
||||
if (hasShiftedLetterHint() && isShiftedLetterActivated()) {
|
||||
mCode = mHintLabel.codePointAt(0);
|
||||
} else {
|
||||
mCode = mLabel.codePointAt(0);
|
||||
}
|
||||
} else {
|
||||
// In some locale and case, the character might be represented by multiple code
|
||||
// points, such as upper case Eszett of German alphabet.
|
||||
outputText = mLabel;
|
||||
mCode = CODE_OUTPUT_TEXT;
|
||||
}
|
||||
} else if (code == CODE_UNSPECIFIED && outputText != null) {
|
||||
if (StringUtils.codePointCount(outputText) == 1) {
|
||||
mCode = outputText.codePointAt(0);
|
||||
outputText = null;
|
||||
} else {
|
||||
mCode = CODE_OUTPUT_TEXT;
|
||||
}
|
||||
} else {
|
||||
mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing)
|
||||
: code;
|
||||
}
|
||||
final int altCodeInAttr = KeySpecParser.parseCode(
|
||||
style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
|
||||
final int altCode = needsToUpcase
|
||||
? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing)
|
||||
: altCodeInAttr;
|
||||
mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode);
|
||||
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
|
||||
mHashCode = computeHashCode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for DynamicGridKeyboard.GridKey.
|
||||
*
|
||||
* @param key the original key.
|
||||
*/
|
||||
protected Key(final Key key) {
|
||||
this(key, key.mMoreKeys);
|
||||
}
|
||||
|
||||
private Key(final Key key,final MoreKeySpec[] moreKeys) {
|
||||
// Final attributes.
|
||||
mCode = key.mCode;
|
||||
mLabel = key.mLabel;
|
||||
mHintLabel = key.mHintLabel;
|
||||
mLabelFlags = key.mLabelFlags;
|
||||
mIconId = key.mIconId;
|
||||
mWidth = key.mWidth;
|
||||
mHeight = key.mHeight;
|
||||
mDefinedWidth = key.mDefinedWidth;
|
||||
mDefinedHeight = key.mDefinedHeight;
|
||||
mX = key.mX;
|
||||
mY = key.mY;
|
||||
mHitbox.set(key.mHitbox);
|
||||
mMoreKeys = moreKeys;
|
||||
mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
|
||||
mBackgroundType = key.mBackgroundType;
|
||||
mActionFlags = key.mActionFlags;
|
||||
mKeyVisualAttributes = key.mKeyVisualAttributes;
|
||||
mOptionalAttributes = key.mOptionalAttributes;
|
||||
mHashCode = key.mHashCode;
|
||||
// Key state.
|
||||
mPressed = key.mPressed;
|
||||
}
|
||||
|
||||
public static Key removeRedundantMoreKeys(final Key key,
|
||||
final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout) {
|
||||
final MoreKeySpec[] moreKeys = key.getMoreKeys();
|
||||
final MoreKeySpec[] filteredMoreKeys = MoreKeySpec.removeRedundantMoreKeys(
|
||||
moreKeys, lettersOnBaseLayout);
|
||||
return (filteredMoreKeys == moreKeys) ? key : new Key(key, filteredMoreKeys);
|
||||
}
|
||||
|
||||
private static boolean needsToUpcase(final int labelFlags, final int keyboardElementId) {
|
||||
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
|
||||
switch (keyboardElementId) {
|
||||
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
|
||||
case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
|
||||
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int computeHashCode(final Key key) {
|
||||
return Arrays.hashCode(new Object[] {
|
||||
key.mX,
|
||||
key.mY,
|
||||
key.mWidth,
|
||||
key.mHeight,
|
||||
key.mCode,
|
||||
key.mLabel,
|
||||
key.mHintLabel,
|
||||
key.mIconId,
|
||||
key.mBackgroundType,
|
||||
Arrays.hashCode(key.mMoreKeys),
|
||||
key.getOutputText(),
|
||||
key.mActionFlags,
|
||||
key.mLabelFlags,
|
||||
// Key can be distinguishable without the following members.
|
||||
// key.mOptionalAttributes.mAltCode,
|
||||
// key.mOptionalAttributes.mDisabledIconId,
|
||||
// key.mOptionalAttributes.mPreviewIconId,
|
||||
// key.mMaxMoreKeysColumn,
|
||||
// key.mDefinedHeight,
|
||||
// key.mDefinedWidth,
|
||||
});
|
||||
}
|
||||
|
||||
private boolean equalsInternal(final Key o) {
|
||||
if (this == o) return true;
|
||||
return o.mX == mX
|
||||
&& o.mY == mY
|
||||
&& o.mWidth == mWidth
|
||||
&& o.mHeight == mHeight
|
||||
&& o.mCode == mCode
|
||||
&& TextUtils.equals(o.mLabel, mLabel)
|
||||
&& TextUtils.equals(o.mHintLabel, mHintLabel)
|
||||
&& o.mIconId == mIconId
|
||||
&& o.mBackgroundType == mBackgroundType
|
||||
&& Arrays.equals(o.mMoreKeys, mMoreKeys)
|
||||
&& TextUtils.equals(o.getOutputText(), getOutputText())
|
||||
&& o.mActionFlags == mActionFlags
|
||||
&& o.mLabelFlags == mLabelFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Key o) {
|
||||
if (equalsInternal(o)) return 0;
|
||||
if (mHashCode > o.mHashCode) return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
return o instanceof Key && equalsInternal((Key)o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight();
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
final int code = getCode();
|
||||
if (code == CODE_OUTPUT_TEXT) {
|
||||
return getOutputText();
|
||||
}
|
||||
return Constants.printableCode(code);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String getHintLabel() {
|
||||
return mHintLabel;
|
||||
}
|
||||
|
||||
public MoreKeySpec[] getMoreKeys() {
|
||||
return mMoreKeys;
|
||||
}
|
||||
|
||||
public void setHitboxRightEdge(final int right) {
|
||||
mHitbox.right = right;
|
||||
}
|
||||
|
||||
public final boolean isSpacer() {
|
||||
return this instanceof Spacer;
|
||||
}
|
||||
|
||||
public final boolean isActionKey() {
|
||||
return mBackgroundType == BACKGROUND_TYPE_ACTION;
|
||||
}
|
||||
|
||||
public final boolean isShift() {
|
||||
return mCode == CODE_SHIFT;
|
||||
}
|
||||
|
||||
public final boolean isModifier() {
|
||||
return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
|
||||
}
|
||||
|
||||
public final boolean isRepeatable() {
|
||||
return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
|
||||
}
|
||||
|
||||
public final boolean noKeyPreview() {
|
||||
return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
|
||||
}
|
||||
|
||||
public final boolean altCodeWhileTyping() {
|
||||
return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
|
||||
}
|
||||
|
||||
public final boolean isLongPressEnabled() {
|
||||
// We need not start long press timer on the key which has activated shifted letter.
|
||||
return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
|
||||
&& (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
|
||||
}
|
||||
|
||||
public KeyVisualAttributes getVisualAttributes() {
|
||||
return mKeyVisualAttributes;
|
||||
}
|
||||
|
||||
public final Typeface selectTypeface(final KeyDrawParams params) {
|
||||
switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) {
|
||||
case LABEL_FLAGS_FONT_NORMAL:
|
||||
return Typeface.DEFAULT;
|
||||
case LABEL_FLAGS_FONT_MONO_SPACE:
|
||||
return Typeface.MONOSPACE;
|
||||
case LABEL_FLAGS_FONT_DEFAULT:
|
||||
default:
|
||||
// The type-face is specified by keyTypeface attribute.
|
||||
return params.mTypeface;
|
||||
}
|
||||
}
|
||||
|
||||
public final int selectTextSize(final KeyDrawParams params) {
|
||||
switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
|
||||
case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
|
||||
return params.mLetterSize;
|
||||
case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
|
||||
return params.mLargeLetterSize;
|
||||
case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
|
||||
return params.mLabelSize;
|
||||
case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
|
||||
return params.mHintLabelSize;
|
||||
default: // No follow key ratio flag specified.
|
||||
return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
|
||||
}
|
||||
}
|
||||
|
||||
public final int selectTextColor(final KeyDrawParams params) {
|
||||
if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
|
||||
return params.mFunctionalTextColor;
|
||||
}
|
||||
return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
|
||||
}
|
||||
|
||||
public final int selectHintTextSize(final KeyDrawParams params) {
|
||||
if (hasHintLabel()) {
|
||||
return params.mHintLabelSize;
|
||||
}
|
||||
if (hasShiftedLetterHint()) {
|
||||
return params.mShiftedLetterHintSize;
|
||||
}
|
||||
return params.mHintLetterSize;
|
||||
}
|
||||
|
||||
public final int selectHintTextColor(final KeyDrawParams params) {
|
||||
if (hasHintLabel()) {
|
||||
return params.mHintLabelColor;
|
||||
}
|
||||
if (hasShiftedLetterHint()) {
|
||||
return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
|
||||
: params.mShiftedLetterHintInactivatedColor;
|
||||
}
|
||||
return params.mHintLetterColor;
|
||||
}
|
||||
|
||||
public final String getPreviewLabel() {
|
||||
return isShiftedLetterActivated() ? mHintLabel : mLabel;
|
||||
}
|
||||
|
||||
private boolean previewHasLetterSize() {
|
||||
return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0
|
||||
|| StringUtils.codePointCount(getPreviewLabel()) == 1;
|
||||
}
|
||||
|
||||
public final int selectPreviewTextSize(final KeyDrawParams params) {
|
||||
if (previewHasLetterSize()) {
|
||||
return params.mPreviewTextSize;
|
||||
}
|
||||
return params.mLetterSize;
|
||||
}
|
||||
|
||||
public Typeface selectPreviewTypeface(final KeyDrawParams params) {
|
||||
if (previewHasLetterSize()) {
|
||||
return selectTypeface(params);
|
||||
}
|
||||
return Typeface.DEFAULT_BOLD;
|
||||
}
|
||||
|
||||
public final boolean isAlignHintLabelToBottom(final int defaultFlags) {
|
||||
return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM) != 0;
|
||||
}
|
||||
|
||||
public final boolean isAlignIconToBottom() {
|
||||
return (mLabelFlags & LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM) != 0;
|
||||
}
|
||||
|
||||
public final boolean isAlignLabelOffCenter() {
|
||||
return (mLabelFlags & LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER) != 0;
|
||||
}
|
||||
|
||||
public final boolean hasShiftedLetterHint() {
|
||||
return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0
|
||||
&& !TextUtils.isEmpty(mHintLabel);
|
||||
}
|
||||
|
||||
public final boolean hasHintLabel() {
|
||||
return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
|
||||
}
|
||||
|
||||
public final boolean needsAutoXScale() {
|
||||
return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
|
||||
}
|
||||
|
||||
public final boolean needsAutoScale() {
|
||||
return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
|
||||
}
|
||||
|
||||
private final boolean isShiftedLetterActivated() {
|
||||
return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
|
||||
&& !TextUtils.isEmpty(mHintLabel);
|
||||
}
|
||||
|
||||
public final int getMoreKeysColumnNumber() {
|
||||
return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_NUMBER_MASK;
|
||||
}
|
||||
|
||||
public final boolean isMoreKeysFixedColumn() {
|
||||
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN) != 0;
|
||||
}
|
||||
|
||||
public final boolean isMoreKeysFixedOrder() {
|
||||
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_ORDER) != 0;
|
||||
}
|
||||
|
||||
public final boolean hasLabelsInMoreKeys() {
|
||||
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
|
||||
}
|
||||
|
||||
public final int getMoreKeyLabelFlags() {
|
||||
final int labelSizeFlag = hasLabelsInMoreKeys()
|
||||
? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
|
||||
: LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
|
||||
return labelSizeFlag | LABEL_FLAGS_AUTO_X_SCALE;
|
||||
}
|
||||
|
||||
public final boolean hasNoPanelAutoMoreKey() {
|
||||
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
|
||||
}
|
||||
|
||||
public final String getOutputText() {
|
||||
final OptionalAttributes attrs = mOptionalAttributes;
|
||||
return (attrs != null) ? attrs.mOutputText : null;
|
||||
}
|
||||
|
||||
public final int getAltCode() {
|
||||
final OptionalAttributes attrs = mOptionalAttributes;
|
||||
return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
|
||||
}
|
||||
|
||||
public int getIconId() {
|
||||
return mIconId;
|
||||
}
|
||||
|
||||
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
|
||||
final Drawable icon = iconSet.getIconDrawable(getIconId());
|
||||
if (icon != null) {
|
||||
icon.setAlpha(alpha);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
|
||||
return iconSet.getIconDrawable(getIconId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the key in pixels, excluding the padding.
|
||||
* @return The width of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the key in pixels, excluding the padding.
|
||||
* @return The height of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the theoretical width of the key in pixels, excluding the padding. This is the exact
|
||||
* width that the key was defined to be, but this will likely differ from the actual drawn width
|
||||
* because the normal (drawn/functional) width was determined by rounding the left and right
|
||||
* edge to fit evenly in a pixel.
|
||||
* @return The defined width of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public float getDefinedWidth() {
|
||||
return mDefinedWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the theoretical height of the key in pixels, excluding the padding. This is the exact
|
||||
* height that the key was defined to be, but this will likely differ from the actual drawn
|
||||
* height because the normal (drawn/functional) width was determined by rounding the top and
|
||||
* bottom edge to fit evenly in a pixel.
|
||||
* @return The defined width of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public float getDefinedHeight() {
|
||||
return mDefinedHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the x-coordinate of the top-left corner of the key in pixels, excluding the padding.
|
||||
* @return The x-coordinate of the top-left corner of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public int getX() {
|
||||
return mX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the y-coordinate of the top-left corner of the key in pixels, excluding the padding.
|
||||
* @return The y-coordinate of the top-left corner of the key in pixels, excluding the padding.
|
||||
*/
|
||||
public int getY() {
|
||||
return mY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of padding for the hitbox above the key's visible position.
|
||||
* @return The hitbox padding above the key.
|
||||
*/
|
||||
public int getTopPadding() {
|
||||
return mY - mHitbox.top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of padding for the hitbox below the key's visible position.
|
||||
* @return The hitbox padding below the key.
|
||||
*/
|
||||
public int getBottomPadding() {
|
||||
return mHitbox.bottom - mY - mHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of padding for the hitbox to the left of the key's visible position.
|
||||
* @return The hitbox padding to the left of the key.
|
||||
*/
|
||||
public int getLeftPadding() {
|
||||
return mX - mHitbox.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of padding for the hitbox to the right of the key's visible position.
|
||||
* @return The hitbox padding to the right of the key.
|
||||
*/
|
||||
public int getRightPadding() {
|
||||
return mHitbox.right - mX - mWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the key that it has been pressed, in case it needs to change its appearance or
|
||||
* state.
|
||||
* @see #onReleased()
|
||||
*/
|
||||
public void onPressed() {
|
||||
mPressed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the key that it has been released, in case it needs to change its appearance or
|
||||
* state.
|
||||
* @see #onPressed()
|
||||
*/
|
||||
public void onReleased() {
|
||||
mPressed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a point falls on this key.
|
||||
* @param x the x-coordinate of the point
|
||||
* @param y the y-coordinate of the point
|
||||
* @return whether or not the point falls on the key. This generally includes all points
|
||||
* between the key and the keyboard edge for keys attached to an edge and all points between
|
||||
* the key and halfway to adjacent keys.
|
||||
*/
|
||||
public boolean isOnKey(final int x, final int y) {
|
||||
return mHitbox.contains(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square of the distance to the nearest clickable edge of the key and the given
|
||||
* point.
|
||||
* @param x the x-coordinate of the point
|
||||
* @param y the y-coordinate of the point
|
||||
* @return the square of the distance of the point from the nearest edge of the key
|
||||
*/
|
||||
public int squaredDistanceToHitboxEdge(final int x, final int y) {
|
||||
final int left = mHitbox.left;
|
||||
// The hit box right is exclusive
|
||||
final int right = mHitbox.right - 1;
|
||||
final int top = mHitbox.top;
|
||||
// The hit box bottom is exclusive
|
||||
final int bottom = mHitbox.bottom - 1;
|
||||
final int edgeX = x < left ? left : Math.min(x, right);
|
||||
final int edgeY = y < top ? top : Math.min(y, bottom);
|
||||
final int dx = x - edgeX;
|
||||
final int dy = y - edgeY;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
static class KeyBackgroundState {
|
||||
private final int[] mReleasedState;
|
||||
private final int[] mPressedState;
|
||||
|
||||
private KeyBackgroundState(final int ... attrs) {
|
||||
mReleasedState = attrs;
|
||||
mPressedState = Arrays.copyOf(attrs, attrs.length + 1);
|
||||
mPressedState[attrs.length] = android.R.attr.state_pressed;
|
||||
}
|
||||
|
||||
public int[] getState(final boolean pressed) {
|
||||
return pressed ? mPressedState : mReleasedState;
|
||||
}
|
||||
|
||||
public static final KeyBackgroundState[] STATES = {
|
||||
// 0: BACKGROUND_TYPE_EMPTY
|
||||
new KeyBackgroundState(android.R.attr.state_empty),
|
||||
// 1: BACKGROUND_TYPE_NORMAL
|
||||
new KeyBackgroundState(),
|
||||
// 2: BACKGROUND_TYPE_FUNCTIONAL
|
||||
new KeyBackgroundState(),
|
||||
// 3: BACKGROUND_TYPE_STICKY_OFF
|
||||
new KeyBackgroundState(android.R.attr.state_checkable),
|
||||
// 4: BACKGROUND_TYPE_STICKY_ON
|
||||
new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked),
|
||||
// 5: BACKGROUND_TYPE_ACTION
|
||||
new KeyBackgroundState(android.R.attr.state_active),
|
||||
// 6: BACKGROUND_TYPE_SPACEBAR
|
||||
new KeyBackgroundState(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the background drawable for the key, based on the current state and type of the key.
|
||||
* @return the background drawable of the key.
|
||||
* @see android.graphics.drawable.StateListDrawable#setState(int[])
|
||||
*/
|
||||
public final Drawable selectBackgroundDrawable(final Drawable keyBackground,
|
||||
final Drawable functionalKeyBackground,
|
||||
final Drawable spacebarBackground) {
|
||||
final Drawable background;
|
||||
if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
|
||||
background = functionalKeyBackground;
|
||||
} else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) {
|
||||
background = spacebarBackground;
|
||||
} else {
|
||||
background = keyBackground;
|
||||
}
|
||||
final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed);
|
||||
background.setState(state);
|
||||
return background;
|
||||
}
|
||||
|
||||
public static class Spacer extends Key {
|
||||
public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle,
|
||||
final KeyboardParams params, final KeyboardRow row) {
|
||||
super(null /* keySpec */, keyAttr, keyStyle, params, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
/**
|
||||
* This class handles key detection.
|
||||
*/
|
||||
public class KeyDetector {
|
||||
private final int mKeyHysteresisDistanceSquared;
|
||||
private final int mKeyHysteresisDistanceForSlidingModifierSquared;
|
||||
|
||||
private Keyboard mKeyboard;
|
||||
private int mCorrectionX;
|
||||
private int mCorrectionY;
|
||||
|
||||
public KeyDetector() {
|
||||
this(0.0f /* keyHysteresisDistance */, 0.0f /* keyHysteresisDistanceForSlidingModifier */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key detection object constructor with key hysteresis distances.
|
||||
*
|
||||
* @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
|
||||
* movement will not be handled as meaningful movement. The unit is pixel.
|
||||
* @param keyHysteresisDistanceForSlidingModifier the same parameter for sliding input that
|
||||
* starts from a modifier key such as shift and symbols key.
|
||||
*/
|
||||
public KeyDetector(final float keyHysteresisDistance,
|
||||
final float keyHysteresisDistanceForSlidingModifier) {
|
||||
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
|
||||
mKeyHysteresisDistanceForSlidingModifierSquared = (int)(
|
||||
keyHysteresisDistanceForSlidingModifier * keyHysteresisDistanceForSlidingModifier);
|
||||
}
|
||||
|
||||
public void setKeyboard(final Keyboard keyboard, final float correctionX,
|
||||
final float correctionY) {
|
||||
if (keyboard == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
mCorrectionX = (int)correctionX;
|
||||
mCorrectionY = (int)correctionY;
|
||||
mKeyboard = keyboard;
|
||||
}
|
||||
|
||||
public int getKeyHysteresisDistanceSquared(final boolean isSlidingFromModifier) {
|
||||
return isSlidingFromModifier
|
||||
? mKeyHysteresisDistanceForSlidingModifierSquared : mKeyHysteresisDistanceSquared;
|
||||
}
|
||||
|
||||
public int getTouchX(final int x) {
|
||||
return x + mCorrectionX;
|
||||
}
|
||||
|
||||
// TODO: Remove vertical correction.
|
||||
public int getTouchY(final int y) {
|
||||
return y + mCorrectionY;
|
||||
}
|
||||
|
||||
public Keyboard getKeyboard() {
|
||||
return mKeyboard;
|
||||
}
|
||||
|
||||
public boolean alwaysAllowsKeySelectionByDraggingFinger() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the key whose hitbox the touch point is in.
|
||||
*
|
||||
* @param x The x-coordinate of a touch point
|
||||
* @param y The y-coordinate of a touch point
|
||||
* @return the key that the touch point hits.
|
||||
*/
|
||||
public Key detectHitKey(final int x, final int y) {
|
||||
if (mKeyboard == null) {
|
||||
return null;
|
||||
}
|
||||
final int touchX = getTouchX(x);
|
||||
final int touchY = getTouchY(y);
|
||||
|
||||
for (final Key key: mKeyboard.getNearestKeys(touchX, touchY)) {
|
||||
if (key.isOnKey(touchX, touchY)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyVisualAttributes;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardIconsSet;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardParams;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
||||
* consists of rows of keys.
|
||||
* <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
|
||||
* <pre>
|
||||
* <Keyboard
|
||||
* latin:keyWidth="10%p"
|
||||
* latin:rowHeight="50px"
|
||||
* latin:horizontalGap="2%p"
|
||||
* latin:verticalGap="2%p" >
|
||||
* <Row latin:keyWidth="10%p" >
|
||||
* <Key latin:keyLabel="A" />
|
||||
* ...
|
||||
* </Row>
|
||||
* ...
|
||||
* </Keyboard>
|
||||
* </pre>
|
||||
*/
|
||||
public class Keyboard {
|
||||
public final KeyboardId mId;
|
||||
|
||||
/** Total height of the keyboard, including the padding and keys */
|
||||
public final int mOccupiedHeight;
|
||||
/** Total width of the keyboard, including the padding and keys */
|
||||
public final int mOccupiedWidth;
|
||||
|
||||
/** The padding below the keyboard */
|
||||
public final float mBottomPadding;
|
||||
/** Default gap between rows */
|
||||
public final float mVerticalGap;
|
||||
/** Default gap between columns */
|
||||
public final float mHorizontalGap;
|
||||
|
||||
/** Per keyboard key visual parameters */
|
||||
public final KeyVisualAttributes mKeyVisualAttributes;
|
||||
|
||||
public final int mMostCommonKeyHeight;
|
||||
public final int mMostCommonKeyWidth;
|
||||
|
||||
/** More keys keyboard template */
|
||||
public final int mMoreKeysTemplate;
|
||||
|
||||
/** List of keys in this keyboard */
|
||||
private final List<Key> mSortedKeys;
|
||||
public final List<Key> mShiftKeys;
|
||||
public final List<Key> mAltCodeKeysWhileTyping;
|
||||
public final KeyboardIconsSet mIconsSet;
|
||||
|
||||
private final SparseArray<Key> mKeyCache = new SparseArray<>();
|
||||
|
||||
private final ProximityInfo mProximityInfo;
|
||||
|
||||
public Keyboard(final KeyboardParams params) {
|
||||
mId = params.mId;
|
||||
mOccupiedHeight = params.mOccupiedHeight;
|
||||
mOccupiedWidth = params.mOccupiedWidth;
|
||||
mMostCommonKeyHeight = params.mMostCommonKeyHeight;
|
||||
mMostCommonKeyWidth = params.mMostCommonKeyWidth;
|
||||
mMoreKeysTemplate = params.mMoreKeysTemplate;
|
||||
mKeyVisualAttributes = params.mKeyVisualAttributes;
|
||||
mBottomPadding = params.mBottomPadding;
|
||||
mVerticalGap = params.mVerticalGap;
|
||||
mHorizontalGap = params.mHorizontalGap;
|
||||
|
||||
mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys));
|
||||
mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
|
||||
mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping);
|
||||
mIconsSet = params.mIconsSet;
|
||||
|
||||
mProximityInfo = new ProximityInfo(params.mGridWidth, params.mGridHeight,
|
||||
mOccupiedWidth, mOccupiedHeight, mSortedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sorted list of keys of this keyboard.
|
||||
* The keys are sorted from top-left to bottom-right order.
|
||||
* The list may contain {@link Key.Spacer} object as well.
|
||||
* @return the sorted unmodifiable list of {@link Key}s of this keyboard.
|
||||
*/
|
||||
public List<Key> getSortedKeys() {
|
||||
return mSortedKeys;
|
||||
}
|
||||
|
||||
public Key getKey(final int code) {
|
||||
if (code == Constants.CODE_UNSPECIFIED) {
|
||||
return null;
|
||||
}
|
||||
synchronized (mKeyCache) {
|
||||
final int index = mKeyCache.indexOfKey(code);
|
||||
if (index >= 0) {
|
||||
return mKeyCache.valueAt(index);
|
||||
}
|
||||
|
||||
for (final Key key : getSortedKeys()) {
|
||||
if (key.getCode() == code) {
|
||||
mKeyCache.put(code, key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
mKeyCache.put(code, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasKey(final Key aKey) {
|
||||
if (mKeyCache.indexOfValue(aKey) >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (final Key key : getSortedKeys()) {
|
||||
if (key == aKey) {
|
||||
mKeyCache.put(key.getCode(), key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mId.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of the keys that are closest to the given point.
|
||||
* @param x the x-coordinate of the point
|
||||
* @param y the y-coordinate of the point
|
||||
* @return the list of the nearest keys to the given point. If the given
|
||||
* point is out of range, then an array of size zero is returned.
|
||||
*/
|
||||
public List<Key> getNearestKeys(final int x, final int y) {
|
||||
// Avoid dead pixels at edges of the keyboard
|
||||
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
|
||||
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
|
||||
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
public interface KeyboardActionListener {
|
||||
/**
|
||||
* Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
|
||||
* For keys that repeat, this is only called once.
|
||||
*
|
||||
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
|
||||
* the value will be zero.
|
||||
* @param repeatCount how many times the key was repeated. Zero if it is the first press.
|
||||
* @param isSinglePointer true if pressing has occurred while no other key is being pressed.
|
||||
*/
|
||||
void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer);
|
||||
|
||||
/**
|
||||
* Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
|
||||
* For keys that repeat, this is only called once.
|
||||
*
|
||||
* @param primaryCode the code of the key that was released
|
||||
* @param withSliding true if releasing has occurred because the user slid finger from the key
|
||||
* to other key without releasing the finger.
|
||||
*/
|
||||
void onReleaseKey(int primaryCode, boolean withSliding);
|
||||
|
||||
/**
|
||||
* Send a key code to the listener.
|
||||
*
|
||||
* @param primaryCode this is the code of the key that was pressed
|
||||
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||
* {@link PointerTracker} or so, the value should be
|
||||
* {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
|
||||
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||
* {@link PointerTracker} or so, the value should be
|
||||
* {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
|
||||
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||
* @param isKeyRepeat true if this is a key repeat, false otherwise
|
||||
*/
|
||||
// TODO: change this to send an Event object instead
|
||||
void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
|
||||
|
||||
/**
|
||||
* Sends a string of characters to the listener.
|
||||
*
|
||||
* @param text the string of characters to be registered.
|
||||
*/
|
||||
void onTextInput(final String rawText);
|
||||
|
||||
/**
|
||||
* Called when user finished sliding key input.
|
||||
*/
|
||||
void onFinishSlidingInput();
|
||||
|
||||
/**
|
||||
* Send a non-"code input" custom request to the listener.
|
||||
* @return true if the request has been consumed, false otherwise.
|
||||
*/
|
||||
boolean onCustomRequest(int requestCode);
|
||||
void onMovePointer(int steps);
|
||||
void onMoveDeletePointer(int steps);
|
||||
void onUpWithDeletePointerActive();
|
||||
|
||||
KeyboardActionListener EMPTY_LISTENER = new Adapter();
|
||||
|
||||
class Adapter implements KeyboardActionListener {
|
||||
@Override
|
||||
public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {}
|
||||
@Override
|
||||
public void onReleaseKey(int primaryCode, boolean withSliding) {}
|
||||
@Override
|
||||
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
|
||||
@Override
|
||||
public void onTextInput(String text) {}
|
||||
@Override
|
||||
public void onFinishSlidingInput() {}
|
||||
@Override
|
||||
public boolean onCustomRequest(int requestCode) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void onMovePointer(int steps) {}
|
||||
@Override
|
||||
public void onMoveDeletePointer(int steps) {}
|
||||
@Override
|
||||
public void onUpWithDeletePointerActive() {}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import com.colorful.keyboard.theme.compat.EditorInfoCompatUtils;
|
||||
import com.colorful.keyboard.theme.latin.Subtype;
|
||||
import com.colorful.keyboard.theme.latin.utils.InputTypeUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* Unique identifier for each keyboard type.
|
||||
*/
|
||||
public final class KeyboardId {
|
||||
public static final int MODE_TEXT = 0;
|
||||
public static final int MODE_URL = 1;
|
||||
public static final int MODE_EMAIL = 2;
|
||||
public static final int MODE_IM = 3;
|
||||
public static final int MODE_PHONE = 4;
|
||||
public static final int MODE_NUMBER = 5;
|
||||
public static final int MODE_DATE = 6;
|
||||
public static final int MODE_TIME = 7;
|
||||
public static final int MODE_DATETIME = 8;
|
||||
|
||||
public static final int ELEMENT_ALPHABET = 0;
|
||||
public static final int ELEMENT_ALPHABET_MANUAL_SHIFTED = 1;
|
||||
public static final int ELEMENT_ALPHABET_AUTOMATIC_SHIFTED = 2;
|
||||
public static final int ELEMENT_ALPHABET_SHIFT_LOCKED = 3;
|
||||
public static final int ELEMENT_SYMBOLS = 5;
|
||||
public static final int ELEMENT_SYMBOLS_SHIFTED = 6;
|
||||
public static final int ELEMENT_PHONE = 7;
|
||||
public static final int ELEMENT_PHONE_SYMBOLS = 8;
|
||||
public static final int ELEMENT_NUMBER = 9;
|
||||
|
||||
public final Subtype mSubtype;
|
||||
public final int mThemeId;
|
||||
public final int mWidth;
|
||||
public final int mHeight;
|
||||
public final int mMode;
|
||||
public final int mElementId;
|
||||
public final EditorInfo mEditorInfo;
|
||||
public final boolean mLanguageSwitchKeyEnabled;
|
||||
public final String mCustomActionLabel;
|
||||
public final boolean mShowMoreKeys;
|
||||
public final boolean mShowNumberRow;
|
||||
|
||||
private final int mHashCode;
|
||||
|
||||
public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
|
||||
mSubtype = params.mSubtype;
|
||||
mThemeId = params.mKeyboardThemeId;
|
||||
mWidth = params.mKeyboardWidth;
|
||||
mHeight = params.mKeyboardHeight;
|
||||
mMode = params.mMode;
|
||||
mElementId = elementId;
|
||||
mEditorInfo = params.mEditorInfo;
|
||||
mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
|
||||
mCustomActionLabel = (mEditorInfo.actionLabel != null)
|
||||
? mEditorInfo.actionLabel.toString() : null;
|
||||
mShowMoreKeys = params.mShowMoreKeys;
|
||||
mShowNumberRow = params.mShowNumberRow;
|
||||
|
||||
mHashCode = computeHashCode(this);
|
||||
}
|
||||
|
||||
private static int computeHashCode(final KeyboardId id) {
|
||||
return Arrays.hashCode(new Object[] {
|
||||
id.mElementId,
|
||||
id.mMode,
|
||||
id.mWidth,
|
||||
id.mHeight,
|
||||
id.passwordInput(),
|
||||
id.mLanguageSwitchKeyEnabled,
|
||||
id.isMultiLine(),
|
||||
id.imeAction(),
|
||||
id.mCustomActionLabel,
|
||||
id.navigateNext(),
|
||||
id.navigatePrevious(),
|
||||
id.mSubtype,
|
||||
id.mThemeId
|
||||
});
|
||||
}
|
||||
|
||||
private boolean equals(final KeyboardId other) {
|
||||
if (other == this)
|
||||
return true;
|
||||
return other.mElementId == mElementId
|
||||
&& other.mMode == mMode
|
||||
&& other.mWidth == mWidth
|
||||
&& other.mHeight == mHeight
|
||||
&& other.passwordInput() == passwordInput()
|
||||
&& other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
|
||||
&& other.isMultiLine() == isMultiLine()
|
||||
&& other.imeAction() == imeAction()
|
||||
&& TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
|
||||
&& other.navigateNext() == navigateNext()
|
||||
&& other.navigatePrevious() == navigatePrevious()
|
||||
&& other.mSubtype.equals(mSubtype)
|
||||
&& other.mThemeId == mThemeId;
|
||||
}
|
||||
|
||||
private static boolean isAlphabetKeyboard(final int elementId) {
|
||||
return elementId < ELEMENT_SYMBOLS;
|
||||
}
|
||||
|
||||
public boolean isAlphabetKeyboard() {
|
||||
return isAlphabetKeyboard(mElementId);
|
||||
}
|
||||
|
||||
public boolean navigateNext() {
|
||||
return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
|
||||
|| imeAction() == EditorInfo.IME_ACTION_NEXT;
|
||||
}
|
||||
|
||||
public boolean navigatePrevious() {
|
||||
return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
|
||||
|| imeAction() == EditorInfo.IME_ACTION_PREVIOUS;
|
||||
}
|
||||
|
||||
public boolean passwordInput() {
|
||||
final int inputType = mEditorInfo.inputType;
|
||||
return InputTypeUtils.isPasswordInputType(inputType)
|
||||
|| InputTypeUtils.isVisiblePasswordInputType(inputType);
|
||||
}
|
||||
|
||||
public boolean isMultiLine() {
|
||||
return (mEditorInfo.inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
|
||||
}
|
||||
|
||||
public int imeAction() {
|
||||
return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo);
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return mSubtype.getLocaleObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
return other instanceof KeyboardId && equals((KeyboardId) other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s %s]",
|
||||
elementIdToName(mElementId),
|
||||
mSubtype.getLocale(),
|
||||
mSubtype.getKeyboardLayoutSet(),
|
||||
mWidth, mHeight,
|
||||
modeName(mMode),
|
||||
actionName(imeAction()),
|
||||
(navigateNext() ? " navigateNext" : ""),
|
||||
(navigatePrevious() ? " navigatePrevious" : ""),
|
||||
(passwordInput() ? " passwordInput" : ""),
|
||||
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
|
||||
(isMultiLine() ? " isMultiLine" : ""),
|
||||
KeyboardTheme.getKeyboardThemeName(mThemeId)
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean equivalentEditorInfoForKeyboard(final EditorInfo a, final EditorInfo b) {
|
||||
if (a == null && b == null) return true;
|
||||
if (a == null || b == null) return false;
|
||||
return a.inputType == b.inputType
|
||||
&& a.imeOptions == b.imeOptions
|
||||
&& TextUtils.equals(a.privateImeOptions, b.privateImeOptions);
|
||||
}
|
||||
|
||||
public static String elementIdToName(final int elementId) {
|
||||
switch (elementId) {
|
||||
case ELEMENT_ALPHABET: return "alphabet";
|
||||
case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted";
|
||||
case ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: return "alphabetAutomaticShifted";
|
||||
case ELEMENT_ALPHABET_SHIFT_LOCKED: return "alphabetShiftLocked";
|
||||
case ELEMENT_SYMBOLS: return "symbols";
|
||||
case ELEMENT_SYMBOLS_SHIFTED: return "symbolsShifted";
|
||||
case ELEMENT_PHONE: return "phone";
|
||||
case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols";
|
||||
case ELEMENT_NUMBER: return "number";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String modeName(final int mode) {
|
||||
switch (mode) {
|
||||
case MODE_TEXT: return "text";
|
||||
case MODE_URL: return "url";
|
||||
case MODE_EMAIL: return "email";
|
||||
case MODE_IM: return "im";
|
||||
case MODE_PHONE: return "phone";
|
||||
case MODE_NUMBER: return "number";
|
||||
case MODE_DATE: return "date";
|
||||
case MODE_TIME: return "time";
|
||||
case MODE_DATETIME: return "datetime";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String actionName(final int actionId) {
|
||||
return (actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel"
|
||||
: EditorInfoCompatUtils.imeActionName(actionId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.Xml;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardBuilder;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.UniqueKeysCache;
|
||||
import com.colorful.keyboard.theme.latin.Subtype;
|
||||
import com.colorful.keyboard.theme.latin.utils.InputTypeUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.XmlParseUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class represents a set of keyboard layouts. Each of them represents a different keyboard
|
||||
* specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same
|
||||
* {@link KeyboardLayoutSet} are related to each other.
|
||||
* A {@link KeyboardLayoutSet} needs to be created for each
|
||||
* {@link EditorInfo}.
|
||||
*/
|
||||
public final class KeyboardLayoutSet {
|
||||
private static final String TAG = KeyboardLayoutSet.class.getSimpleName();
|
||||
private static final boolean DEBUG_CACHE = false;
|
||||
|
||||
private static final String TAG_KEYBOARD_SET = "KeyboardLayoutSet";
|
||||
private static final String TAG_ELEMENT = "Element";
|
||||
|
||||
private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_";
|
||||
|
||||
private final Context mContext;
|
||||
private final Params mParams;
|
||||
|
||||
// How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and
|
||||
// ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of
|
||||
// soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts.
|
||||
private static final int FORCIBLE_CACHE_SIZE = 4;
|
||||
// By construction of soft references, anything that is also referenced somewhere else
|
||||
// will stay in the cache. So we forcibly keep some references in an array to prevent
|
||||
// them from disappearing from sKeyboardCache.
|
||||
private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE];
|
||||
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
|
||||
new HashMap<>();
|
||||
private static final UniqueKeysCache sUniqueKeysCache = UniqueKeysCache.newInstance();
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final class KeyboardLayoutSetException extends RuntimeException {
|
||||
public final KeyboardId mKeyboardId;
|
||||
|
||||
public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
|
||||
super(cause);
|
||||
mKeyboardId = keyboardId;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ElementParams {
|
||||
int mKeyboardXmlId;
|
||||
boolean mAllowRedundantMoreKeys;
|
||||
public ElementParams() {}
|
||||
}
|
||||
|
||||
public static final class Params {
|
||||
String mKeyboardLayoutSetName;
|
||||
int mMode;
|
||||
// TODO: Use {@link InputAttributes} instead of these variables.
|
||||
EditorInfo mEditorInfo;
|
||||
boolean mLanguageSwitchKeyEnabled;
|
||||
Subtype mSubtype;
|
||||
int mKeyboardThemeId;
|
||||
int mKeyboardWidth;
|
||||
int mKeyboardHeight;
|
||||
boolean mShowMoreKeys;
|
||||
boolean mShowNumberRow;
|
||||
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
|
||||
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
|
||||
new SparseArray<>();
|
||||
}
|
||||
|
||||
public static void onSystemLocaleChanged() {
|
||||
clearKeyboardCache();
|
||||
}
|
||||
|
||||
public static void onKeyboardThemeChanged() {
|
||||
clearKeyboardCache();
|
||||
}
|
||||
|
||||
private static void clearKeyboardCache() {
|
||||
sKeyboardCache.clear();
|
||||
sUniqueKeysCache.clear();
|
||||
}
|
||||
|
||||
KeyboardLayoutSet(final Context context, final Params params) {
|
||||
mContext = context;
|
||||
mParams = params;
|
||||
}
|
||||
|
||||
public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
|
||||
final int keyboardLayoutSetElementId;
|
||||
switch (mParams.mMode) {
|
||||
case KeyboardId.MODE_PHONE:
|
||||
if (baseKeyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||
keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS;
|
||||
} else {
|
||||
keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE;
|
||||
}
|
||||
break;
|
||||
case KeyboardId.MODE_NUMBER:
|
||||
case KeyboardId.MODE_DATE:
|
||||
case KeyboardId.MODE_TIME:
|
||||
case KeyboardId.MODE_DATETIME:
|
||||
keyboardLayoutSetElementId = KeyboardId.ELEMENT_NUMBER;
|
||||
break;
|
||||
default:
|
||||
keyboardLayoutSetElementId = baseKeyboardLayoutSetElementId;
|
||||
break;
|
||||
}
|
||||
|
||||
ElementParams elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get(
|
||||
keyboardLayoutSetElementId);
|
||||
if (elementParams == null) {
|
||||
elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get(
|
||||
KeyboardId.ELEMENT_ALPHABET);
|
||||
}
|
||||
// Note: The keyboard for each shift state, and mode are represented as an elementName
|
||||
// attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is
|
||||
// specified as an elementKeyboard attribute in the file.
|
||||
// The KeyboardId is an internal key for a Keyboard object.
|
||||
|
||||
final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
|
||||
return getKeyboard(elementParams, id);
|
||||
}
|
||||
|
||||
private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
|
||||
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
|
||||
final Keyboard cachedKeyboard = (ref == null) ? null : ref.get();
|
||||
if (cachedKeyboard != null) {
|
||||
if (DEBUG_CACHE) {
|
||||
Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT id=" + id);
|
||||
}
|
||||
return cachedKeyboard;
|
||||
}
|
||||
|
||||
final KeyboardBuilder<KeyboardParams> builder =
|
||||
new KeyboardBuilder<>(mContext, new KeyboardParams(sUniqueKeysCache));
|
||||
sUniqueKeysCache.setEnabled(id.isAlphabetKeyboard());
|
||||
builder.setAllowRedundantMoreKes(elementParams.mAllowRedundantMoreKeys);
|
||||
final int keyboardXmlId = elementParams.mKeyboardXmlId;
|
||||
builder.load(keyboardXmlId, id);
|
||||
final Keyboard keyboard = builder.build();
|
||||
sKeyboardCache.put(id, new SoftReference<>(keyboard));
|
||||
if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET
|
||||
|| id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)) {
|
||||
// We only forcibly cache the primary, "ALPHABET", layouts.
|
||||
for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) {
|
||||
sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1];
|
||||
}
|
||||
sForcibleKeyboardCache[0] = keyboard;
|
||||
if (DEBUG_CACHE) {
|
||||
Log.d(TAG, "forcing caching of keyboard with id=" + id);
|
||||
}
|
||||
}
|
||||
if (DEBUG_CACHE) {
|
||||
Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": "
|
||||
+ ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
|
||||
}
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
|
||||
private final Params mParams = new Params();
|
||||
|
||||
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
|
||||
|
||||
public Builder(final Context context, final EditorInfo ei) {
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
final Params params = mParams;
|
||||
|
||||
final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
|
||||
params.mMode = getKeyboardMode(editorInfo);
|
||||
// TODO: Consolidate those with {@link InputAttributes}.
|
||||
params.mEditorInfo = editorInfo;
|
||||
}
|
||||
|
||||
public Builder setKeyboardTheme(final int themeId) {
|
||||
mParams.mKeyboardThemeId = themeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
|
||||
mParams.mKeyboardWidth = keyboardWidth;
|
||||
mParams.mKeyboardHeight = keyboardHeight;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSubtype(final Subtype subtype) {
|
||||
// TODO: Consolidate with {@link InputAttributes}.
|
||||
mParams.mSubtype = subtype;
|
||||
mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
|
||||
+ subtype.getKeyboardLayoutSet();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLanguageSwitchKeyEnabled(final boolean enabled) {
|
||||
mParams.mLanguageSwitchKeyEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setShowSpecialChars(final boolean enabled) {
|
||||
mParams.mShowMoreKeys = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setShowNumberRow(final boolean enabled) {
|
||||
mParams.mShowNumberRow = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyboardLayoutSet build() {
|
||||
if (mParams.mSubtype == null)
|
||||
throw new RuntimeException("KeyboardLayoutSet subtype is not specified");
|
||||
final int xmlId = getXmlId(mResources, mParams.mKeyboardLayoutSetName);
|
||||
try {
|
||||
parseKeyboardLayoutSet(mResources, xmlId);
|
||||
} catch (final IOException | XmlPullParserException e) {
|
||||
throw new RuntimeException(e.getMessage() + " in " + mParams.mKeyboardLayoutSetName,
|
||||
e);
|
||||
}
|
||||
return new KeyboardLayoutSet(mContext, mParams);
|
||||
}
|
||||
|
||||
private static int getXmlId(final Resources resources, final String keyboardLayoutSetName) {
|
||||
final String packageName = resources.getResourcePackageName(
|
||||
R.xml.keyboard_layout_set_qwerty);
|
||||
return resources.getIdentifier(keyboardLayoutSetName, "xml", packageName);
|
||||
}
|
||||
|
||||
private void parseKeyboardLayoutSet(final Resources res, final int resId)
|
||||
throws XmlPullParserException, IOException {
|
||||
final XmlResourceParser parser = res.getXml(resId);
|
||||
try {
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_KEYBOARD_SET.equals(tag)) {
|
||||
parseKeyboardLayoutSetContent(parser);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_ELEMENT.equals(tag)) {
|
||||
parseKeyboardLayoutSetElement(parser);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET);
|
||||
}
|
||||
} else if (event == XmlPullParser.END_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_KEYBOARD_SET.equals(tag)) {
|
||||
break;
|
||||
}
|
||||
throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_KEYBOARD_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.KeyboardLayoutSet_Element);
|
||||
try {
|
||||
XmlParseUtils.checkAttributeExists(a,
|
||||
R.styleable.KeyboardLayoutSet_Element_elementName, "elementName",
|
||||
TAG_ELEMENT, parser);
|
||||
XmlParseUtils.checkAttributeExists(a,
|
||||
R.styleable.KeyboardLayoutSet_Element_elementKeyboard, "elementKeyboard",
|
||||
TAG_ELEMENT, parser);
|
||||
XmlParseUtils.checkEndTag(TAG_ELEMENT, parser);
|
||||
|
||||
final ElementParams elementParams = new ElementParams();
|
||||
final int elementName = a.getInt(
|
||||
R.styleable.KeyboardLayoutSet_Element_elementName, 0);
|
||||
elementParams.mKeyboardXmlId = a.getResourceId(
|
||||
R.styleable.KeyboardLayoutSet_Element_elementKeyboard, 0);
|
||||
elementParams.mAllowRedundantMoreKeys = a.getBoolean(
|
||||
R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true);
|
||||
mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static int getKeyboardMode(final EditorInfo editorInfo) {
|
||||
final int inputType = editorInfo.inputType;
|
||||
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
switch (inputType & InputType.TYPE_MASK_CLASS) {
|
||||
case InputType.TYPE_CLASS_NUMBER:
|
||||
return KeyboardId.MODE_NUMBER;
|
||||
case InputType.TYPE_CLASS_DATETIME:
|
||||
switch (variation) {
|
||||
case InputType.TYPE_DATETIME_VARIATION_DATE:
|
||||
return KeyboardId.MODE_DATE;
|
||||
case InputType.TYPE_DATETIME_VARIATION_TIME:
|
||||
return KeyboardId.MODE_TIME;
|
||||
default: // InputType.TYPE_DATETIME_VARIATION_NORMAL
|
||||
return KeyboardId.MODE_DATETIME;
|
||||
}
|
||||
case InputType.TYPE_CLASS_PHONE:
|
||||
return KeyboardId.MODE_PHONE;
|
||||
case InputType.TYPE_CLASS_TEXT:
|
||||
if (InputTypeUtils.isEmailVariation(variation)) {
|
||||
return KeyboardId.MODE_EMAIL;
|
||||
} else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
|
||||
return KeyboardId.MODE_URL;
|
||||
} else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
|
||||
return KeyboardId.MODE_IM;
|
||||
} else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
|
||||
return KeyboardId.MODE_TEXT;
|
||||
} else {
|
||||
return KeyboardId.MODE_TEXT;
|
||||
}
|
||||
default:
|
||||
return KeyboardId.MODE_TEXT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.colorful.keyboard.theme.App;
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.event.Event;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardState;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardTextsSet;
|
||||
import com.colorful.keyboard.theme.latin.InputView;
|
||||
import com.colorful.keyboard.theme.latin.LatinIME;
|
||||
import com.colorful.keyboard.theme.latin.RichInputMethodManager;
|
||||
import com.colorful.keyboard.theme.latin.settings.Settings;
|
||||
import com.colorful.keyboard.theme.latin.settings.SettingsValues;
|
||||
import com.colorful.keyboard.theme.latin.utils.CapsModeUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.LanguageOnSpacebarUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.RecapitalizeStatus;
|
||||
import com.colorful.keyboard.theme.latin.utils.ResourceUtils;
|
||||
import com.colorful.keyboard.theme.myutil.SPUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
||||
|
||||
private InputView mCurrentInputView;
|
||||
private int mCurrentUiMode;
|
||||
private int mCurrentTextColor = 0x0;
|
||||
private View mMainKeyboardFrame;
|
||||
private MainKeyboardView mKeyboardView;
|
||||
private LatinIME mLatinIME;
|
||||
private RichInputMethodManager mRichImm;
|
||||
|
||||
private KeyboardState mState;
|
||||
|
||||
private KeyboardLayoutSet mKeyboardLayoutSet;
|
||||
// TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}.
|
||||
private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet();
|
||||
|
||||
private KeyboardTheme mKeyboardTheme;
|
||||
private Context mThemeContext;
|
||||
|
||||
private static KeyboardSwitcher sInstance = new KeyboardSwitcher();
|
||||
|
||||
public static KeyboardSwitcher getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private KeyboardSwitcher() {
|
||||
// Intentional empty constructor for singleton.
|
||||
}
|
||||
|
||||
public static void init(final LatinIME latinIme) {
|
||||
sInstance.initInternal(latinIme);
|
||||
}
|
||||
|
||||
private void initInternal(final LatinIME latinIme) {
|
||||
mLatinIME = latinIme;
|
||||
mRichImm = RichInputMethodManager.getInstance();
|
||||
mState = new KeyboardState(this);
|
||||
}
|
||||
|
||||
public void updateKeyboardTheme(final int uiMode) {
|
||||
final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
|
||||
mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME), uiMode);
|
||||
if (themeUpdated && mKeyboardView != null) {
|
||||
mLatinIME.setInputView(onCreateInputView(uiMode));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
|
||||
final KeyboardTheme keyboardTheme, final int uiMode) {
|
||||
int newTextColor = 0x0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
newTextColor = context.getResources().getColor(R.color.key_text_color_lxx_system);
|
||||
}
|
||||
|
||||
if (mThemeContext == null
|
||||
|| !keyboardTheme.equals(mKeyboardTheme)
|
||||
|| mCurrentUiMode != uiMode
|
||||
|| newTextColor != mCurrentTextColor) {
|
||||
mKeyboardTheme = keyboardTheme;
|
||||
mCurrentUiMode = uiMode;
|
||||
mCurrentTextColor = newTextColor;
|
||||
mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
|
||||
final int currentAutoCapsState, final int currentRecapitalizeState) {
|
||||
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
|
||||
mThemeContext, editorInfo);
|
||||
final Resources res = mThemeContext.getResources();
|
||||
final int keyboardWidth = mLatinIME.getMaxWidth();
|
||||
final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues);
|
||||
builder.setKeyboardTheme(mKeyboardTheme.mThemeId);
|
||||
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
|
||||
builder.setSubtype(mRichImm.getCurrentSubtype());
|
||||
builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
|
||||
builder.setShowSpecialChars(!settingsValues.mHideSpecialChars);
|
||||
builder.setShowNumberRow(settingsValues.mShowNumberRow);
|
||||
mKeyboardLayoutSet = builder.build();
|
||||
try {
|
||||
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState);
|
||||
mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtype().getLocaleObject(),
|
||||
mThemeContext);
|
||||
} catch (KeyboardLayoutSet.KeyboardLayoutSetException e) {
|
||||
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
public void saveKeyboardState() {
|
||||
if (getKeyboard() != null) {
|
||||
mState.onSaveKeyboardState();
|
||||
}
|
||||
}
|
||||
|
||||
public void onHideWindow() {
|
||||
if (mKeyboardView != null) {
|
||||
mKeyboardView.onHideWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeyboard(
|
||||
final int keyboardId,
|
||||
final KeyboardSwitchState toggleState) {
|
||||
final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
|
||||
setMainKeyboardFrame(currentSettingsValues, toggleState);
|
||||
// TODO: pass this object to setKeyboard instead of getting the current values.
|
||||
final MainKeyboardView keyboardView = mKeyboardView;
|
||||
final Keyboard oldKeyboard = keyboardView.getKeyboard();
|
||||
final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId);
|
||||
keyboardView.setKeyboard(newKeyboard);
|
||||
keyboardView.setKeyPreviewPopupEnabled(
|
||||
currentSettingsValues.mKeyPreviewPopupOn,
|
||||
currentSettingsValues.mKeyPreviewPopupDismissDelay);
|
||||
final boolean subtypeChanged = (oldKeyboard == null)
|
||||
|| !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype);
|
||||
final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils
|
||||
.getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype);
|
||||
keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType);
|
||||
}
|
||||
|
||||
public Keyboard getKeyboard() {
|
||||
if (mKeyboardView != null) {
|
||||
return mKeyboardView.getKeyboard();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
|
||||
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
|
||||
public void resetKeyboardStateToAlphabet(final int currentAutoCapsState,
|
||||
final int currentRecapitalizeState) {
|
||||
mState.onResetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState);
|
||||
}
|
||||
|
||||
public void onPressKey(final int code, final boolean isSinglePointer,
|
||||
final int currentAutoCapsState, final int currentRecapitalizeState) {
|
||||
mState.onPressKey(code, isSinglePointer, currentAutoCapsState, currentRecapitalizeState);
|
||||
}
|
||||
|
||||
public void onReleaseKey(final int code, final boolean withSliding,
|
||||
final int currentAutoCapsState, final int currentRecapitalizeState) {
|
||||
mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState);
|
||||
}
|
||||
|
||||
public void onFinishSlidingInput(final int currentAutoCapsState,
|
||||
final int currentRecapitalizeState) {
|
||||
mState.onFinishSlidingInput(currentAutoCapsState, currentRecapitalizeState);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setAlphabetKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setAlphabetKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setAlphabetManualShiftedKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setAlphabetManualShiftedKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setAlphabetAutomaticShiftedKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setAlphabetShiftLockedKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setAlphabetShiftLockedKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setSymbolsKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setSymbolsKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void setSymbolsShiftedKeyboard() {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "setSymbolsShiftedKeyboard");
|
||||
}
|
||||
setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED);
|
||||
}
|
||||
|
||||
public boolean isImeSuppressedByHardwareKeyboard(
|
||||
final SettingsValues settingsValues,
|
||||
final KeyboardSwitchState toggleState) {
|
||||
return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN;
|
||||
}
|
||||
|
||||
private void setMainKeyboardFrame(
|
||||
final SettingsValues settingsValues,
|
||||
final KeyboardSwitchState toggleState) {
|
||||
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState)
|
||||
? View.GONE : View.VISIBLE;
|
||||
mKeyboardView.setVisibility(visibility);
|
||||
// The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
|
||||
// @see #getVisibleKeyboardView() and
|
||||
// @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
|
||||
mMainKeyboardFrame.setVisibility(visibility);
|
||||
}
|
||||
|
||||
public enum KeyboardSwitchState {
|
||||
HIDDEN(-1),
|
||||
SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED),
|
||||
OTHER(-1);
|
||||
|
||||
final int mKeyboardId;
|
||||
|
||||
KeyboardSwitchState(int keyboardId) {
|
||||
mKeyboardId = keyboardId;
|
||||
}
|
||||
}
|
||||
|
||||
public KeyboardSwitchState getKeyboardSwitchState() {
|
||||
boolean hidden = mKeyboardLayoutSet == null
|
||||
|| mKeyboardView == null
|
||||
|| !mKeyboardView.isShown();
|
||||
if (hidden) {
|
||||
return KeyboardSwitchState.HIDDEN;
|
||||
} else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) {
|
||||
return KeyboardSwitchState.SYMBOLS_SHIFTED;
|
||||
}
|
||||
return KeyboardSwitchState.OTHER;
|
||||
}
|
||||
|
||||
// Future method for requesting an updating to the shift state.
|
||||
@Override
|
||||
public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_ACTION) {
|
||||
Log.d(TAG, "requestUpdatingShiftState: "
|
||||
+ " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags)
|
||||
+ " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode));
|
||||
}
|
||||
mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void startDoubleTapShiftKeyTimer() {
|
||||
if (DEBUG_TIMER_ACTION) {
|
||||
Log.d(TAG, "startDoubleTapShiftKeyTimer");
|
||||
}
|
||||
final MainKeyboardView keyboardView = getMainKeyboardView();
|
||||
if (keyboardView != null) {
|
||||
keyboardView.startDoubleTapShiftKeyTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public void cancelDoubleTapShiftKeyTimer() {
|
||||
if (DEBUG_TIMER_ACTION) {
|
||||
Log.d(TAG, "setAlphabetKeyboard");
|
||||
}
|
||||
final MainKeyboardView keyboardView = getMainKeyboardView();
|
||||
if (keyboardView != null) {
|
||||
keyboardView.cancelDoubleTapShiftKeyTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Implements {@link KeyboardState.SwitchActions}.
|
||||
@Override
|
||||
public boolean isInDoubleTapShiftKeyTimeout() {
|
||||
if (DEBUG_TIMER_ACTION) {
|
||||
Log.d(TAG, "isInDoubleTapShiftKeyTimeout");
|
||||
}
|
||||
final MainKeyboardView keyboardView = getMainKeyboardView();
|
||||
return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates state machine to figure out when to automatically switch back to the previous mode.
|
||||
*/
|
||||
public void onEvent(final Event event, final int currentAutoCapsState,
|
||||
final int currentRecapitalizeState) {
|
||||
mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState);
|
||||
}
|
||||
|
||||
public boolean isShowingKeyboardId(int... keyboardIds) {
|
||||
if (mKeyboardView == null || !mKeyboardView.isShown()) {
|
||||
return false;
|
||||
}
|
||||
int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId;
|
||||
for (int keyboardId : keyboardIds) {
|
||||
if (activeKeyboardId == keyboardId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isShowingMoreKeysPanel() {
|
||||
return mKeyboardView.isShowingMoreKeysPanel();
|
||||
}
|
||||
|
||||
public View getVisibleKeyboardView() {
|
||||
return mKeyboardView;
|
||||
}
|
||||
|
||||
public MainKeyboardView getMainKeyboardView() {
|
||||
return mKeyboardView;
|
||||
}
|
||||
|
||||
public void deallocateMemory() {
|
||||
if (mKeyboardView != null) {
|
||||
mKeyboardView.cancelAllOngoingEvents();
|
||||
mKeyboardView.deallocateMemory();
|
||||
}
|
||||
}
|
||||
|
||||
public View onCreateInputView(final int uiMode) {
|
||||
if (mKeyboardView != null) {
|
||||
mKeyboardView.closing();
|
||||
}
|
||||
|
||||
updateKeyboardThemeAndContextThemeWrapper(
|
||||
mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */), uiMode);
|
||||
mCurrentInputView = (InputView) LayoutInflater.from(mThemeContext).inflate(
|
||||
R.layout.input_view, null);
|
||||
mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
|
||||
ImageView imageView = mCurrentInputView.findViewById(R.id.iv_keyboard_bg);
|
||||
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
|
||||
|
||||
mMainKeyboardFrame.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
layoutParams.height = mMainKeyboardFrame.getHeight();
|
||||
layoutParams.width = mMainKeyboardFrame.getWidth();
|
||||
/* if (layoutParams.height > layoutParams.width){
|
||||
return;
|
||||
}*/
|
||||
File file = new File(SPUtils.getInstance().getString("bg"));
|
||||
Glide.with(App.Companion.getApp()).load(file).into(imageView);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
|
||||
mKeyboardView.setKeyboardActionListener(mLatinIME);
|
||||
return mCurrentInputView;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.compat.PreferenceManagerCompat;
|
||||
import com.colorful.keyboard.theme.latin.settings.Settings;
|
||||
|
||||
public final class KeyboardTheme {
|
||||
private static final String TAG = KeyboardTheme.class.getSimpleName();
|
||||
|
||||
static final String KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
|
||||
|
||||
// These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme
|
||||
// attributes' values in attrs.xml.
|
||||
public static final int THEME_ID_LIGHT_BORDER = 1;
|
||||
public static final int THEME_ID_DARK_BORDER = 2;
|
||||
public static final int THEME_ID_LIGHT = 3;
|
||||
public static final int THEME_ID_DARK = 4;
|
||||
public static final int THEME_ID_SYSTEM = 5;
|
||||
public static final int THEME_ID_SYSTEM_BORDER = 6;
|
||||
public static final int DEFAULT_THEME_ID = THEME_ID_LIGHT;
|
||||
|
||||
/* package private for testing */
|
||||
static final KeyboardTheme[] KEYBOARD_THEMES = {
|
||||
new KeyboardTheme(THEME_ID_LIGHT, "LXXLight", R.style.KeyboardTheme_LXX_Light),
|
||||
new KeyboardTheme(THEME_ID_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark),
|
||||
new KeyboardTheme(THEME_ID_LIGHT_BORDER, "LXXLightBorder", R.style.KeyboardTheme_LXX_Light_Border),
|
||||
new KeyboardTheme(THEME_ID_DARK_BORDER, "LXXDarkBorder", R.style.KeyboardTheme_LXX_Dark_Border),
|
||||
new KeyboardTheme(THEME_ID_SYSTEM, "LXXSystem", R.style.KeyboardTheme_LXX_System),
|
||||
new KeyboardTheme(THEME_ID_SYSTEM_BORDER, "LXXSystemBorder", R.style.KeyboardTheme_LXX_System_Border),
|
||||
};
|
||||
|
||||
public final int mThemeId;
|
||||
public final int mStyleId;
|
||||
public final String mThemeName;
|
||||
|
||||
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
|
||||
// in values/themes-<style>.xml.
|
||||
private KeyboardTheme(final int themeId, final String themeName, final int styleId) {
|
||||
mThemeId = themeId;
|
||||
mThemeName = themeName;
|
||||
mStyleId = styleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) return true;
|
||||
return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mThemeId;
|
||||
}
|
||||
|
||||
/* package private for testing */
|
||||
static KeyboardTheme searchKeyboardThemeById(final int themeId) {
|
||||
// TODO: This search algorithm isn't optimal if there are many themes.
|
||||
for (final KeyboardTheme theme : KEYBOARD_THEMES) {
|
||||
if (theme.mThemeId == themeId) {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* package private for testing */
|
||||
static KeyboardTheme getDefaultKeyboardTheme() {
|
||||
return searchKeyboardThemeById(DEFAULT_THEME_ID);
|
||||
}
|
||||
|
||||
public static String getKeyboardThemeName(final int themeId) {
|
||||
final KeyboardTheme theme = searchKeyboardThemeById(themeId);
|
||||
Log.i("Getting theme ID", Integer.toString(themeId));
|
||||
return theme.mThemeName;
|
||||
}
|
||||
|
||||
public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) {
|
||||
prefs.edit().putString(KEYBOARD_THEME_KEY, Integer.toString(themeId)).apply();
|
||||
}
|
||||
|
||||
public static KeyboardTheme getKeyboardTheme(final Context context) {
|
||||
final SharedPreferences prefs = PreferenceManagerCompat.getDeviceSharedPreferences(context);
|
||||
return getKeyboardTheme(prefs);
|
||||
}
|
||||
|
||||
public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
|
||||
final String themeIdString = prefs.getString(KEYBOARD_THEME_KEY, null);
|
||||
if (themeIdString == null) {
|
||||
return searchKeyboardThemeById(THEME_ID_LIGHT);
|
||||
}
|
||||
try {
|
||||
final int themeId = Integer.parseInt(themeIdString);
|
||||
final KeyboardTheme theme = searchKeyboardThemeById(themeId);
|
||||
if (theme != null) {
|
||||
return theme;
|
||||
}
|
||||
Log.w(TAG, "Unknown keyboard theme in preference: " + themeIdString);
|
||||
} catch (final NumberFormatException e) {
|
||||
Log.w(TAG, "Illegal keyboard theme in preference: " + themeIdString, e);
|
||||
}
|
||||
// Remove preference that contains unknown or illegal theme id.
|
||||
prefs.edit().remove(KEYBOARD_THEME_KEY).remove(Settings.PREF_KEYBOARD_COLOR).apply();
|
||||
return getDefaultKeyboardTheme();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.compat.PreferenceManagerCompat;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyDrawParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyVisualAttributes;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.settings.Settings;
|
||||
import com.colorful.keyboard.theme.latin.utils.TypefaceUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
|
||||
/**
|
||||
* A view that renders a virtual {@link Keyboard}.
|
||||
*
|
||||
* @attr ref R.styleable#KeyboardView_keyBackground
|
||||
* @attr ref R.styleable#KeyboardView_functionalKeyBackground
|
||||
* @attr ref R.styleable#KeyboardView_spacebarBackground
|
||||
* @attr ref R.styleable#KeyboardView_spacebarIconWidthRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLabelFlags
|
||||
* @attr ref R.styleable#KeyboardView_keyHintLetterPadding
|
||||
* @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
|
||||
* @attr ref R.styleable#KeyboardView_keyTextShadowRadius
|
||||
* @attr ref R.styleable#KeyboardView_verticalCorrection
|
||||
* @attr ref R.styleable#Keyboard_Key_keyTypeface
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLetterSize
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLabelSize
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyLabelOffCenterRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyHintLabelOffCenterRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
|
||||
* @attr ref R.styleable#Keyboard_Key_keyTextColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
|
||||
* @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
|
||||
* @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
|
||||
*/
|
||||
public class KeyboardView extends View {
|
||||
// XML attributes
|
||||
private final KeyVisualAttributes mKeyVisualAttributes;
|
||||
// Default keyLabelFlags from {@link KeyboardTheme}.
|
||||
// Currently only "alignHintLabelToBottom" is supported.
|
||||
private final int mDefaultKeyLabelFlags;
|
||||
private final float mKeyHintLetterPadding;
|
||||
private final float mKeyShiftedLetterHintPadding;
|
||||
private final float mKeyTextShadowRadius;
|
||||
private final float mVerticalCorrection;
|
||||
private final Drawable mKeyBackground;
|
||||
private final Drawable mFunctionalKeyBackground;
|
||||
private final Drawable mSpacebarBackground;
|
||||
private final float mSpacebarIconWidthRatio;
|
||||
private final Rect mKeyBackgroundPadding = new Rect();
|
||||
private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
|
||||
public int mCustomColor = 0;
|
||||
|
||||
// The maximum key label width in the proportion to the key width.
|
||||
private static final float MAX_LABEL_RATIO = 0.90f;
|
||||
|
||||
// Main keyboard
|
||||
// TODO: Consider having a dummy keyboard object to make this @NonNull
|
||||
private Keyboard mKeyboard;
|
||||
private final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
|
||||
|
||||
// Drawing
|
||||
/** True if all keys should be drawn */
|
||||
private boolean mInvalidateAllKeys;
|
||||
/** The keys that should be drawn */
|
||||
private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
|
||||
/** The working rectangle for clipping */
|
||||
private final Rect mClipRect = new Rect();
|
||||
/** The keyboard bitmap buffer for faster updates */
|
||||
private Bitmap mOffscreenBuffer;
|
||||
/** The canvas for the above mutable keyboard bitmap */
|
||||
private final Canvas mOffscreenCanvas = new Canvas();
|
||||
private final Paint mPaint = new Paint();
|
||||
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
|
||||
|
||||
public KeyboardView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.keyboardViewStyle);
|
||||
}
|
||||
|
||||
public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||
mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
|
||||
mKeyBackground.getPadding(mKeyBackgroundPadding);
|
||||
final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
|
||||
R.styleable.KeyboardView_functionalKeyBackground);
|
||||
mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
|
||||
: mKeyBackground;
|
||||
final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
|
||||
R.styleable.KeyboardView_spacebarBackground);
|
||||
mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground;
|
||||
mSpacebarIconWidthRatio = keyboardViewAttr.getFloat(
|
||||
R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f);
|
||||
mKeyHintLetterPadding = keyboardViewAttr.getDimension(
|
||||
R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
|
||||
mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
|
||||
R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
|
||||
mKeyTextShadowRadius = keyboardViewAttr.getFloat(
|
||||
R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
|
||||
mVerticalCorrection = keyboardViewAttr.getDimension(
|
||||
R.styleable.KeyboardView_verticalCorrection, 0.0f);
|
||||
keyboardViewAttr.recycle();
|
||||
|
||||
final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
|
||||
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
|
||||
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
|
||||
keyAttr.recycle();
|
||||
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
private static void blendAlpha(final Paint paint, final int alpha) {
|
||||
final int color = paint.getColor();
|
||||
paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
|
||||
Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a keyboard to this view. The keyboard can be switched at any time and the
|
||||
* view will re-layout itself to accommodate the keyboard.
|
||||
* @see Keyboard
|
||||
* @see #getKeyboard()
|
||||
* @param keyboard the keyboard to display in this view
|
||||
*/
|
||||
public void setKeyboard(final Keyboard keyboard) {
|
||||
mKeyboard = keyboard;
|
||||
final int keyHeight = keyboard.mMostCommonKeyHeight;
|
||||
mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
|
||||
mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
|
||||
final SharedPreferences prefs = PreferenceManagerCompat.getDeviceSharedPreferences(getContext());
|
||||
mCustomColor = Settings.readKeyboardColor(prefs, getContext());
|
||||
invalidateAllKeys();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current keyboard being displayed by this view.
|
||||
* @return the currently attached keyboard
|
||||
* @see #setKeyboard(Keyboard)
|
||||
*/
|
||||
public Keyboard getKeyboard() {
|
||||
return mKeyboard;
|
||||
}
|
||||
|
||||
protected float getVerticalCorrection() {
|
||||
return mVerticalCorrection;
|
||||
}
|
||||
|
||||
protected KeyDrawParams getKeyDrawParams() {
|
||||
return mKeyDrawParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
// The main keyboard expands to the entire this {@link KeyboardView}.
|
||||
final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
|
||||
final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (canvas.isHardwareAccelerated()) {
|
||||
onDrawKeyboard(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
|
||||
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
|
||||
if (maybeAllocateOffscreenBuffer()) {
|
||||
mInvalidateAllKeys = true;
|
||||
// TODO: Stop using the offscreen canvas even when in software rendering
|
||||
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
|
||||
}
|
||||
onDrawKeyboard(mOffscreenCanvas);
|
||||
}
|
||||
canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null);
|
||||
}
|
||||
|
||||
private boolean maybeAllocateOffscreenBuffer() {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
if (width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width
|
||||
&& mOffscreenBuffer.getHeight() == height) {
|
||||
return false;
|
||||
}
|
||||
freeOffscreenBuffer();
|
||||
mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void freeOffscreenBuffer() {
|
||||
mOffscreenCanvas.setBitmap(null);
|
||||
mOffscreenCanvas.setMatrix(null);
|
||||
if (mOffscreenBuffer != null) {
|
||||
mOffscreenBuffer.recycle();
|
||||
mOffscreenBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDrawKeyboard(final Canvas canvas) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Paint paint = mPaint;
|
||||
final Drawable background = getBackground();
|
||||
if (Color.alpha(mCustomColor) > 0 && keyboard.getKey(Constants.CODE_SPACE) != null) {
|
||||
setBackgroundColor(mCustomColor);
|
||||
}
|
||||
// Calculate clip region and set.
|
||||
final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
|
||||
final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
|
||||
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
|
||||
if (drawAllKeys || isHardwareAccelerated) {
|
||||
if (!isHardwareAccelerated && background != null) {
|
||||
// Need to draw keyboard background on {@link #mOffscreenBuffer}.
|
||||
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
|
||||
background.draw(canvas);
|
||||
}
|
||||
// Draw all keys.
|
||||
for (final Key key : keyboard.getSortedKeys()) {
|
||||
onDrawKey(key, canvas, paint);
|
||||
}
|
||||
} else {
|
||||
for (final Key key : mInvalidatedKeys) {
|
||||
if (!keyboard.hasKey(key)) {
|
||||
continue;
|
||||
}
|
||||
if (background != null) {
|
||||
// Need to redraw key's background on {@link #mOffscreenBuffer}.
|
||||
final int x = key.getX() + getPaddingLeft();
|
||||
final int y = key.getY() + getPaddingTop();
|
||||
mClipRect.set(x, y, x + key.getWidth(), y + key.getHeight());
|
||||
canvas.save();
|
||||
canvas.clipRect(mClipRect);
|
||||
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
|
||||
background.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
onDrawKey(key, canvas, paint);
|
||||
}
|
||||
}
|
||||
|
||||
mInvalidatedKeys.clear();
|
||||
mInvalidateAllKeys = false;
|
||||
}
|
||||
|
||||
private void onDrawKey(final Key key, final Canvas canvas,
|
||||
final Paint paint) {
|
||||
final int keyDrawX = key.getX() + getPaddingLeft();
|
||||
final int keyDrawY = key.getY() + getPaddingTop();
|
||||
canvas.translate(keyDrawX, keyDrawY);
|
||||
|
||||
final KeyVisualAttributes attr = key.getVisualAttributes();
|
||||
final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(key.getHeight(), attr);
|
||||
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
|
||||
|
||||
if (!key.isSpacer()) {
|
||||
final Drawable background = key.selectBackgroundDrawable(
|
||||
mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
|
||||
if (background != null) {
|
||||
onDrawKeyBackground(key, canvas, background);
|
||||
}
|
||||
}
|
||||
onDrawKeyTopVisuals(key, canvas, paint, params);
|
||||
|
||||
canvas.translate(-keyDrawX, -keyDrawY);
|
||||
}
|
||||
|
||||
// Draw key background.
|
||||
protected void onDrawKeyBackground(final Key key, final Canvas canvas,
|
||||
final Drawable background) {
|
||||
final int keyWidth = key.getWidth();
|
||||
final int keyHeight = key.getHeight();
|
||||
final Rect padding = mKeyBackgroundPadding;
|
||||
final int bgWidth = keyWidth + padding.left + padding.right;
|
||||
final int bgHeight = keyHeight + padding.top + padding.bottom;
|
||||
final int bgX = -padding.left;
|
||||
final int bgY = -padding.top;
|
||||
final Rect bounds = background.getBounds();
|
||||
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
|
||||
background.setBounds(0, 0, bgWidth, bgHeight);
|
||||
}
|
||||
canvas.translate(bgX, bgY);
|
||||
background.draw(canvas);
|
||||
canvas.translate(-bgX, -bgY);
|
||||
}
|
||||
|
||||
// Draw key top visuals.
|
||||
protected void onDrawKeyTopVisuals(final Key key,final Canvas canvas,
|
||||
final Paint paint, final KeyDrawParams params) {
|
||||
final int keyWidth = key.getWidth();
|
||||
final int keyHeight = key.getHeight();
|
||||
final float centerX = keyWidth * 0.5f;
|
||||
final float centerY = keyHeight * 0.5f;
|
||||
|
||||
// Draw key label.
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
final Drawable icon = (keyboard == null) ? null
|
||||
: key.getIcon(keyboard.mIconsSet, params.mAnimAlpha);
|
||||
float labelX = centerX;
|
||||
float labelBaseline = centerY;
|
||||
final String label = key.getLabel();
|
||||
if (label != null) {
|
||||
paint.setTypeface(key.selectTypeface(params));
|
||||
paint.setTextSize(key.selectTextSize(params));
|
||||
final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
|
||||
final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
|
||||
|
||||
// Vertical label text alignment.
|
||||
labelBaseline = centerY + labelCharHeight / 2.0f;
|
||||
|
||||
// Horizontal label text alignment
|
||||
if (key.isAlignLabelOffCenter()) {
|
||||
// The label is placed off center of the key. Used mainly on "phone number" layout.
|
||||
labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth;
|
||||
paint.setTextAlign(Align.LEFT);
|
||||
} else {
|
||||
labelX = centerX;
|
||||
paint.setTextAlign(Align.CENTER);
|
||||
}
|
||||
if (key.needsAutoXScale()) {
|
||||
final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
|
||||
TypefaceUtils.getStringWidth(label, paint));
|
||||
if (key.needsAutoScale()) {
|
||||
final float autoSize = paint.getTextSize() * ratio;
|
||||
paint.setTextSize(autoSize);
|
||||
} else {
|
||||
paint.setTextScaleX(ratio);
|
||||
}
|
||||
}
|
||||
|
||||
paint.setColor(key.selectTextColor(params));
|
||||
// Set a drop shadow for the text if the shadow radius is positive value.
|
||||
if (mKeyTextShadowRadius > 0.0f) {
|
||||
paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
|
||||
} else {
|
||||
paint.clearShadowLayer();
|
||||
}
|
||||
|
||||
blendAlpha(paint, params.mAnimAlpha);
|
||||
canvas.drawText(label, 0, label.length(), labelX, labelBaseline, paint);
|
||||
// Turn off drop shadow and reset x-scale.
|
||||
paint.clearShadowLayer();
|
||||
paint.setTextScaleX(1.0f);
|
||||
}
|
||||
|
||||
// Draw hint label.
|
||||
final String hintLabel = key.getHintLabel();
|
||||
if (hintLabel != null) {
|
||||
paint.setTextSize(key.selectHintTextSize(params));
|
||||
paint.setColor(key.selectHintTextColor(params));
|
||||
// TODO: Should add a way to specify type face for hint letters
|
||||
paint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
blendAlpha(paint, params.mAnimAlpha);
|
||||
final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
|
||||
final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
|
||||
final float hintX, hintBaseline;
|
||||
if (key.hasHintLabel()) {
|
||||
// The hint label is placed just right of the key label. Used mainly on
|
||||
// "phone number" layout.
|
||||
hintX = labelX + params.mHintLabelOffCenterRatio * labelCharWidth;
|
||||
if (key.isAlignHintLabelToBottom(mDefaultKeyLabelFlags)) {
|
||||
hintBaseline = labelBaseline;
|
||||
} else {
|
||||
hintBaseline = centerY + labelCharHeight / 2.0f;
|
||||
}
|
||||
paint.setTextAlign(Align.LEFT);
|
||||
} else if (key.hasShiftedLetterHint()) {
|
||||
// The hint label is placed at top-right corner of the key. Used mainly on tablet.
|
||||
hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
|
||||
paint.getFontMetrics(mFontMetrics);
|
||||
hintBaseline = -mFontMetrics.top;
|
||||
paint.setTextAlign(Align.CENTER);
|
||||
} else { // key.hasHintLetter()
|
||||
// The hint letter is placed at top-right corner of the key. Used mainly on phone.
|
||||
final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
|
||||
final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
|
||||
hintX = keyWidth - mKeyHintLetterPadding
|
||||
- Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
|
||||
hintBaseline = -paint.ascent();
|
||||
paint.setTextAlign(Align.CENTER);
|
||||
}
|
||||
final float adjustmentY = params.mHintLabelVerticalAdjustment * labelCharHeight;
|
||||
canvas.drawText(
|
||||
hintLabel, 0, hintLabel.length(), hintX, hintBaseline + adjustmentY, paint);
|
||||
}
|
||||
|
||||
// Draw key icon.
|
||||
if (label == null && icon != null) {
|
||||
final int iconWidth;
|
||||
if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
|
||||
iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio);
|
||||
} else {
|
||||
iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
|
||||
}
|
||||
final int iconHeight = icon.getIntrinsicHeight();
|
||||
final int iconY;
|
||||
if (key.isAlignIconToBottom()) {
|
||||
iconY = keyHeight - iconHeight;
|
||||
} else {
|
||||
iconY = (keyHeight - iconHeight) / 2; // Align vertically center.
|
||||
}
|
||||
final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center.
|
||||
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void drawIcon(final Canvas canvas, final Drawable icon,
|
||||
final int x, final int y, final int width, final int height) {
|
||||
canvas.translate(x, y);
|
||||
icon.setBounds(0, 0, width, height);
|
||||
icon.draw(canvas);
|
||||
canvas.translate(-x, -y);
|
||||
}
|
||||
|
||||
public Paint newLabelPaint(final Key key) {
|
||||
final Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
if (key == null) {
|
||||
paint.setTypeface(mKeyDrawParams.mTypeface);
|
||||
paint.setTextSize(mKeyDrawParams.mLabelSize);
|
||||
} else {
|
||||
paint.setColor(key.selectTextColor(mKeyDrawParams));
|
||||
paint.setTypeface(key.selectTypeface(mKeyDrawParams));
|
||||
paint.setTextSize(key.selectTextSize(mKeyDrawParams));
|
||||
}
|
||||
return paint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
|
||||
* because the keyboard renders the keys to an off-screen buffer and an invalidate() only
|
||||
* draws the cached buffer.
|
||||
* @see #invalidateKey(Key)
|
||||
*/
|
||||
public void invalidateAllKeys() {
|
||||
mInvalidatedKeys.clear();
|
||||
mInvalidateAllKeys = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
|
||||
* one key is changing it's content. Any changes that affect the position or size of the key
|
||||
* may not be honored.
|
||||
* @param key key in the attached {@link Keyboard}.
|
||||
* @see #invalidateAllKeys
|
||||
*/
|
||||
public void invalidateKey(final Key key) {
|
||||
if (mInvalidateAllKeys || key == null) {
|
||||
return;
|
||||
}
|
||||
mInvalidatedKeys.add(key);
|
||||
final int x = key.getX() + getPaddingLeft();
|
||||
final int y = key.getY() + getPaddingTop();
|
||||
invalidate(x, y, x + key.getWidth(), y + key.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
freeOffscreenBuffer();
|
||||
}
|
||||
|
||||
public void deallocateMemory() {
|
||||
freeOffscreenBuffer();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,617 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.DrawingPreviewPlacerView;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.DrawingProxy;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyDrawParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyPreviewChoreographer;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyPreviewDrawParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyPreviewView;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.MoreKeySpec;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.NonDistinctMultitouchHelper;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.TimerHandler;
|
||||
import com.colorful.keyboard.theme.latin.RichInputMethodManager;
|
||||
import com.colorful.keyboard.theme.latin.Subtype;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.CoordinateUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.LanguageOnSpacebarUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.LocaleResourceUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.TypefaceUtils;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class MainKeyboardView extends KeyboardView implements MoreKeysPanel.Controller, DrawingProxy {
|
||||
private static final String TAG = MainKeyboardView.class.getSimpleName();
|
||||
|
||||
/** Listener for {@link KeyboardActionListener}. */
|
||||
private KeyboardActionListener mKeyboardActionListener;
|
||||
|
||||
/* Space key and its icon and background. */
|
||||
private Key mSpaceKey;
|
||||
// Stuff to draw language name on spacebar.
|
||||
private final int mLanguageOnSpacebarFinalAlpha;
|
||||
private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
|
||||
private int mLanguageOnSpacebarFormatType;
|
||||
private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
|
||||
private final float mLanguageOnSpacebarTextRatio;
|
||||
private float mLanguageOnSpacebarTextSize;
|
||||
private final int mLanguageOnSpacebarTextColor;
|
||||
// The minimum x-scale to fit the language name on spacebar.
|
||||
private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
|
||||
|
||||
// Stuff to draw altCodeWhileTyping keys.
|
||||
private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
|
||||
private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
|
||||
private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
|
||||
|
||||
// Drawing preview placer view
|
||||
private final DrawingPreviewPlacerView mDrawingPreviewPlacerView;
|
||||
private final int[] mOriginCoords = CoordinateUtils.newInstance();
|
||||
|
||||
// Key preview
|
||||
private final KeyPreviewDrawParams mKeyPreviewDrawParams;
|
||||
private final KeyPreviewChoreographer mKeyPreviewChoreographer;
|
||||
|
||||
// More keys keyboard
|
||||
private final Paint mBackgroundDimAlphaPaint = new Paint();
|
||||
private final View mMoreKeysKeyboardContainer;
|
||||
private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>();
|
||||
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
|
||||
// More keys panel (used by both more keys keyboard and more suggestions view)
|
||||
// TODO: Consider extending to support multiple more keys panels
|
||||
private MoreKeysPanel mMoreKeysPanel;
|
||||
|
||||
private final KeyDetector mKeyDetector;
|
||||
private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
|
||||
|
||||
private final TimerHandler mTimerHandler;
|
||||
private final int mLanguageOnSpacebarHorizontalMargin;
|
||||
|
||||
public MainKeyboardView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.mainKeyboardViewStyle);
|
||||
}
|
||||
|
||||
public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
final DrawingPreviewPlacerView drawingPreviewPlacerView =
|
||||
new DrawingPreviewPlacerView(context, attrs);
|
||||
|
||||
final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
|
||||
final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
|
||||
mTimerHandler = new TimerHandler(this, ignoreAltCodeKeyTimeout);
|
||||
|
||||
final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
|
||||
R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
|
||||
final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
|
||||
R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
|
||||
mKeyDetector = new KeyDetector(
|
||||
keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
|
||||
|
||||
PointerTracker.init(mainKeyboardViewAttr, mTimerHandler, this /* DrawingProxy */);
|
||||
|
||||
final boolean hasDistinctMultitouch = context.getPackageManager()
|
||||
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
|
||||
mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
|
||||
: new NonDistinctMultitouchHelper();
|
||||
|
||||
final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
|
||||
mBackgroundDimAlphaPaint.setColor(Color.BLACK);
|
||||
mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
|
||||
mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
|
||||
R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
|
||||
mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
|
||||
R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
|
||||
mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
|
||||
Constants.Color.ALPHA_OPAQUE);
|
||||
final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
|
||||
final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
|
||||
final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
|
||||
|
||||
mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr);
|
||||
mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams);
|
||||
|
||||
final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
|
||||
mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
|
||||
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
|
||||
|
||||
mainKeyboardViewAttr.recycle();
|
||||
|
||||
mDrawingPreviewPlacerView = drawingPreviewPlacerView;
|
||||
|
||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
mMoreKeysKeyboardContainer = inflater.inflate(moreKeysKeyboardLayoutId, null);
|
||||
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
|
||||
languageOnSpacebarFadeoutAnimatorResId, this);
|
||||
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
|
||||
altCodeKeyWhileTypingFadeoutAnimatorResId, this);
|
||||
mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
|
||||
altCodeKeyWhileTypingFadeinAnimatorResId, this);
|
||||
|
||||
mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
|
||||
|
||||
mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
|
||||
R.dimen.config_language_on_spacebar_horizontal_margin);
|
||||
}
|
||||
|
||||
private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
|
||||
if (resId == 0) {
|
||||
// TODO: Stop returning null.
|
||||
return null;
|
||||
}
|
||||
final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
|
||||
getContext(), resId);
|
||||
if (animator != null) {
|
||||
animator.setTarget(target);
|
||||
}
|
||||
return animator;
|
||||
}
|
||||
|
||||
private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
|
||||
final ObjectAnimator animatorToStart) {
|
||||
if (animatorToCancel == null || animatorToStart == null) {
|
||||
// TODO: Stop using null as a no-operation animator.
|
||||
return;
|
||||
}
|
||||
float startFraction = 0.0f;
|
||||
if (animatorToCancel.isStarted()) {
|
||||
animatorToCancel.cancel();
|
||||
startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
|
||||
}
|
||||
final long startTime = (long)(animatorToStart.getDuration() * startFraction);
|
||||
animatorToStart.start();
|
||||
animatorToStart.setCurrentPlayTime(startTime);
|
||||
}
|
||||
|
||||
// Implements {@link DrawingProxy#startWhileTypingAnimation(int)}.
|
||||
/**
|
||||
* Called when a while-typing-animation should be started.
|
||||
* @param fadeInOrOut {@link DrawingProxy#FADE_IN} starts while-typing-fade-in animation.
|
||||
* {@link DrawingProxy#FADE_OUT} starts while-typing-fade-out animation.
|
||||
*/
|
||||
@Override
|
||||
public void startWhileTypingAnimation(final int fadeInOrOut) {
|
||||
switch (fadeInOrOut) {
|
||||
case DrawingProxy.FADE_IN:
|
||||
cancelAndStartAnimators(
|
||||
mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
|
||||
break;
|
||||
case DrawingProxy.FADE_OUT:
|
||||
cancelAndStartAnimators(
|
||||
mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
|
||||
mLanguageOnSpacebarAnimAlpha = alpha;
|
||||
invalidateKey(mSpaceKey);
|
||||
}
|
||||
|
||||
public void setKeyboardActionListener(final KeyboardActionListener listener) {
|
||||
mKeyboardActionListener = listener;
|
||||
PointerTracker.setKeyboardActionListener(listener);
|
||||
}
|
||||
|
||||
// TODO: We should reconsider which coordinate system should be used to represent keyboard
|
||||
// event.
|
||||
public int getKeyX(final int x) {
|
||||
return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x;
|
||||
}
|
||||
|
||||
// TODO: We should reconsider which coordinate system should be used to represent keyboard
|
||||
// event.
|
||||
public int getKeyY(final int y) {
|
||||
return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a keyboard to this view. The keyboard can be switched at any time and the
|
||||
* view will re-layout itself to accommodate the keyboard.
|
||||
* @see Keyboard
|
||||
* @see #getKeyboard()
|
||||
* @param keyboard the keyboard to display in this view
|
||||
*/
|
||||
@Override
|
||||
public void setKeyboard(final Keyboard keyboard) {
|
||||
// Remove any pending messages, except dismissing preview and key repeat.
|
||||
mTimerHandler.cancelLongPressTimers();
|
||||
super.setKeyboard(keyboard);
|
||||
mKeyDetector.setKeyboard(
|
||||
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
|
||||
PointerTracker.setKeyDetector(mKeyDetector);
|
||||
mMoreKeysKeyboardCache.clear();
|
||||
|
||||
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
|
||||
final int keyHeight = keyboard.mMostCommonKeyHeight;
|
||||
mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the key preview popup. This is a popup that shows a magnified
|
||||
* version of the depressed key. By default the preview is enabled.
|
||||
* @param previewEnabled whether or not to enable the key feedback preview
|
||||
* @param delay the delay after which the preview is dismissed
|
||||
*/
|
||||
public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
|
||||
mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
|
||||
}
|
||||
|
||||
private void locatePreviewPlacerView() {
|
||||
getLocationInWindow(mOriginCoords);
|
||||
mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords);
|
||||
}
|
||||
|
||||
private void installPreviewPlacerView() {
|
||||
final View rootView = getRootView();
|
||||
if (rootView == null) {
|
||||
Log.w(TAG, "Cannot find root view");
|
||||
return;
|
||||
}
|
||||
final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
|
||||
// Note: It'd be very weird if we get null by android.R.id.content.
|
||||
if (windowContentView == null) {
|
||||
Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
|
||||
return;
|
||||
}
|
||||
windowContentView.addView(mDrawingPreviewPlacerView);
|
||||
}
|
||||
|
||||
// Implements {@link DrawingProxy#onKeyPressed(Key,boolean)}.
|
||||
@Override
|
||||
public void onKeyPressed(final Key key, final boolean withPreview) {
|
||||
key.onPressed();
|
||||
invalidateKey(key);
|
||||
if (withPreview && !key.noKeyPreview()) {
|
||||
showKeyPreview(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void showKeyPreview(final Key key) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return;
|
||||
}
|
||||
final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
|
||||
if (!previewParams.isPopupEnabled()) {
|
||||
previewParams.setVisibleOffset(-Math.round(keyboard.mVerticalGap));
|
||||
return;
|
||||
}
|
||||
|
||||
locatePreviewPlacerView();
|
||||
getLocationInWindow(mOriginCoords);
|
||||
mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(),
|
||||
mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated());
|
||||
}
|
||||
|
||||
private void dismissKeyPreviewWithoutDelay(final Key key) {
|
||||
mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
|
||||
invalidateKey(key);
|
||||
}
|
||||
|
||||
// Implements {@link DrawingProxy#onKeyReleased(Key,boolean)}.
|
||||
@Override
|
||||
public void onKeyReleased(final Key key, final boolean withAnimation) {
|
||||
key.onReleased();
|
||||
invalidateKey(key);
|
||||
if (!key.noKeyPreview()) {
|
||||
if (withAnimation) {
|
||||
dismissKeyPreview(key);
|
||||
} else {
|
||||
dismissKeyPreviewWithoutDelay(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissKeyPreview(final Key key) {
|
||||
if (isHardwareAccelerated()) {
|
||||
mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
|
||||
return;
|
||||
}
|
||||
// TODO: Implement preference option to control key preview method and duration.
|
||||
mTimerHandler.postDismissKeyPreview(key, mKeyPreviewDrawParams.getLingerTimeout());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
installPreviewPlacerView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mDrawingPreviewPlacerView.removeAllViews();
|
||||
}
|
||||
|
||||
// Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}.
|
||||
//@Override
|
||||
public MoreKeysPanel showMoreKeysKeyboard(final Key key,
|
||||
final PointerTracker tracker) {
|
||||
final MoreKeySpec[] moreKeys = key.getMoreKeys();
|
||||
if (moreKeys == null) {
|
||||
return null;
|
||||
}
|
||||
Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
|
||||
if (moreKeysKeyboard == null) {
|
||||
// {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
|
||||
// {@link KeyPreviewChoreographer#placeKeyPreview(Key,TextView,KeyboardIconsSet,KeyDrawParams,int,int[]},
|
||||
// though there may be some chances that the value is zero. <code>width == 0</code>
|
||||
// will cause zero-division error at
|
||||
// {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
|
||||
final boolean isSingleMoreKeyWithPreview = mKeyPreviewDrawParams.isPopupEnabled()
|
||||
&& !key.noKeyPreview() && moreKeys.length == 1
|
||||
&& mKeyPreviewDrawParams.getVisibleWidth() > 0;
|
||||
final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder(
|
||||
getContext(), key, getKeyboard(), isSingleMoreKeyWithPreview,
|
||||
mKeyPreviewDrawParams.getVisibleWidth(),
|
||||
mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key));
|
||||
moreKeysKeyboard = builder.build();
|
||||
mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
|
||||
}
|
||||
|
||||
final MoreKeysKeyboardView moreKeysKeyboardView =
|
||||
mMoreKeysKeyboardContainer.findViewById(R.id.more_keys_keyboard_view);
|
||||
moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
|
||||
mMoreKeysKeyboardContainer.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
final int[] lastCoords = CoordinateUtils.newInstance();
|
||||
tracker.getLastCoordinates(lastCoords);
|
||||
final boolean keyPreviewEnabled = mKeyPreviewDrawParams.isPopupEnabled()
|
||||
&& !key.noKeyPreview();
|
||||
// The more keys keyboard is usually horizontally aligned with the center of the parent key.
|
||||
// If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
|
||||
// keys keyboard is placed at the touch point of the parent key.
|
||||
final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
|
||||
? CoordinateUtils.x(lastCoords)
|
||||
: key.getX() + key.getWidth() / 2;
|
||||
// The more keys keyboard is usually vertically aligned with the top edge of the parent key
|
||||
// (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
|
||||
// aligned with the bottom edge of the visible part of the key preview.
|
||||
// {@code mPreviewVisibleOffset} has been set appropriately in
|
||||
// {@link KeyboardView#showKeyPreview(PointerTracker)}.
|
||||
final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset()
|
||||
+ Math.round(moreKeysKeyboard.mBottomPadding);
|
||||
moreKeysKeyboardView.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
|
||||
return moreKeysKeyboardView;
|
||||
}
|
||||
|
||||
public boolean isInDraggingFinger() {
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
return true;
|
||||
}
|
||||
return PointerTracker.isAnyInDraggingFinger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
|
||||
locatePreviewPlacerView();
|
||||
// Dismiss another {@link MoreKeysPanel} that may be being showed.
|
||||
onDismissMoreKeysPanel();
|
||||
// Dismiss all key previews that may be being showed.
|
||||
PointerTracker.setReleasedKeyGraphicsToAllKeys();
|
||||
// Dismiss sliding key input preview that may be being showed.
|
||||
panel.showInParent(mDrawingPreviewPlacerView);
|
||||
mMoreKeysPanel = panel;
|
||||
}
|
||||
|
||||
public boolean isShowingMoreKeysPanel() {
|
||||
return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelMoreKeysPanel() {
|
||||
PointerTracker.dismissAllMoreKeysPanels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissMoreKeysPanel() {
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
mMoreKeysPanel.removeFromParent();
|
||||
mMoreKeysPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void startDoubleTapShiftKeyTimer() {
|
||||
mTimerHandler.startDoubleTapShiftKeyTimer();
|
||||
}
|
||||
|
||||
public void cancelDoubleTapShiftKeyTimer() {
|
||||
mTimerHandler.cancelDoubleTapShiftKeyTimer();
|
||||
}
|
||||
|
||||
public boolean isInDoubleTapShiftKeyTimeout() {
|
||||
return mTimerHandler.isInDoubleTapShiftKeyTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(final MotionEvent event) {
|
||||
if (getKeyboard() == null) {
|
||||
return false;
|
||||
}
|
||||
if (mNonDistinctMultitouchHelper != null) {
|
||||
if (event.getPointerCount() > 1 && mTimerHandler.isInKeyRepeat()) {
|
||||
// Key repeating timer will be canceled if 2 or more keys are in action.
|
||||
mTimerHandler.cancelKeyRepeatTimers();
|
||||
}
|
||||
// Non distinct multitouch screen support
|
||||
mNonDistinctMultitouchHelper.processMotionEvent(event, mKeyDetector);
|
||||
return true;
|
||||
}
|
||||
return processMotionEvent(event);
|
||||
}
|
||||
|
||||
public boolean processMotionEvent(final MotionEvent event) {
|
||||
final int index = event.getActionIndex();
|
||||
final int id = event.getPointerId(index);
|
||||
final PointerTracker tracker = PointerTracker.getPointerTracker(id);
|
||||
// When a more keys panel is showing, we should ignore other fingers' single touch events
|
||||
// other than the finger that is showing the more keys panel.
|
||||
if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
|
||||
&& PointerTracker.getActivePointerTrackerCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
tracker.processMotionEvent(event, mKeyDetector);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cancelAllOngoingEvents() {
|
||||
mTimerHandler.cancelAllMessages();
|
||||
PointerTracker.setReleasedKeyGraphicsToAllKeys();
|
||||
PointerTracker.dismissAllMoreKeysPanels();
|
||||
PointerTracker.cancelAllPointerTrackers();
|
||||
}
|
||||
|
||||
public void closing() {
|
||||
cancelAllOngoingEvents();
|
||||
mMoreKeysKeyboardCache.clear();
|
||||
}
|
||||
|
||||
public void onHideWindow() {
|
||||
onDismissMoreKeysPanel();
|
||||
}
|
||||
|
||||
public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
|
||||
final int languageOnSpacebarFormatType) {
|
||||
if (subtypeChanged) {
|
||||
KeyPreviewView.clearTextCache();
|
||||
}
|
||||
mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
|
||||
final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
|
||||
if (animator == null) {
|
||||
mLanguageOnSpacebarFormatType = LanguageOnSpacebarUtils.FORMAT_TYPE_NONE;
|
||||
} else {
|
||||
if (subtypeChanged
|
||||
&& languageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) {
|
||||
setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
|
||||
if (animator.isStarted()) {
|
||||
animator.cancel();
|
||||
}
|
||||
animator.start();
|
||||
} else {
|
||||
if (!animator.isStarted()) {
|
||||
mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidateKey(mSpaceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
|
||||
final KeyDrawParams params) {
|
||||
if (key.altCodeWhileTyping()) {
|
||||
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
|
||||
}
|
||||
super.onDrawKeyTopVisuals(key, canvas, paint, params);
|
||||
final int code = key.getCode();
|
||||
if (code == Constants.CODE_SPACE) {
|
||||
// If more than one language is enabled in current input method
|
||||
final RichInputMethodManager imm = RichInputMethodManager.getInstance();
|
||||
if (imm.hasMultipleEnabledSubtypes()) {
|
||||
drawLanguageOnSpacebar(key, canvas, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
|
||||
final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
|
||||
paint.setTextScaleX(1.0f);
|
||||
final float textWidth = TypefaceUtils.getStringWidth(text, paint);
|
||||
if (textWidth < width) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final float scaleX = maxTextWidth / textWidth;
|
||||
if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
paint.setTextScaleX(scaleX);
|
||||
return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
|
||||
}
|
||||
|
||||
// Layout language name on spacebar.
|
||||
private String layoutLanguageOnSpacebar(final Paint paint,
|
||||
final Subtype subtype, final int width) {
|
||||
// Choose appropriate language name to fit into the width.
|
||||
if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarUtils.FORMAT_TYPE_FULL_LOCALE) {
|
||||
final String fullText =
|
||||
LocaleResourceUtils.getLocaleDisplayNameInLocale(subtype.getLocale());
|
||||
if (fitsTextIntoWidth(width, fullText, paint)) {
|
||||
return fullText;
|
||||
}
|
||||
}
|
||||
|
||||
final String middleText =
|
||||
LocaleResourceUtils.getLanguageDisplayNameInLocale(subtype.getLocale());
|
||||
if (fitsTextIntoWidth(width, middleText, paint)) {
|
||||
return middleText;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return;
|
||||
}
|
||||
final int width = key.getWidth();
|
||||
final int height = key.getHeight();
|
||||
paint.setTextAlign(Align.CENTER);
|
||||
paint.setTypeface(Typeface.DEFAULT);
|
||||
paint.setTextSize(mLanguageOnSpacebarTextSize);
|
||||
final String language = layoutLanguageOnSpacebar(paint, keyboard.mId.mSubtype, width);
|
||||
// Draw language text with shadow
|
||||
final float descent = paint.descent();
|
||||
final float textHeight = -paint.ascent() + descent;
|
||||
final float baseline = height / 2 + textHeight / 2;
|
||||
paint.setColor(mLanguageOnSpacebarTextColor);
|
||||
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
|
||||
canvas.drawText(language, width / 2, baseline - descent, paint);
|
||||
paint.clearShadowLayer();
|
||||
paint.setTextScaleX(1.0f);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
public final class MoreKeysDetector extends KeyDetector {
|
||||
private final int mSlideAllowanceSquare;
|
||||
private final int mSlideAllowanceSquareTop;
|
||||
|
||||
public MoreKeysDetector(float slideAllowance) {
|
||||
super();
|
||||
mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance);
|
||||
// Top slide allowance is slightly longer (sqrt(2) times) than other edges.
|
||||
mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean alwaysAllowsKeySelectionByDraggingFinger() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key detectHitKey(final int x, final int y) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return null;
|
||||
}
|
||||
final int touchX = getTouchX(x);
|
||||
final int touchY = getTouchY(y);
|
||||
|
||||
Key nearestKey = null;
|
||||
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
|
||||
for (final Key key : keyboard.getSortedKeys()) {
|
||||
final int dist = key.squaredDistanceToHitboxEdge(touchX, touchY);
|
||||
if (dist < nearestDist) {
|
||||
nearestKey = key;
|
||||
nearestDist = dist;
|
||||
}
|
||||
}
|
||||
return nearestKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.util.Log;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardBuilder;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.KeyboardParams;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.MoreKeySpec;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.TypefaceUtils;
|
||||
|
||||
|
||||
public final class MoreKeysKeyboard extends Keyboard {
|
||||
private static final String TAG = MoreKeysKeyboard.class.getSimpleName();
|
||||
private final int mDefaultKeyCoordX;
|
||||
private static final float FLOAT_THRESHOLD = 0.0001f;
|
||||
|
||||
MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
|
||||
super(params);
|
||||
mDefaultKeyCoordX = Math.round(params.getDefaultKeyCoordX() + params.mOffsetX
|
||||
+ (params.mDefaultKeyPaddedWidth - params.mHorizontalGap) / 2);
|
||||
}
|
||||
|
||||
public int getDefaultCoordX() {
|
||||
return mDefaultKeyCoordX;
|
||||
}
|
||||
|
||||
static class MoreKeysKeyboardParams extends KeyboardParams {
|
||||
public boolean mIsMoreKeysFixedOrder;
|
||||
/* package */int mTopRowAdjustment;
|
||||
public int mNumRows;
|
||||
public int mNumColumns;
|
||||
public int mTopKeys;
|
||||
public int mLeftKeys;
|
||||
public int mRightKeys; // includes default key.
|
||||
public float mColumnWidth;
|
||||
public float mOffsetX;
|
||||
|
||||
public MoreKeysKeyboardParams() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set keyboard parameters of more keys keyboard.
|
||||
*
|
||||
* @param numKeys number of keys in this more keys keyboard.
|
||||
* @param numColumn number of columns of this more keys keyboard.
|
||||
* @param keyPaddedWidth more keys keyboard key width in pixel, including horizontal gap.
|
||||
* @param rowHeight more keys keyboard row height in pixel, including vertical gap.
|
||||
* @param coordXInParent coordinate x of the key preview in parent keyboard.
|
||||
* @param parentKeyboardWidth parent keyboard width in pixel.
|
||||
* @param isMoreKeysFixedColumn true if more keys keyboard should have
|
||||
* <code>numColumn</code> columns. Otherwise more keys keyboard should have
|
||||
* <code>numColumn</code> columns at most.
|
||||
* @param isMoreKeysFixedOrder true if the order of more keys is determined by the order in
|
||||
* the more keys' specification. Otherwise the order of more keys is automatically
|
||||
* determined.
|
||||
*/
|
||||
public void setParameters(final int numKeys, final int numColumn,
|
||||
final float keyPaddedWidth, final float rowHeight,
|
||||
final float coordXInParent, final int parentKeyboardWidth,
|
||||
final boolean isMoreKeysFixedColumn,
|
||||
final boolean isMoreKeysFixedOrder) {
|
||||
// Add the horizontal padding because there is no horizontal gap on the outside edge,
|
||||
// but it is included in the key width, so this compensates for simple division and
|
||||
// comparison.
|
||||
final float availableWidth = parentKeyboardWidth - mLeftPadding - mRightPadding
|
||||
+ mHorizontalGap;
|
||||
if (availableWidth < keyPaddedWidth) {
|
||||
throw new IllegalArgumentException("Keyboard is too small to hold more keys: "
|
||||
+ availableWidth + " " + keyPaddedWidth);
|
||||
}
|
||||
mIsMoreKeysFixedOrder = isMoreKeysFixedOrder;
|
||||
mDefaultKeyPaddedWidth = keyPaddedWidth;
|
||||
mDefaultRowHeight = rowHeight;
|
||||
|
||||
final int maxColumns = getMaxKeys(availableWidth, keyPaddedWidth);
|
||||
if (isMoreKeysFixedColumn) {
|
||||
int requestedNumColumns = Math.min(numKeys, numColumn);
|
||||
if (maxColumns < requestedNumColumns) {
|
||||
Log.e(TAG, "Keyboard is too small to hold the requested more keys columns: "
|
||||
+ availableWidth + " " + keyPaddedWidth + " " + numKeys + " "
|
||||
+ requestedNumColumns + ". The number of columns was reduced.");
|
||||
mNumColumns = maxColumns;
|
||||
} else {
|
||||
mNumColumns = requestedNumColumns;
|
||||
}
|
||||
mNumRows = getNumRows(numKeys, mNumColumns);
|
||||
} else {
|
||||
int defaultNumColumns = Math.min(maxColumns, numColumn);
|
||||
mNumRows = getNumRows(numKeys, defaultNumColumns);
|
||||
mNumColumns = getOptimizedColumns(numKeys, defaultNumColumns, mNumRows);
|
||||
}
|
||||
final int topKeys = numKeys % mNumColumns;
|
||||
mTopKeys = topKeys == 0 ? mNumColumns : topKeys;
|
||||
|
||||
final int numLeftKeys = (mNumColumns - 1) / 2;
|
||||
final int numRightKeys = mNumColumns - numLeftKeys; // including default key.
|
||||
// Determine the maximum number of keys we can lay out on both side of the left edge of
|
||||
// a key centered on the parent key. Also, account for horizontal padding because there
|
||||
// is no horizontal gap on the outside edge.
|
||||
final float leftWidth = Math.max(coordXInParent - mLeftPadding - keyPaddedWidth / 2
|
||||
+ mHorizontalGap / 2, 0);
|
||||
final float rightWidth = Math.max(parentKeyboardWidth - coordXInParent
|
||||
+ keyPaddedWidth / 2 - mRightPadding + mHorizontalGap / 2, 0);
|
||||
int maxLeftKeys = getMaxKeys(leftWidth, keyPaddedWidth);
|
||||
int maxRightKeys = getMaxKeys(rightWidth, keyPaddedWidth);
|
||||
// Handle the case where the number of columns fits but doesn't have enough room
|
||||
// for the default key to be centered on the parent key.
|
||||
if (numKeys >= mNumColumns && mNumColumns == maxColumns
|
||||
&& maxLeftKeys + maxRightKeys < maxColumns) {
|
||||
final float extraLeft = leftWidth - maxLeftKeys * keyPaddedWidth;
|
||||
final float extraRight = rightWidth - maxRightKeys * keyPaddedWidth;
|
||||
// Put the extra key on whatever side has more space
|
||||
if (extraLeft > extraRight) {
|
||||
maxLeftKeys++;
|
||||
} else {
|
||||
maxRightKeys++;
|
||||
}
|
||||
}
|
||||
|
||||
int leftKeys, rightKeys;
|
||||
if (numLeftKeys > maxLeftKeys) {
|
||||
leftKeys = maxLeftKeys;
|
||||
rightKeys = mNumColumns - leftKeys;
|
||||
} else if (numRightKeys > maxRightKeys) {
|
||||
// Make sure the default key is included even if it doesn't exactly fit (the default
|
||||
// key just won't be completely centered on the parent key)
|
||||
rightKeys = Math.max(maxRightKeys, 1);
|
||||
leftKeys = mNumColumns - rightKeys;
|
||||
} else {
|
||||
leftKeys = numLeftKeys;
|
||||
rightKeys = numRightKeys;
|
||||
}
|
||||
mLeftKeys = leftKeys;
|
||||
mRightKeys = rightKeys;
|
||||
|
||||
// Adjustment of the top row.
|
||||
mTopRowAdjustment = getTopRowAdjustment();
|
||||
mColumnWidth = mDefaultKeyPaddedWidth;
|
||||
mBaseWidth = mNumColumns * mColumnWidth;
|
||||
// Need to subtract the right most column's gutter only.
|
||||
mOccupiedWidth = Math.round(mBaseWidth + mLeftPadding + mRightPadding - mHorizontalGap);
|
||||
mBaseHeight = mNumRows * mDefaultRowHeight;
|
||||
// Need to subtract the bottom row's gutter only.
|
||||
mOccupiedHeight = Math.round(mBaseHeight + mTopPadding + mBottomPadding - mVerticalGap);
|
||||
|
||||
// The proximity grid size can be reduced because the more keys keyboard is probably
|
||||
// smaller and doesn't need extra precision from smaller cells.
|
||||
mGridWidth = Math.min(mGridWidth, mNumColumns);
|
||||
mGridHeight = Math.min(mGridHeight, mNumRows);
|
||||
}
|
||||
|
||||
private int getTopRowAdjustment() {
|
||||
final int numOffCenterKeys = Math.abs(mRightKeys - 1 - mLeftKeys);
|
||||
// Don't center if there are more keys in the top row than can be centered around the
|
||||
// default more key or if there is an odd number of keys in the top row (already will
|
||||
// be centered).
|
||||
if (mTopKeys > mNumColumns - numOffCenterKeys || mTopKeys % 2 == 1) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return key position according to column count (0 is default).
|
||||
/* package */int getColumnPos(final int n) {
|
||||
return mIsMoreKeysFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
|
||||
}
|
||||
|
||||
private int getFixedOrderColumnPos(final int n) {
|
||||
final int col = n % mNumColumns;
|
||||
final int row = n / mNumColumns;
|
||||
if (!isTopRow(row)) {
|
||||
return col - mLeftKeys;
|
||||
}
|
||||
final int rightSideKeys = mTopKeys / 2;
|
||||
final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
|
||||
final int pos = col - leftSideKeys;
|
||||
final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
|
||||
final int numRightKeys = mRightKeys - 1;
|
||||
if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
|
||||
return pos;
|
||||
} else if (numRightKeys < rightSideKeys) {
|
||||
return pos - (rightSideKeys - numRightKeys);
|
||||
} else { // numLeftKeys < leftSideKeys
|
||||
return pos + (leftSideKeys - numLeftKeys);
|
||||
}
|
||||
}
|
||||
|
||||
private int getAutomaticColumnPos(final int n) {
|
||||
final int col = n % mNumColumns;
|
||||
final int row = n / mNumColumns;
|
||||
int leftKeys = mLeftKeys;
|
||||
if (isTopRow(row)) {
|
||||
leftKeys += mTopRowAdjustment;
|
||||
}
|
||||
if (col == 0) {
|
||||
// default position.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
int right = 1; // include default position key.
|
||||
int left = 0;
|
||||
int i = 0;
|
||||
while (true) {
|
||||
// Assign right key if available.
|
||||
if (right < mRightKeys) {
|
||||
pos = right;
|
||||
right++;
|
||||
i++;
|
||||
}
|
||||
if (i >= col)
|
||||
break;
|
||||
// Assign left key if available.
|
||||
if (left < leftKeys) {
|
||||
left++;
|
||||
pos = -left;
|
||||
i++;
|
||||
}
|
||||
if (i >= col)
|
||||
break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
|
||||
final int remainings = numKeys % numColumns;
|
||||
return remainings == 0 ? 0 : numColumns - remainings;
|
||||
}
|
||||
|
||||
private static int getOptimizedColumns(final int numKeys, final int maxColumns,
|
||||
final int numRows) {
|
||||
int numColumns = Math.min(numKeys, maxColumns);
|
||||
while (getTopRowEmptySlots(numKeys, numColumns) >= numRows) {
|
||||
numColumns--;
|
||||
}
|
||||
return numColumns;
|
||||
}
|
||||
|
||||
private static int getNumRows(final int numKeys, final int numColumn) {
|
||||
return (numKeys + numColumn - 1) / numColumn;
|
||||
}
|
||||
|
||||
private static int getMaxKeys(final float keyboardWidth, final float keyPaddedWidth) {
|
||||
// This is effectively the same as returning (int)(keyboardWidth / keyPaddedWidth)
|
||||
// except this handles floating point errors better since rounding in the wrong
|
||||
// directing here doesn't cause an issue, but truncating incorrectly from an error
|
||||
// could be a problem (eg: the keyboard width is an exact multiple of the key width
|
||||
// could return one less than the expected number).
|
||||
final int maxKeys = Math.round(keyboardWidth / keyPaddedWidth);
|
||||
if (maxKeys * keyPaddedWidth > keyboardWidth + FLOAT_THRESHOLD) {
|
||||
return maxKeys - 1;
|
||||
}
|
||||
return maxKeys;
|
||||
}
|
||||
|
||||
public float getDefaultKeyCoordX() {
|
||||
return mLeftKeys * mColumnWidth + mLeftPadding;
|
||||
}
|
||||
|
||||
public float getX(final int n, final int row) {
|
||||
final float x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
|
||||
if (isTopRow(row)) {
|
||||
return x + mTopRowAdjustment * (mColumnWidth / 2);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY(final int row) {
|
||||
return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
|
||||
}
|
||||
|
||||
private boolean isTopRow(final int rowCount) {
|
||||
return mNumRows > 1 && rowCount == mNumRows - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
|
||||
private final Key mParentKey;
|
||||
|
||||
private static final float LABEL_PADDING_RATIO = 0.2f;
|
||||
|
||||
/**
|
||||
* The builder of MoreKeysKeyboard.
|
||||
* @param context the context of {@link MoreKeysKeyboardView}.
|
||||
* @param key the {@link Key} that invokes more keys keyboard.
|
||||
* @param keyboard the {@link Keyboard} that contains the parentKey.
|
||||
* @param isSingleMoreKeyWithPreview true if the <code>key</code> has just a single
|
||||
* "more key" and its key popup preview is enabled.
|
||||
* @param keyPreviewVisibleWidth the width of visible part of key popup preview.
|
||||
* @param keyPreviewVisibleHeight the height of visible part of key popup preview
|
||||
* @param paintToMeasure the {@link Paint} object to measure a "more key" width
|
||||
*/
|
||||
public Builder(final Context context, final Key key, final Keyboard keyboard,
|
||||
final boolean isSingleMoreKeyWithPreview, final int keyPreviewVisibleWidth,
|
||||
final int keyPreviewVisibleHeight, final Paint paintToMeasure) {
|
||||
super(context, new MoreKeysKeyboardParams());
|
||||
load(keyboard.mMoreKeysTemplate, keyboard.mId);
|
||||
|
||||
// TODO: More keys keyboard's vertical gap is currently calculated heuristically.
|
||||
// Should revise the algorithm.
|
||||
mParams.mVerticalGap = keyboard.mVerticalGap / 2;
|
||||
// This {@link MoreKeysKeyboard} is invoked from the <code>key</code>.
|
||||
mParentKey = key;
|
||||
|
||||
final float keyPaddedWidth, rowHeight;
|
||||
if (isSingleMoreKeyWithPreview) {
|
||||
// Use pre-computed width and height if this more keys keyboard has only one key to
|
||||
// mitigate visual flicker between key preview and more keys keyboard.
|
||||
// The bottom paddings don't need to be considered because the vertical positions
|
||||
// of both backgrounds and the keyboard were already adjusted with their bottom
|
||||
// paddings deducted. The keyboard's left/right/top paddings do need to be deducted
|
||||
// so the key including the paddings matches the key preview.
|
||||
final float keyboardHorizontalPadding = mParams.mLeftPadding
|
||||
+ mParams.mRightPadding;
|
||||
final float baseKeyPaddedWidth = keyPreviewVisibleWidth + mParams.mHorizontalGap;
|
||||
if (keyboardHorizontalPadding > baseKeyPaddedWidth - FLOAT_THRESHOLD) {
|
||||
// If the padding doesn't fit we'll just add it outside of the key preview.
|
||||
keyPaddedWidth = baseKeyPaddedWidth;
|
||||
} else {
|
||||
keyPaddedWidth = baseKeyPaddedWidth - keyboardHorizontalPadding;
|
||||
// Keep the more keys keyboard with uneven padding lined up with the key
|
||||
// preview rather than centering the more keys keyboard's key with the parent
|
||||
// key.
|
||||
mParams.mOffsetX = (mParams.mRightPadding - mParams.mLeftPadding) / 2;
|
||||
}
|
||||
final float baseKeyPaddedHeight = keyPreviewVisibleHeight + mParams.mVerticalGap;
|
||||
if (mParams.mTopPadding > baseKeyPaddedHeight - FLOAT_THRESHOLD) {
|
||||
// If the padding doesn't fit we'll just add it outside of the key preview.
|
||||
rowHeight = baseKeyPaddedHeight;
|
||||
} else {
|
||||
rowHeight = baseKeyPaddedHeight - mParams.mTopPadding;
|
||||
}
|
||||
} else {
|
||||
final float defaultKeyWidth = mParams.mDefaultKeyPaddedWidth
|
||||
- mParams.mHorizontalGap;
|
||||
final float padding = context.getResources().getDimension(
|
||||
R.dimen.config_more_keys_keyboard_key_horizontal_padding)
|
||||
+ (key.hasLabelsInMoreKeys()
|
||||
? defaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
|
||||
keyPaddedWidth = getMaxKeyWidth(key, defaultKeyWidth, padding, paintToMeasure)
|
||||
+ mParams.mHorizontalGap;
|
||||
rowHeight = keyboard.mMostCommonKeyHeight + keyboard.mVerticalGap;
|
||||
}
|
||||
final MoreKeySpec[] moreKeys = key.getMoreKeys();
|
||||
mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyPaddedWidth,
|
||||
rowHeight, key.getX() + key.getWidth() / 2f, keyboard.mId.mWidth,
|
||||
key.isMoreKeysFixedColumn(), key.isMoreKeysFixedOrder());
|
||||
}
|
||||
|
||||
private static float getMaxKeyWidth(final Key parentKey, final float minKeyWidth,
|
||||
final float padding, final Paint paint) {
|
||||
float maxWidth = minKeyWidth;
|
||||
for (final MoreKeySpec spec : parentKey.getMoreKeys()) {
|
||||
final String label = spec.mLabel;
|
||||
// If the label is single letter, minKeyWidth is enough to hold the label.
|
||||
if (label != null && StringUtils.codePointCount(label) > 1) {
|
||||
maxWidth = Math.max(maxWidth,
|
||||
TypefaceUtils.getStringWidth(label, paint) + padding);
|
||||
}
|
||||
}
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MoreKeysKeyboard build() {
|
||||
final MoreKeysKeyboardParams params = mParams;
|
||||
final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags();
|
||||
final MoreKeySpec[] moreKeys = mParentKey.getMoreKeys();
|
||||
for (int n = 0; n < moreKeys.length; n++) {
|
||||
final MoreKeySpec moreKeySpec = moreKeys[n];
|
||||
final int row = n / params.mNumColumns;
|
||||
final float width = params.mDefaultKeyPaddedWidth - params.mHorizontalGap;
|
||||
final float height = params.mDefaultRowHeight - params.mVerticalGap;
|
||||
final float keyLeftEdge = params.getX(n, row);
|
||||
final float keyTopEdge = params.getY(row);
|
||||
final float keyRightEdge = keyLeftEdge + width;
|
||||
final float keyBottomEdge = keyTopEdge + height;
|
||||
|
||||
final float keyboardLeftEdge = params.mLeftPadding;
|
||||
final float keyboardRightEdge = params.mOccupiedWidth - params.mRightPadding;
|
||||
final float keyboardTopEdge = params.mTopPadding;
|
||||
final float keyboardBottomEdge = params.mOccupiedHeight - params.mBottomPadding;
|
||||
|
||||
final float keyLeftPadding = keyLeftEdge < keyboardLeftEdge + FLOAT_THRESHOLD
|
||||
? params.mLeftPadding : params.mHorizontalGap / 2;
|
||||
final float keyRightPadding = keyRightEdge > keyboardRightEdge - FLOAT_THRESHOLD
|
||||
? params.mRightPadding : params.mHorizontalGap / 2;
|
||||
final float keyTopPadding = keyTopEdge < keyboardTopEdge + FLOAT_THRESHOLD
|
||||
? params.mTopPadding : params.mVerticalGap / 2;
|
||||
final float keyBottomPadding = keyBottomEdge > keyboardBottomEdge - FLOAT_THRESHOLD
|
||||
? params.mBottomPadding : params.mVerticalGap / 2;
|
||||
|
||||
final Key key = moreKeySpec.buildKey(keyLeftEdge, keyTopEdge, width, height,
|
||||
keyLeftPadding, keyRightPadding, keyTopPadding, keyBottomPadding,
|
||||
moreKeyFlags);
|
||||
params.onAddKey(key);
|
||||
}
|
||||
return new MoreKeysKeyboard(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.CoordinateUtils;
|
||||
|
||||
/**
|
||||
* A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
|
||||
* detecting key presses and touch movements.
|
||||
*/
|
||||
public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
|
||||
private final int[] mCoordinates = CoordinateUtils.newInstance();
|
||||
|
||||
protected final KeyDetector mKeyDetector;
|
||||
private Controller mController = EMPTY_CONTROLLER;
|
||||
protected KeyboardActionListener mListener;
|
||||
private int mOriginX;
|
||||
private int mOriginY;
|
||||
private Key mCurrentKey;
|
||||
|
||||
private int mActivePointerId;
|
||||
|
||||
public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
|
||||
}
|
||||
|
||||
public MoreKeysKeyboardView(final Context context, final AttributeSet attrs,
|
||||
final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
final TypedArray moreKeysKeyboardViewAttr = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.MoreKeysKeyboardView, defStyle, R.style.MoreKeysKeyboardView);
|
||||
moreKeysKeyboardViewAttr.recycle();
|
||||
mKeyDetector = new MoreKeysDetector(getResources().getDimension(
|
||||
R.dimen.config_more_keys_keyboard_slide_allowance));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard != null) {
|
||||
final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
|
||||
final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
|
||||
setMeasuredDimension(width, height);
|
||||
} else {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyboard(final Keyboard keyboard) {
|
||||
super.setKeyboard(keyboard);
|
||||
mKeyDetector.setKeyboard(
|
||||
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMoreKeysPanel(final View parentView, final Controller controller,
|
||||
final int pointX, final int pointY, final KeyboardActionListener listener) {
|
||||
mController = controller;
|
||||
mListener = listener;
|
||||
final View container = getContainerView();
|
||||
// The coordinates of panel's left-top corner in parentView's coordinate system.
|
||||
// We need to consider background drawable paddings.
|
||||
final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft();
|
||||
final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
|
||||
+ getPaddingBottom();
|
||||
|
||||
parentView.getLocationInWindow(mCoordinates);
|
||||
// Ensure the horizontal position of the panel does not extend past the parentView edges.
|
||||
final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
|
||||
final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
|
||||
final int panelY = y + CoordinateUtils.y(mCoordinates);
|
||||
container.setX(panelX);
|
||||
container.setY(panelY);
|
||||
|
||||
mOriginX = x + container.getPaddingLeft();
|
||||
mOriginY = y + container.getPaddingTop();
|
||||
controller.onShowMoreKeysPanel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default x coordinate for showing this panel.
|
||||
*/
|
||||
protected int getDefaultCoordX() {
|
||||
return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownEvent(final int x, final int y, final int pointerId) {
|
||||
mActivePointerId = pointerId;
|
||||
mCurrentKey = detectKey(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveEvent(final int x, final int y, final int pointerId) {
|
||||
if (mActivePointerId != pointerId) {
|
||||
return;
|
||||
}
|
||||
final boolean hasOldKey = (mCurrentKey != null);
|
||||
mCurrentKey = detectKey(x, y);
|
||||
if (hasOldKey && mCurrentKey == null) {
|
||||
// A more keys keyboard is canceled when detecting no key.
|
||||
mController.onCancelMoreKeysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpEvent(final int x, final int y, final int pointerId) {
|
||||
if (mActivePointerId != pointerId) {
|
||||
return;
|
||||
}
|
||||
// Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and
|
||||
// the following up event share the same coordinates.
|
||||
mCurrentKey = detectKey(x, y);
|
||||
if (mCurrentKey != null) {
|
||||
updateReleaseKeyGraphics(mCurrentKey);
|
||||
onKeyInput(mCurrentKey);
|
||||
mCurrentKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the specific action for this panel when the user presses a key on the panel.
|
||||
*/
|
||||
protected void onKeyInput(final Key key) {
|
||||
final int code = key.getCode();
|
||||
if (code == Constants.CODE_OUTPUT_TEXT) {
|
||||
mListener.onTextInput(mCurrentKey.getOutputText());
|
||||
} else if (code != Constants.CODE_UNSPECIFIED) {
|
||||
mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
|
||||
}
|
||||
}
|
||||
|
||||
private Key detectKey(int x, int y) {
|
||||
final Key oldKey = mCurrentKey;
|
||||
final Key newKey = mKeyDetector.detectHitKey(x, y);
|
||||
if (newKey == oldKey) {
|
||||
return newKey;
|
||||
}
|
||||
// A new key is detected.
|
||||
if (oldKey != null) {
|
||||
updateReleaseKeyGraphics(oldKey);
|
||||
invalidateKey(oldKey);
|
||||
}
|
||||
if (newKey != null) {
|
||||
updatePressKeyGraphics(newKey);
|
||||
invalidateKey(newKey);
|
||||
}
|
||||
return newKey;
|
||||
}
|
||||
|
||||
private void updateReleaseKeyGraphics(final Key key) {
|
||||
key.onReleased();
|
||||
invalidateKey(key);
|
||||
}
|
||||
|
||||
private void updatePressKeyGraphics(final Key key) {
|
||||
key.onPressed();
|
||||
invalidateKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismissMoreKeysPanel() {
|
||||
if (!isShowingInParent()) {
|
||||
return;
|
||||
}
|
||||
mController.onDismissMoreKeysPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int translateX(final int x) {
|
||||
return x - mOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int translateY(final int y) {
|
||||
return y - mOriginY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(final MotionEvent me) {
|
||||
final int action = me.getActionMasked();
|
||||
final int index = me.getActionIndex();
|
||||
final int x = (int)me.getX(index);
|
||||
final int y = (int)me.getY(index);
|
||||
final int pointerId = me.getPointerId(index);
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
onDownEvent(x, y, pointerId);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onUpEvent(x, y, pointerId);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
onMoveEvent(x, y, pointerId);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private View getContainerView() {
|
||||
return (View)getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showInParent(final ViewGroup parentView) {
|
||||
removeFromParent();
|
||||
parentView.addView(getContainerView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromParent() {
|
||||
final View containerView = getContainerView();
|
||||
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
|
||||
if (currentParent != null) {
|
||||
currentParent.removeView(containerView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowingInParent() {
|
||||
return (getContainerView().getParent() != null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public interface MoreKeysPanel {
|
||||
interface Controller {
|
||||
/**
|
||||
* Add the {@link MoreKeysPanel} to the target view.
|
||||
* @param panel the panel to be shown.
|
||||
*/
|
||||
void onShowMoreKeysPanel(final MoreKeysPanel panel);
|
||||
|
||||
/**
|
||||
* Remove the current {@link MoreKeysPanel} from the target view.
|
||||
*/
|
||||
void onDismissMoreKeysPanel();
|
||||
|
||||
/**
|
||||
* Instructs the parent to cancel the panel (e.g., when entering a different input mode).
|
||||
*/
|
||||
void onCancelMoreKeysPanel();
|
||||
}
|
||||
|
||||
Controller EMPTY_CONTROLLER = new Controller() {
|
||||
@Override
|
||||
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {}
|
||||
@Override
|
||||
public void onDismissMoreKeysPanel() {}
|
||||
@Override
|
||||
public void onCancelMoreKeysPanel() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the
|
||||
* controller's onShowMoreKeysPanel to add the panel's container view.
|
||||
*
|
||||
* @param parentView the parent view of this {@link MoreKeysPanel}
|
||||
* @param controller the controller that can dismiss this {@link MoreKeysPanel}
|
||||
* @param pointX x coordinate of this {@link MoreKeysPanel}
|
||||
* @param pointY y coordinate of this {@link MoreKeysPanel}
|
||||
* @param listener the listener that will receive keyboard action from this
|
||||
* {@link MoreKeysPanel}.
|
||||
*/
|
||||
// TODO: Currently the MoreKeysPanel is inside a container view that is added to the parent.
|
||||
// Consider the simpler approach of placing the MoreKeysPanel itself into the parent view.
|
||||
void showMoreKeysPanel(View parentView, Controller controller, int pointX,
|
||||
int pointY, KeyboardActionListener listener);
|
||||
|
||||
/**
|
||||
* Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove
|
||||
* the panel's container view.
|
||||
*/
|
||||
void dismissMoreKeysPanel();
|
||||
|
||||
/**
|
||||
* Process a move event on the more keys panel.
|
||||
*
|
||||
* @param x translated x coordinate of the touch point
|
||||
* @param y translated y coordinate of the touch point
|
||||
* @param pointerId pointer id touch point
|
||||
*/
|
||||
void onMoveEvent(final int x, final int y, final int pointerId);
|
||||
|
||||
/**
|
||||
* Process a down event on the more keys panel.
|
||||
*
|
||||
* @param x translated x coordinate of the touch point
|
||||
* @param y translated y coordinate of the touch point
|
||||
* @param pointerId pointer id touch point
|
||||
*/
|
||||
void onDownEvent(final int x, final int y, final int pointerId);
|
||||
|
||||
/**
|
||||
* Process an up event on the more keys panel.
|
||||
*
|
||||
* @param x translated x coordinate of the touch point
|
||||
* @param y translated y coordinate of the touch point
|
||||
* @param pointerId pointer id touch point
|
||||
*/
|
||||
void onUpEvent(final int x, final int y, final int pointerId);
|
||||
|
||||
/**
|
||||
* Translate X-coordinate of touch event to the local X-coordinate of this
|
||||
* {@link MoreKeysPanel}.
|
||||
*
|
||||
* @param x the global X-coordinate
|
||||
* @return the local X-coordinate to this {@link MoreKeysPanel}
|
||||
*/
|
||||
int translateX(int x);
|
||||
|
||||
/**
|
||||
* Translate Y-coordinate of touch event to the local Y-coordinate of this
|
||||
* {@link MoreKeysPanel}.
|
||||
*
|
||||
* @param y the global Y-coordinate
|
||||
* @return the local Y-coordinate to this {@link MoreKeysPanel}
|
||||
*/
|
||||
int translateY(int y);
|
||||
|
||||
/**
|
||||
* Show this {@link MoreKeysPanel} in the parent view.
|
||||
*
|
||||
* @param parentView the {@link ViewGroup} that hosts this {@link MoreKeysPanel}.
|
||||
*/
|
||||
void showInParent(ViewGroup parentView);
|
||||
|
||||
/**
|
||||
* Remove this {@link MoreKeysPanel} from the parent view.
|
||||
*/
|
||||
void removeFromParent();
|
||||
|
||||
/**
|
||||
* Return whether the panel is currently being shown.
|
||||
*/
|
||||
boolean isShowingInParent();
|
||||
}
|
||||
@ -0,0 +1,910 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.BogusMoveEventDetector;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.DrawingProxy;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.PointerTrackerQueue;
|
||||
import com.colorful.keyboard.theme.keyboard.internal.TimerProxy;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.CoordinateUtils;
|
||||
import com.colorful.keyboard.theme.latin.define.DebugFlags;
|
||||
import com.colorful.keyboard.theme.latin.settings.Settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public final class PointerTracker implements PointerTrackerQueue.Element {
|
||||
private static final String TAG = PointerTracker.class.getSimpleName();
|
||||
private static final boolean DEBUG_EVENT = false;
|
||||
private static final boolean DEBUG_MOVE_EVENT = false;
|
||||
private static final boolean DEBUG_LISTENER = false;
|
||||
private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT;
|
||||
|
||||
static final class PointerTrackerParams {
|
||||
public final boolean mKeySelectionByDraggingFinger;
|
||||
public final int mTouchNoiseThresholdTime;
|
||||
public final int mTouchNoiseThresholdDistance;
|
||||
public final int mKeyRepeatStartTimeout;
|
||||
public final int mKeyRepeatInterval;
|
||||
public final int mLongPressShiftLockTimeout;
|
||||
|
||||
public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
|
||||
mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean(
|
||||
R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false);
|
||||
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
|
||||
mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
|
||||
mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
|
||||
mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatInterval, 0);
|
||||
mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters for pointer handling.
|
||||
private static PointerTrackerParams sParams;
|
||||
private static int sPointerStep = (int)(10.0 * Resources.getSystem().getDisplayMetrics().density);
|
||||
|
||||
private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>();
|
||||
private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
|
||||
|
||||
public final int mPointerId;
|
||||
|
||||
private static DrawingProxy sDrawingProxy;
|
||||
private static TimerProxy sTimerProxy;
|
||||
private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER;
|
||||
|
||||
// The {@link KeyDetector} is set whenever the down event is processed. Also this is updated
|
||||
// when new {@link Keyboard} is set by {@link #setKeyDetector(KeyDetector)}.
|
||||
private KeyDetector mKeyDetector = new KeyDetector();
|
||||
private Keyboard mKeyboard;
|
||||
private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
|
||||
|
||||
// The position and time at which first down event occurred.
|
||||
private int[] mDownCoordinates = CoordinateUtils.newInstance();
|
||||
|
||||
// The current key where this pointer is.
|
||||
private Key mCurrentKey = null;
|
||||
// The position where the current key was recognized for the first time.
|
||||
private int mKeyX;
|
||||
private int mKeyY;
|
||||
|
||||
// Last pointer position.
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
private int mStartX;
|
||||
//private int mStartY;
|
||||
private long mStartTime;
|
||||
private boolean mCursorMoved = false;
|
||||
|
||||
// true if keyboard layout has been changed.
|
||||
private boolean mKeyboardLayoutHasBeenChanged;
|
||||
|
||||
// true if this pointer is no longer triggering any action because it has been canceled.
|
||||
private boolean mIsTrackingForActionDisabled;
|
||||
|
||||
// the more keys panel currently being shown. equals null if no panel is active.
|
||||
private MoreKeysPanel mMoreKeysPanel;
|
||||
|
||||
private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
|
||||
// true if this pointer is in the dragging finger mode.
|
||||
boolean mIsInDraggingFinger;
|
||||
// true if this pointer is sliding from a modifier key and in the sliding key input mode,
|
||||
// so that further modifier keys should be ignored.
|
||||
boolean mIsInSlidingKeyInput;
|
||||
// if not a NOT_A_CODE, the key of this code is repeating
|
||||
private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
|
||||
|
||||
// true if dragging finger is allowed.
|
||||
private boolean mIsAllowedDraggingFinger;
|
||||
|
||||
// TODO: Add PointerTrackerFactory singleton and move some class static methods into it.
|
||||
public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy,
|
||||
final DrawingProxy drawingProxy) {
|
||||
sParams = new PointerTrackerParams(mainKeyboardViewAttr);
|
||||
|
||||
final Resources res = mainKeyboardViewAttr.getResources();
|
||||
BogusMoveEventDetector.init(res);
|
||||
|
||||
sTimerProxy = timerProxy;
|
||||
sDrawingProxy = drawingProxy;
|
||||
}
|
||||
|
||||
public static PointerTracker getPointerTracker(final int id) {
|
||||
final ArrayList<PointerTracker> trackers = sTrackers;
|
||||
|
||||
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
|
||||
for (int i = trackers.size(); i <= id; i++) {
|
||||
final PointerTracker tracker = new PointerTracker(i);
|
||||
trackers.add(tracker);
|
||||
}
|
||||
|
||||
return trackers.get(id);
|
||||
}
|
||||
|
||||
public static boolean isAnyInDraggingFinger() {
|
||||
return sPointerTrackerQueue.isAnyInDraggingFinger();
|
||||
}
|
||||
|
||||
public static void cancelAllPointerTrackers() {
|
||||
sPointerTrackerQueue.cancelAllPointerTrackers();
|
||||
}
|
||||
|
||||
public static void setKeyboardActionListener(final KeyboardActionListener listener) {
|
||||
sListener = listener;
|
||||
}
|
||||
|
||||
public static void setKeyDetector(final KeyDetector keyDetector) {
|
||||
final Keyboard keyboard = keyDetector.getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return;
|
||||
}
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.setKeyDetectorInner(keyDetector);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setReleasedKeyGraphicsToAllKeys() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.setReleasedKeyGraphics(tracker.getKey(), true /* withAnimation */);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dismissAllMoreKeysPanels() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.dismissMoreKeysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
private PointerTracker(final int id) {
|
||||
mPointerId = id;
|
||||
}
|
||||
|
||||
// Returns true if keyboard has been changed by this callback.
|
||||
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
|
||||
final int repeatCount) {
|
||||
// While gesture input is going on, this method should be a no-operation. But when gesture
|
||||
// input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
|
||||
// are set to false. To keep this method is a no-operation,
|
||||
// <code>mIsTrackingForActionDisabled</code> should also be taken account of.
|
||||
final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId,
|
||||
(key == null ? "none" : Constants.printableCode(key.getCode())),
|
||||
ignoreModifierKey ? " ignoreModifier" : "",
|
||||
repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
|
||||
}
|
||||
if (ignoreModifierKey) {
|
||||
return false;
|
||||
}
|
||||
sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
|
||||
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
|
||||
mKeyboardLayoutHasBeenChanged = false;
|
||||
sTimerProxy.startTypingStateTimer(key);
|
||||
return keyboardLayoutHasBeenChanged;
|
||||
}
|
||||
|
||||
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
||||
// primaryCode is different from {@link Key#mKeyCode}.
|
||||
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
|
||||
final int y, final boolean isKeyRepeat) {
|
||||
final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
|
||||
final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
|
||||
final int code = altersCode ? key.getAltCode() : primaryCode;
|
||||
if (DEBUG_LISTENER) {
|
||||
final String output = code == Constants.CODE_OUTPUT_TEXT
|
||||
? key.getOutputText() : Constants.printableCode(code);
|
||||
Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y,
|
||||
output, ignoreModifierKey ? " ignoreModifier" : "",
|
||||
altersCode ? " altersCode" : ""));
|
||||
}
|
||||
if (ignoreModifierKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == Constants.CODE_OUTPUT_TEXT) {
|
||||
sListener.onTextInput(key.getOutputText());
|
||||
} else if (code != Constants.CODE_UNSPECIFIED) {
|
||||
sListener.onCodeInput(code,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we need primaryCode argument because the keyboard may be in shifted state and the
|
||||
// primaryCode is different from {@link Key#mKeyCode}.
|
||||
private void callListenerOnRelease(final Key key, final int primaryCode,
|
||||
final boolean withSliding) {
|
||||
// See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}.
|
||||
final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, String.format("[%d] onRelease : %s%s%s", mPointerId,
|
||||
Constants.printableCode(primaryCode),
|
||||
withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : ""));
|
||||
}
|
||||
if (ignoreModifierKey) {
|
||||
return;
|
||||
}
|
||||
sListener.onReleaseKey(primaryCode, withSliding);
|
||||
}
|
||||
|
||||
private void callListenerOnFinishSlidingInput() {
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId));
|
||||
}
|
||||
sListener.onFinishSlidingInput();
|
||||
}
|
||||
|
||||
private void setKeyDetectorInner(final KeyDetector keyDetector) {
|
||||
final Keyboard keyboard = keyDetector.getKeyboard();
|
||||
if (keyboard == null) {
|
||||
return;
|
||||
}
|
||||
if (keyDetector == mKeyDetector && keyboard == mKeyboard) {
|
||||
return;
|
||||
}
|
||||
mKeyDetector = keyDetector;
|
||||
mKeyboard = keyboard;
|
||||
// Mark that keyboard layout has been changed.
|
||||
mKeyboardLayoutHasBeenChanged = true;
|
||||
final int keyPaddedWidth = mKeyboard.mMostCommonKeyWidth
|
||||
+ Math.round(mKeyboard.mHorizontalGap);
|
||||
final int keyPaddedHeight = mKeyboard.mMostCommonKeyHeight
|
||||
+ Math.round(mKeyboard.mVerticalGap);
|
||||
// Keep {@link #mCurrentKey} that comes from previous keyboard. The key preview of
|
||||
// {@link #mCurrentKey} will be dismissed by {@setReleasedKeyGraphics(Key)} via
|
||||
// {@link onMoveEventInternal(int,int,long)} or {@link #onUpEventInternal(int,int,long)}.
|
||||
mBogusMoveEventDetector.setKeyboardGeometry(keyPaddedWidth, keyPaddedHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInDraggingFinger() {
|
||||
return mIsInDraggingFinger;
|
||||
}
|
||||
|
||||
public Key getKey() {
|
||||
return mCurrentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModifier() {
|
||||
return mCurrentKey != null && mCurrentKey.isModifier();
|
||||
}
|
||||
|
||||
public Key getKeyOn(final int x, final int y) {
|
||||
return mKeyDetector.detectHitKey(x, y);
|
||||
}
|
||||
|
||||
private void setReleasedKeyGraphics(final Key key, final boolean withAnimation) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sDrawingProxy.onKeyReleased(key, withAnimation);
|
||||
|
||||
if (key.isShift()) {
|
||||
for (final Key shiftKey : mKeyboard.mShiftKeys) {
|
||||
if (shiftKey != key) {
|
||||
sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key.altCodeWhileTyping()) {
|
||||
final int altCode = key.getAltCode();
|
||||
final Key altKey = mKeyboard.getKey(altCode);
|
||||
if (altKey != null) {
|
||||
sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */);
|
||||
}
|
||||
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
|
||||
if (k != key && k.getAltCode() == altCode) {
|
||||
sDrawingProxy.onKeyReleased(k, false /* withAnimation */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPressedKeyGraphics(final Key key) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
|
||||
final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
|
||||
|
||||
sDrawingProxy.onKeyPressed(key, true);
|
||||
|
||||
if (key.isShift()) {
|
||||
for (final Key shiftKey : mKeyboard.mShiftKeys) {
|
||||
if (shiftKey != key) {
|
||||
sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (altersCode) {
|
||||
final int altCode = key.getAltCode();
|
||||
final Key altKey = mKeyboard.getKey(altCode);
|
||||
if (altKey != null) {
|
||||
sDrawingProxy.onKeyPressed(altKey, false /* withPreview */);
|
||||
}
|
||||
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
|
||||
if (k != key && k.getAltCode() == altCode) {
|
||||
sDrawingProxy.onKeyPressed(k, false /* withPreview */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getLastCoordinates(final int[] outCoords) {
|
||||
CoordinateUtils.set(outCoords, mLastX, mLastY);
|
||||
}
|
||||
|
||||
private Key onDownKey(final int x, final int y) {
|
||||
CoordinateUtils.set(mDownCoordinates, x, y);
|
||||
mBogusMoveEventDetector.onDownKey();
|
||||
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
|
||||
}
|
||||
|
||||
private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
|
||||
return (int)Math.hypot(x1 - x2, y1 - y2);
|
||||
}
|
||||
|
||||
private Key onMoveKeyInternal(final int x, final int y) {
|
||||
mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
return mKeyDetector.detectHitKey(x, y);
|
||||
}
|
||||
|
||||
private Key onMoveKey(final int x, final int y) {
|
||||
return onMoveKeyInternal(x, y);
|
||||
}
|
||||
|
||||
private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
|
||||
mCurrentKey = newKey;
|
||||
mKeyX = x;
|
||||
mKeyY = y;
|
||||
return newKey;
|
||||
}
|
||||
|
||||
/* package */ static int getActivePointerTrackerCount() {
|
||||
return sPointerTrackerQueue.size();
|
||||
}
|
||||
|
||||
public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
|
||||
final int action = me.getActionMasked();
|
||||
final long eventTime = me.getEventTime();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
// When this pointer is the only active pointer and is showing a more keys panel,
|
||||
// we should ignore other pointers' motion event.
|
||||
final boolean shouldIgnoreOtherPointers =
|
||||
isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1;
|
||||
final int pointerCount = me.getPointerCount();
|
||||
for (int index = 0; index < pointerCount; index++) {
|
||||
final int id = me.getPointerId(index);
|
||||
if (shouldIgnoreOtherPointers && id != mPointerId) {
|
||||
continue;
|
||||
}
|
||||
final int x = (int)me.getX(index);
|
||||
final int y = (int)me.getY(index);
|
||||
final PointerTracker tracker = getPointerTracker(id);
|
||||
tracker.onMoveEvent(x, y, eventTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final int index = me.getActionIndex();
|
||||
final int x = (int)me.getX(index);
|
||||
final int y = (int)me.getY(index);
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
onDownEvent(x, y, eventTime, keyDetector);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onUpEvent(x, y, eventTime);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onCancelEvent(x, y, eventTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownEvent(final int x, final int y, final long eventTime,
|
||||
final KeyDetector keyDetector) {
|
||||
setKeyDetectorInner(keyDetector);
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onDownEvent:", x, y, eventTime);
|
||||
}
|
||||
// Naive up-to-down noise filter.
|
||||
final long deltaT = eventTime;
|
||||
if (deltaT < sParams.mTouchNoiseThresholdTime) {
|
||||
final int distance = getDistance(x, y, mLastX, mLastY);
|
||||
if (distance < sParams.mTouchNoiseThresholdDistance) {
|
||||
if (DEBUG_MODE)
|
||||
Log.w(TAG, String.format("[%d] onDownEvent:"
|
||||
+ " ignore potential noise: time=%d distance=%d",
|
||||
mPointerId, deltaT, distance));
|
||||
cancelTrackingForAction();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Key key = getKeyOn(x, y);
|
||||
mBogusMoveEventDetector.onActualDownEvent(x, y);
|
||||
if (key != null && key.isModifier()) {
|
||||
// Before processing a down event of modifier key, all pointers already being
|
||||
// tracked should be released.
|
||||
sPointerTrackerQueue.releaseAllPointers(eventTime);
|
||||
}
|
||||
sPointerTrackerQueue.add(this);
|
||||
onDownEventInternal(x, y);
|
||||
}
|
||||
|
||||
/* package */ boolean isShowingMoreKeysPanel() {
|
||||
return (mMoreKeysPanel != null);
|
||||
}
|
||||
|
||||
private void dismissMoreKeysPanel() {
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
mMoreKeysPanel.dismissMoreKeysPanel();
|
||||
mMoreKeysPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownEventInternal(final int x, final int y) {
|
||||
Key key = onDownKey(x, y);
|
||||
// Key selection by dragging finger is allowed when 1) key selection by dragging finger is
|
||||
// enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this
|
||||
// pointer's KeyDetector always allows key selection by dragging finger, such as
|
||||
// {@link MoreKeysKeyboard}.
|
||||
mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger
|
||||
|| (key != null && key.isModifier())
|
||||
|| mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger();
|
||||
mKeyboardLayoutHasBeenChanged = false;
|
||||
mIsTrackingForActionDisabled = false;
|
||||
resetKeySelectionByDraggingFinger();
|
||||
if (key != null) {
|
||||
// This onPress call may have changed keyboard layout. Those cases are detected at
|
||||
// {@link #setKeyboard}. In those cases, we should update key according to the new
|
||||
// keyboard layout.
|
||||
if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
|
||||
key = onDownKey(x, y);
|
||||
}
|
||||
|
||||
startRepeatKey(key);
|
||||
startLongPressTimer(key);
|
||||
setPressedKeyGraphics(key);
|
||||
mStartX = x;
|
||||
//mStartY = y;
|
||||
mStartTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
private void startKeySelectionByDraggingFinger(final Key key) {
|
||||
if (!mIsInDraggingFinger) {
|
||||
mIsInSlidingKeyInput = key.isModifier();
|
||||
}
|
||||
mIsInDraggingFinger = true;
|
||||
}
|
||||
|
||||
private void resetKeySelectionByDraggingFinger() {
|
||||
mIsInDraggingFinger = false;
|
||||
mIsInSlidingKeyInput = false;
|
||||
}
|
||||
|
||||
private void onMoveEvent(final int x, final int y, final long eventTime) {
|
||||
if (DEBUG_MOVE_EVENT) {
|
||||
printTouchEvent("onMoveEvent:", x, y, eventTime);
|
||||
}
|
||||
if (mIsTrackingForActionDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
final int translatedX = mMoreKeysPanel.translateX(x);
|
||||
final int translatedY = mMoreKeysPanel.translateY(y);
|
||||
mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId);
|
||||
onMoveKey(x, y);
|
||||
return;
|
||||
}
|
||||
onMoveEventInternal(x, y, eventTime);
|
||||
}
|
||||
|
||||
private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y) {
|
||||
// This onPress call may have changed keyboard layout. Those cases are detected
|
||||
// at {@link #setKeyboard}. In those cases, we should update key according
|
||||
// to the new keyboard layout.
|
||||
Key key = newKey;
|
||||
if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
|
||||
key = onMoveKey(x, y);
|
||||
}
|
||||
onMoveToNewKey(key, x, y);
|
||||
if (mIsTrackingForActionDisabled) {
|
||||
return;
|
||||
}
|
||||
startLongPressTimer(key);
|
||||
setPressedKeyGraphics(key);
|
||||
}
|
||||
|
||||
private void processDraggingFingerOutFromOldKey(final Key oldKey) {
|
||||
setReleasedKeyGraphics(oldKey, true /* withAnimation */);
|
||||
callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
|
||||
startKeySelectionByDraggingFinger(oldKey);
|
||||
sTimerProxy.cancelKeyTimersOf(this);
|
||||
}
|
||||
|
||||
private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y,
|
||||
final long eventTime, final Key oldKey) {
|
||||
// The pointer has been slid in to the new key from the previous key, we must call
|
||||
// onRelease() first to notify that the previous key has been released, then call
|
||||
// onPress() to notify that the new key is being pressed.
|
||||
processDraggingFingerOutFromOldKey(oldKey);
|
||||
startRepeatKey(key);
|
||||
if (mIsAllowedDraggingFinger) {
|
||||
processDraggingFingerInToNewKey(key, x, y);
|
||||
}
|
||||
// HACK: If there are currently multiple touches, register the key even if the finger
|
||||
// slides off the key. This defends against noise from some touch panels when there are
|
||||
// close multiple touches.
|
||||
// Caveat: When in chording input mode with a modifier key, we don't use this hack.
|
||||
else if (getActivePointerTrackerCount() > 1
|
||||
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
|
||||
if (DEBUG_MODE) {
|
||||
Log.w(TAG, String.format("[%d] onMoveEvent:"
|
||||
+ " detected sliding finger while multi touching", mPointerId));
|
||||
}
|
||||
onUpEvent(x, y, eventTime);
|
||||
cancelTrackingForAction();
|
||||
setReleasedKeyGraphics(oldKey, true /* withAnimation */);
|
||||
} else {
|
||||
cancelTrackingForAction();
|
||||
setReleasedKeyGraphics(oldKey, true /* withAnimation */);
|
||||
}
|
||||
}
|
||||
|
||||
private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) {
|
||||
// The pointer has been slid out from the previous key, we must call onRelease() to
|
||||
// notify that the previous key has been released.
|
||||
processDraggingFingerOutFromOldKey(oldKey);
|
||||
if (mIsAllowedDraggingFinger) {
|
||||
onMoveToNewKey(null, x, y);
|
||||
} else {
|
||||
cancelTrackingForAction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMoveEventInternal(final int x, final int y, final long eventTime) {
|
||||
final Key oldKey = mCurrentKey;
|
||||
|
||||
if (oldKey != null && oldKey.getCode() == Constants.CODE_SPACE && Settings.getInstance().getCurrent().mSpaceSwipeEnabled) {
|
||||
//Pointer slider
|
||||
int steps = (x - mStartX) / sPointerStep;
|
||||
final int swipeIgnoreTime = Settings.getInstance().getCurrent().mKeyLongpressTimeout / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
|
||||
if (steps != 0 && mStartTime + swipeIgnoreTime < System.currentTimeMillis()) {
|
||||
mCursorMoved = true;
|
||||
mStartX += steps * sPointerStep;
|
||||
sListener.onMovePointer(steps);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldKey != null && oldKey.getCode() == Constants.CODE_DELETE && Settings.getInstance().getCurrent().mDeleteSwipeEnabled) {
|
||||
//Delete slider
|
||||
int steps = (x - mStartX) / sPointerStep;
|
||||
if (steps != 0) {
|
||||
sTimerProxy.cancelKeyTimersOf(this);
|
||||
mCursorMoved = true;
|
||||
mStartX += steps * sPointerStep;
|
||||
sListener.onMoveDeletePointer(steps);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Key newKey = onMoveKey(x, y);
|
||||
if (newKey != null) {
|
||||
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, newKey)) {
|
||||
dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey);
|
||||
} else if (oldKey == null) {
|
||||
// The pointer has been slid in to the new key, but the finger was not on any keys.
|
||||
// In this case, we must call onPress() to notify that the new key is being pressed.
|
||||
processDraggingFingerInToNewKey(newKey, x, y);
|
||||
}
|
||||
} else { // newKey == null
|
||||
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, newKey)) {
|
||||
dragFingerOutFromOldKey(oldKey, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpEvent(final int x, final int y, final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onUpEvent :", x, y, eventTime);
|
||||
}
|
||||
|
||||
sTimerProxy.cancelUpdateBatchInputTimer(this);
|
||||
if (mCurrentKey != null && mCurrentKey.isModifier()) {
|
||||
// Before processing an up event of modifier key, all pointers already being
|
||||
// tracked should be released.
|
||||
sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime);
|
||||
} else {
|
||||
sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime);
|
||||
}
|
||||
onUpEventInternal(x, y);
|
||||
sPointerTrackerQueue.remove(this);
|
||||
}
|
||||
|
||||
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
|
||||
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
|
||||
// "virtual" up event.
|
||||
@Override
|
||||
public void onPhantomUpEvent(final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime);
|
||||
}
|
||||
onUpEventInternal(mLastX, mLastY);
|
||||
cancelTrackingForAction();
|
||||
}
|
||||
|
||||
private void onUpEventInternal(final int x, final int y) {
|
||||
sTimerProxy.cancelKeyTimersOf(this);
|
||||
final boolean isInDraggingFinger = mIsInDraggingFinger;
|
||||
final boolean isInSlidingKeyInput = mIsInSlidingKeyInput;
|
||||
resetKeySelectionByDraggingFinger();
|
||||
final Key currentKey = mCurrentKey;
|
||||
mCurrentKey = null;
|
||||
final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode;
|
||||
mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
|
||||
// Release the last pressed key.
|
||||
setReleasedKeyGraphics(currentKey, true /* withAnimation */);
|
||||
|
||||
if(mCursorMoved && currentKey.getCode() == Constants.CODE_DELETE) {
|
||||
sListener.onUpWithDeletePointerActive();
|
||||
}
|
||||
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
if (!mIsTrackingForActionDisabled) {
|
||||
final int translatedX = mMoreKeysPanel.translateX(x);
|
||||
final int translatedY = mMoreKeysPanel.translateY(y);
|
||||
mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId);
|
||||
}
|
||||
dismissMoreKeysPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCursorMoved) {
|
||||
mCursorMoved = false;
|
||||
return;
|
||||
}
|
||||
if (mIsTrackingForActionDisabled) {
|
||||
return;
|
||||
}
|
||||
if (currentKey != null && currentKey.isRepeatable()
|
||||
&& (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) {
|
||||
return;
|
||||
}
|
||||
detectAndSendKey(currentKey, mKeyX, mKeyY);
|
||||
if (isInSlidingKeyInput) {
|
||||
callListenerOnFinishSlidingInput();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelTrackingForAction() {
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
return;
|
||||
}
|
||||
mIsTrackingForActionDisabled = true;
|
||||
}
|
||||
|
||||
public void onLongPressed() {
|
||||
sTimerProxy.cancelLongPressTimersOf(this);
|
||||
if (isShowingMoreKeysPanel()) {
|
||||
return;
|
||||
}
|
||||
if (mCursorMoved) {
|
||||
return;
|
||||
}
|
||||
final Key key = getKey();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
if (key.hasNoPanelAutoMoreKey()) {
|
||||
cancelKeyTracking();
|
||||
final int moreKeyCode = key.getMoreKeys()[0].mCode;
|
||||
sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */);
|
||||
sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
|
||||
Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
|
||||
sListener.onReleaseKey(moreKeyCode, false /* withSliding */);
|
||||
return;
|
||||
}
|
||||
final int code = key.getCode();
|
||||
if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
|
||||
// Long pressing the space key invokes IME switcher dialog.
|
||||
if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
|
||||
cancelKeyTracking();
|
||||
sListener.onReleaseKey(code, false /* withSliding */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setReleasedKeyGraphics(key, false /* withAnimation */);
|
||||
final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this);
|
||||
if (moreKeysPanel == null) {
|
||||
return;
|
||||
}
|
||||
final int translatedX = moreKeysPanel.translateX(mLastX);
|
||||
final int translatedY = moreKeysPanel.translateY(mLastY);
|
||||
moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId);
|
||||
mMoreKeysPanel = moreKeysPanel;
|
||||
}
|
||||
|
||||
private void cancelKeyTracking() {
|
||||
resetKeySelectionByDraggingFinger();
|
||||
cancelTrackingForAction();
|
||||
setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
|
||||
sPointerTrackerQueue.remove(this);
|
||||
}
|
||||
|
||||
private void onCancelEvent(final int x, final int y, final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onCancelEvt:", x, y, eventTime);
|
||||
}
|
||||
|
||||
cancelAllPointerTrackers();
|
||||
sPointerTrackerQueue.releaseAllPointers(eventTime);
|
||||
onCancelEventInternal();
|
||||
}
|
||||
|
||||
private void onCancelEventInternal() {
|
||||
sTimerProxy.cancelKeyTimersOf(this);
|
||||
setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
|
||||
resetKeySelectionByDraggingFinger();
|
||||
dismissMoreKeysPanel();
|
||||
}
|
||||
|
||||
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
|
||||
final Key curKey = mCurrentKey;
|
||||
if (newKey == curKey) {
|
||||
return false;
|
||||
}
|
||||
if (curKey == null /* && newKey != null */) {
|
||||
return true;
|
||||
}
|
||||
// Here curKey points to the different key from newKey.
|
||||
final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
|
||||
mIsInSlidingKeyInput);
|
||||
final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToHitboxEdge(x, y);
|
||||
if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
|
||||
if (DEBUG_MODE) {
|
||||
final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared)
|
||||
/ (mKeyboard.mMostCommonKeyWidth + mKeyboard.mHorizontalGap);
|
||||
Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
|
||||
+" %.2f key width from key edge", mPointerId, distanceToEdgeRatio));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!mIsAllowedDraggingFinger && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) {
|
||||
if (DEBUG_MODE) {
|
||||
final float keyDiagonal = (float)Math.hypot(
|
||||
mKeyboard.mMostCommonKeyWidth + mKeyboard.mHorizontalGap,
|
||||
mKeyboard.mMostCommonKeyHeight + mKeyboard.mVerticalGap);
|
||||
final float lengthFromDownRatio =
|
||||
mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal;
|
||||
Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
|
||||
+ " %.2f key diagonal from virtual down point",
|
||||
mPointerId, lengthFromDownRatio));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void startLongPressTimer(final Key key) {
|
||||
// Note that we need to cancel all active long press shift key timers if any whenever we
|
||||
// start a new long press timer for both non-shift and shift keys.
|
||||
sTimerProxy.cancelLongPressShiftKeyTimer();
|
||||
if (key == null) return;
|
||||
if (!key.isLongPressEnabled()) return;
|
||||
// Caveat: Please note that isLongPressEnabled() can be true even if the current key
|
||||
// doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger
|
||||
// mode, we will disable long press timer of such key.
|
||||
// We always need to start the long press timer if the key has its more keys regardless of
|
||||
// whether or not we are in the dragging finger mode.
|
||||
if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
|
||||
|
||||
final int delay = getLongPressTimeout(key.getCode());
|
||||
if (delay <= 0) return;
|
||||
sTimerProxy.startLongPressTimerOf(this, delay);
|
||||
}
|
||||
|
||||
private int getLongPressTimeout(final int code) {
|
||||
if (code == Constants.CODE_SHIFT) {
|
||||
return sParams.mLongPressShiftLockTimeout;
|
||||
}
|
||||
final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
|
||||
if (mIsInSlidingKeyInput) {
|
||||
// We use longer timeout for sliding finger input started from the modifier key.
|
||||
return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
|
||||
}
|
||||
if (code == Constants.CODE_SPACE) {
|
||||
// Cursor can be moved in space
|
||||
return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
|
||||
}
|
||||
return longpressTimeout;
|
||||
}
|
||||
|
||||
private void detectAndSendKey(final Key key, final int x, final int y) {
|
||||
if (key == null) return;
|
||||
|
||||
final int code = key.getCode();
|
||||
callListenerOnCodeInput(key, code, x, y, false /* isKeyRepeat */);
|
||||
callListenerOnRelease(key, code, false /* withSliding */);
|
||||
}
|
||||
|
||||
private void startRepeatKey(final Key key) {
|
||||
if (key == null) return;
|
||||
if (!key.isRepeatable()) return;
|
||||
// Don't start key repeat when we are in the dragging finger mode.
|
||||
if (mIsInDraggingFinger) return;
|
||||
final int startRepeatCount = 1;
|
||||
startKeyRepeatTimer(startRepeatCount);
|
||||
}
|
||||
|
||||
public void onKeyRepeat(final int code, final int repeatCount) {
|
||||
final Key key = getKey();
|
||||
if (key == null || key.getCode() != code) {
|
||||
mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
|
||||
return;
|
||||
}
|
||||
mCurrentRepeatingKeyCode = code;
|
||||
final int nextRepeatCount = repeatCount + 1;
|
||||
startKeyRepeatTimer(nextRepeatCount);
|
||||
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
|
||||
callListenerOnCodeInput(key, code, mKeyX, mKeyY, true /* isKeyRepeat */);
|
||||
}
|
||||
|
||||
private void startKeyRepeatTimer(final int repeatCount) {
|
||||
final int delay =
|
||||
(repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval;
|
||||
sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay);
|
||||
}
|
||||
|
||||
private void printTouchEvent(final String title, final int x, final int y,
|
||||
final long eventTime) {
|
||||
final Key key = mKeyDetector.detectHitKey(x, y);
|
||||
final String code = (key == null ? "none" : Constants.printableCode(key.getCode()));
|
||||
Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
|
||||
(mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ProximityInfo {
|
||||
private static final List<Key> EMPTY_KEY_LIST = Collections.emptyList();
|
||||
|
||||
private final int mGridWidth;
|
||||
private final int mGridHeight;
|
||||
private final int mGridSize;
|
||||
private final int mCellWidth;
|
||||
private final int mCellHeight;
|
||||
// TODO: Find a proper name for mKeyboardMinWidth
|
||||
private final int mKeyboardMinWidth;
|
||||
private final int mKeyboardHeight;
|
||||
private final List<Key> mSortedKeys;
|
||||
private final List<Key>[] mGridNeighbors;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProximityInfo(final int gridWidth, final int gridHeight, final int minWidth, final int height,
|
||||
final List<Key> sortedKeys) {
|
||||
mGridWidth = gridWidth;
|
||||
mGridHeight = gridHeight;
|
||||
mGridSize = mGridWidth * mGridHeight;
|
||||
mCellWidth = (minWidth + mGridWidth - 1) / mGridWidth;
|
||||
mCellHeight = (height + mGridHeight - 1) / mGridHeight;
|
||||
mKeyboardMinWidth = minWidth;
|
||||
mKeyboardHeight = height;
|
||||
mSortedKeys = sortedKeys;
|
||||
mGridNeighbors = new List[mGridSize];
|
||||
if (minWidth == 0 || height == 0) {
|
||||
// No proximity required. Keyboard might be more keys keyboard.
|
||||
return;
|
||||
}
|
||||
computeNearestNeighbors();
|
||||
}
|
||||
|
||||
private void computeNearestNeighbors() {
|
||||
final int keyCount = mSortedKeys.size();
|
||||
final int gridSize = mGridNeighbors.length;
|
||||
final int maxKeyRight = mGridWidth * mCellWidth;
|
||||
final int maxKeyBottom = mGridHeight * mCellHeight;
|
||||
|
||||
// For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
|
||||
// keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
|
||||
// enough space for as many keys as there are on the keyboard. Hence, every
|
||||
// keycount'th element is the start of a new cell, and each of these virtual subarrays
|
||||
// start empty with keycount spaces available. This fills up gradually in the loop below.
|
||||
// Since in the practice each cell does not have a lot of neighbors, most of this space is
|
||||
// actually just empty padding in this fixed-size buffer.
|
||||
final Key[] neighborsFlatBuffer = new Key[gridSize * keyCount];
|
||||
final int[] neighborCountPerCell = new int[gridSize];
|
||||
for (final Key key : mSortedKeys) {
|
||||
if (key.isSpacer()) continue;
|
||||
|
||||
// Iterate through all of the cells that overlap with the clickable region of the
|
||||
// current key and add the key as a neighbor.
|
||||
final int keyX = key.getX();
|
||||
final int keyY = key.getY();
|
||||
final int keyTop = keyY - key.getTopPadding();
|
||||
final int keyBottom = Math.min(keyY + key.getHeight() + key.getBottomPadding(),
|
||||
maxKeyBottom);
|
||||
final int keyLeft = keyX - key.getLeftPadding();
|
||||
final int keyRight = Math.min(keyX + key.getWidth() + key.getRightPadding(),
|
||||
maxKeyRight);
|
||||
final int yDeltaToGrid = keyTop % mCellHeight;
|
||||
final int xDeltaToGrid = keyLeft % mCellWidth;
|
||||
final int yStart = keyTop - yDeltaToGrid;
|
||||
final int xStart = keyLeft - xDeltaToGrid;
|
||||
int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
|
||||
for (int cellTop = yStart; cellTop < keyBottom; cellTop += mCellHeight) {
|
||||
int index = baseIndexOfCurrentRow;
|
||||
for (int cellLeft = xStart; cellLeft < keyRight; cellLeft += mCellWidth) {
|
||||
neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
|
||||
++neighborCountPerCell[index];
|
||||
++index;
|
||||
}
|
||||
baseIndexOfCurrentRow += mGridWidth;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < gridSize; ++i) {
|
||||
final int indexStart = i * keyCount;
|
||||
final int indexEnd = indexStart + neighborCountPerCell[i];
|
||||
final ArrayList<Key> neighbors = new ArrayList<>(indexEnd - indexStart);
|
||||
for (int index = indexStart; index < indexEnd; index++) {
|
||||
neighbors.add(neighborsFlatBuffer[index]);
|
||||
}
|
||||
mGridNeighbors[i] = Collections.unmodifiableList(neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Key> getNearestKeys(final int x, final int y) {
|
||||
if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
|
||||
int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
|
||||
if (index < mGridSize) {
|
||||
return mGridNeighbors[index];
|
||||
}
|
||||
}
|
||||
return EMPTY_KEY_LIST;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public final class AlphabetShiftState {
|
||||
private static final String TAG = AlphabetShiftState.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int UNSHIFTED = 0;
|
||||
private static final int MANUAL_SHIFTED = 1;
|
||||
private static final int AUTOMATIC_SHIFTED = 2;
|
||||
private static final int SHIFT_LOCKED = 3;
|
||||
private static final int SHIFT_LOCK_SHIFTED = 4;
|
||||
|
||||
private int mState = UNSHIFTED;
|
||||
|
||||
public void setShifted(boolean newShiftState) {
|
||||
final int oldState = mState;
|
||||
if (newShiftState) {
|
||||
switch (oldState) {
|
||||
case UNSHIFTED:
|
||||
mState = MANUAL_SHIFTED;
|
||||
break;
|
||||
case SHIFT_LOCKED:
|
||||
mState = SHIFT_LOCK_SHIFTED;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (oldState) {
|
||||
case MANUAL_SHIFTED:
|
||||
case AUTOMATIC_SHIFTED:
|
||||
mState = UNSHIFTED;
|
||||
break;
|
||||
case SHIFT_LOCK_SHIFTED:
|
||||
mState = SHIFT_LOCKED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this);
|
||||
}
|
||||
|
||||
public void setShiftLocked(boolean newShiftLockState) {
|
||||
final int oldState = mState;
|
||||
if (newShiftLockState) {
|
||||
switch (oldState) {
|
||||
case UNSHIFTED:
|
||||
case MANUAL_SHIFTED:
|
||||
case AUTOMATIC_SHIFTED:
|
||||
mState = SHIFT_LOCKED;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mState = UNSHIFTED;
|
||||
}
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState)
|
||||
+ " > " + this);
|
||||
}
|
||||
|
||||
public void setAutomaticShifted() {
|
||||
mState = AUTOMATIC_SHIFTED;
|
||||
}
|
||||
|
||||
public boolean isShiftedOrShiftLocked() {
|
||||
return mState != UNSHIFTED;
|
||||
}
|
||||
|
||||
public boolean isShiftLocked() {
|
||||
return mState == SHIFT_LOCKED || mState == SHIFT_LOCK_SHIFTED;
|
||||
}
|
||||
|
||||
public boolean isShiftLockShifted() {
|
||||
return mState == SHIFT_LOCK_SHIFTED;
|
||||
}
|
||||
|
||||
public boolean isAutomaticShifted() {
|
||||
return mState == AUTOMATIC_SHIFTED;
|
||||
}
|
||||
|
||||
public boolean isManualShifted() {
|
||||
return mState == MANUAL_SHIFTED || mState == SHIFT_LOCK_SHIFTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(mState);
|
||||
}
|
||||
|
||||
private static String toString(int state) {
|
||||
switch (state) {
|
||||
case UNSHIFTED: return "UNSHIFTED";
|
||||
case MANUAL_SHIFTED: return "MANUAL_SHIFTED";
|
||||
case AUTOMATIC_SHIFTED: return "AUTOMATIC_SHIFTED";
|
||||
case SHIFT_LOCKED: return "SHIFT_LOCKED";
|
||||
case SHIFT_LOCK_SHIFTED: return "SHIFT_LOCK_SHIFTED";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.define.DebugFlags;
|
||||
|
||||
|
||||
// This hack is applied to certain classes of tablets.
|
||||
public final class BogusMoveEventDetector {
|
||||
private static final String TAG = BogusMoveEventDetector.class.getSimpleName();
|
||||
private static final boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED;
|
||||
|
||||
// Move these thresholds to resource.
|
||||
// These thresholds' unit is a diagonal length of a key.
|
||||
private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f;
|
||||
|
||||
private static boolean sNeedsProximateBogusDownMoveUpEventHack;
|
||||
|
||||
public static void init(final Resources res) {
|
||||
// The proximate bogus down move up event hack is needed for a device such like,
|
||||
// 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
|
||||
// Though it seems odd to use screen density as criteria of the quality of the touch
|
||||
// screen, the small table that has a less density screen than hdpi most likely has been
|
||||
// made with the touch screen that needs the hack.
|
||||
final int screenMetrics = res.getInteger(R.integer.config_screen_metrics);
|
||||
final boolean isLargeTablet = (screenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET);
|
||||
final boolean isSmallTablet = (screenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET);
|
||||
final int densityDpi = res.getDisplayMetrics().densityDpi;
|
||||
final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
|
||||
final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
|
||||
if (DEBUG_MODE) {
|
||||
final int sw = res.getConfiguration().smallestScreenWidthDp;
|
||||
Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
|
||||
+ " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi
|
||||
+ " screenMetrics=" + screenMetrics);
|
||||
}
|
||||
sNeedsProximateBogusDownMoveUpEventHack = needsTheHack;
|
||||
}
|
||||
|
||||
private int mAccumulatedDistanceThreshold;
|
||||
|
||||
// Accumulated distance from actual and artificial down keys.
|
||||
/* package */ int mAccumulatedDistanceFromDownKey;
|
||||
private int mActualDownX;
|
||||
private int mActualDownY;
|
||||
|
||||
public void setKeyboardGeometry(final int keyPaddedWidth, final int keyPaddedHeight) {
|
||||
final float keyDiagonal = (float)Math.hypot(keyPaddedWidth, keyPaddedHeight);
|
||||
mAccumulatedDistanceThreshold = (int)(
|
||||
keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
|
||||
}
|
||||
|
||||
public void onActualDownEvent(final int x, final int y) {
|
||||
mActualDownX = x;
|
||||
mActualDownY = y;
|
||||
}
|
||||
|
||||
public void onDownKey() {
|
||||
mAccumulatedDistanceFromDownKey = 0;
|
||||
}
|
||||
|
||||
public void onMoveKey(final int distance) {
|
||||
mAccumulatedDistanceFromDownKey += distance;
|
||||
}
|
||||
|
||||
public boolean hasTraveledLongDistance(final int x, final int y) {
|
||||
if (!sNeedsProximateBogusDownMoveUpEventHack) {
|
||||
return false;
|
||||
}
|
||||
final int dx = Math.abs(x - mActualDownX);
|
||||
final int dy = Math.abs(y - mActualDownY);
|
||||
// A bogus move event should be a horizontal movement. A vertical movement might be
|
||||
// a sloppy typing and should be ignored.
|
||||
return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
|
||||
}
|
||||
|
||||
public int getAccumulatedDistanceFromDownKey() {
|
||||
return mAccumulatedDistanceFromDownKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.common.CoordinateUtils;
|
||||
|
||||
|
||||
public final class DrawingPreviewPlacerView extends RelativeLayout {
|
||||
private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
|
||||
|
||||
public DrawingPreviewPlacerView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
public void setKeyboardViewGeometry(final int[] originCoords) {
|
||||
CoordinateUtils.copy(mKeyboardViewOrigin, originCoords);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
final int originX = CoordinateUtils.x(mKeyboardViewOrigin);
|
||||
final int originY = CoordinateUtils.y(mKeyboardViewOrigin);
|
||||
canvas.translate(originX, originY);
|
||||
canvas.translate(-originX, -originY);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.keyboard.MoreKeysPanel;
|
||||
import com.colorful.keyboard.theme.keyboard.PointerTracker;
|
||||
|
||||
public interface DrawingProxy {
|
||||
/**
|
||||
* Called when a key is being pressed.
|
||||
* @param key the {@link Key} that is being pressed.
|
||||
* @param withPreview true if key popup preview should be displayed.
|
||||
*/
|
||||
void onKeyPressed(Key key, boolean withPreview);
|
||||
|
||||
/**
|
||||
* Called when a key is being released.
|
||||
* @param key the {@link Key} that is being released.
|
||||
* @param withAnimation when true, key popup preview should be dismissed with animation.
|
||||
*/
|
||||
void onKeyReleased(Key key, boolean withAnimation);
|
||||
|
||||
/**
|
||||
* Start showing more keys keyboard of a key that is being long pressed.
|
||||
* @param key the {@link Key} that is being long pressed and showing more keys keyboard.
|
||||
* @param tracker the {@link PointerTracker} that detects this long pressing.
|
||||
* @return {@link MoreKeysPanel} that is being shown. null if there is no need to show more keys
|
||||
* keyboard.
|
||||
*/
|
||||
MoreKeysPanel showMoreKeysKeyboard(Key key, PointerTracker tracker);
|
||||
|
||||
/**
|
||||
* Start a while-typing-animation.
|
||||
* @param fadeInOrOut {@link #FADE_IN} starts while-typing-fade-in animation.
|
||||
* {@link #FADE_OUT} starts while-typing-fade-out animation.
|
||||
*/
|
||||
void startWhileTypingAnimation(int fadeInOrOut);
|
||||
int FADE_IN = 0;
|
||||
int FADE_OUT = 1;
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.utils.ResourceUtils;
|
||||
|
||||
public final class KeyDrawParams {
|
||||
public Typeface mTypeface = Typeface.DEFAULT;
|
||||
|
||||
public int mLetterSize;
|
||||
public int mLabelSize;
|
||||
public int mLargeLetterSize;
|
||||
public int mHintLetterSize;
|
||||
public int mShiftedLetterHintSize;
|
||||
public int mHintLabelSize;
|
||||
public int mPreviewTextSize;
|
||||
|
||||
public int mTextColor;
|
||||
public int mTextInactivatedColor;
|
||||
public int mTextShadowColor;
|
||||
public int mFunctionalTextColor;
|
||||
public int mHintLetterColor;
|
||||
public int mHintLabelColor;
|
||||
public int mShiftedLetterHintInactivatedColor;
|
||||
public int mShiftedLetterHintActivatedColor;
|
||||
public int mPreviewTextColor;
|
||||
|
||||
public float mHintLabelVerticalAdjustment;
|
||||
public float mLabelOffCenterRatio;
|
||||
public float mHintLabelOffCenterRatio;
|
||||
|
||||
public int mAnimAlpha;
|
||||
|
||||
public KeyDrawParams() {}
|
||||
|
||||
private KeyDrawParams(final KeyDrawParams copyFrom) {
|
||||
mTypeface = copyFrom.mTypeface;
|
||||
|
||||
mLetterSize = copyFrom.mLetterSize;
|
||||
mLabelSize = copyFrom.mLabelSize;
|
||||
mLargeLetterSize = copyFrom.mLargeLetterSize;
|
||||
mHintLetterSize = copyFrom.mHintLetterSize;
|
||||
mShiftedLetterHintSize = copyFrom.mShiftedLetterHintSize;
|
||||
mHintLabelSize = copyFrom.mHintLabelSize;
|
||||
mPreviewTextSize = copyFrom.mPreviewTextSize;
|
||||
|
||||
mTextColor = copyFrom.mTextColor;
|
||||
mTextInactivatedColor = copyFrom.mTextInactivatedColor;
|
||||
mTextShadowColor = copyFrom.mTextShadowColor;
|
||||
mFunctionalTextColor = copyFrom.mFunctionalTextColor;
|
||||
mHintLetterColor = copyFrom.mHintLetterColor;
|
||||
mHintLabelColor = copyFrom.mHintLabelColor;
|
||||
mShiftedLetterHintInactivatedColor = copyFrom.mShiftedLetterHintInactivatedColor;
|
||||
mShiftedLetterHintActivatedColor = copyFrom.mShiftedLetterHintActivatedColor;
|
||||
mPreviewTextColor = copyFrom.mPreviewTextColor;
|
||||
|
||||
mHintLabelVerticalAdjustment = copyFrom.mHintLabelVerticalAdjustment;
|
||||
mLabelOffCenterRatio = copyFrom.mLabelOffCenterRatio;
|
||||
mHintLabelOffCenterRatio = copyFrom.mHintLabelOffCenterRatio;
|
||||
|
||||
mAnimAlpha = copyFrom.mAnimAlpha;
|
||||
}
|
||||
|
||||
public void updateParams(final int keyHeight, final KeyVisualAttributes attr) {
|
||||
if (attr == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.mTypeface != null) {
|
||||
mTypeface = attr.mTypeface;
|
||||
}
|
||||
|
||||
mLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight,
|
||||
attr.mLetterSize, attr.mLetterRatio, mLetterSize);
|
||||
mLabelSize = selectTextSizeFromDimensionOrRatio(keyHeight,
|
||||
attr.mLabelSize, attr.mLabelRatio, mLabelSize);
|
||||
mLargeLetterSize = selectTextSize(keyHeight, attr.mLargeLetterRatio, mLargeLetterSize);
|
||||
mHintLetterSize = selectTextSize(keyHeight, attr.mHintLetterRatio, mHintLetterSize);
|
||||
mShiftedLetterHintSize = selectTextSize(keyHeight,
|
||||
attr.mShiftedLetterHintRatio, mShiftedLetterHintSize);
|
||||
mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize);
|
||||
mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize);
|
||||
|
||||
mTextColor = selectColor(attr.mTextColor, mTextColor);
|
||||
mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
|
||||
mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
|
||||
mFunctionalTextColor = selectColor(attr.mFunctionalTextColor, mFunctionalTextColor);
|
||||
mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
|
||||
mHintLabelColor = selectColor(attr.mHintLabelColor, mHintLabelColor);
|
||||
mShiftedLetterHintInactivatedColor = selectColor(
|
||||
attr.mShiftedLetterHintInactivatedColor, mShiftedLetterHintInactivatedColor);
|
||||
mShiftedLetterHintActivatedColor = selectColor(
|
||||
attr.mShiftedLetterHintActivatedColor, mShiftedLetterHintActivatedColor);
|
||||
mPreviewTextColor = selectColor(attr.mPreviewTextColor, mPreviewTextColor);
|
||||
|
||||
mHintLabelVerticalAdjustment = selectFloatIfNonZero(
|
||||
attr.mHintLabelVerticalAdjustment, mHintLabelVerticalAdjustment);
|
||||
mLabelOffCenterRatio = selectFloatIfNonZero(
|
||||
attr.mLabelOffCenterRatio, mLabelOffCenterRatio);
|
||||
mHintLabelOffCenterRatio = selectFloatIfNonZero(
|
||||
attr.mHintLabelOffCenterRatio, mHintLabelOffCenterRatio);
|
||||
}
|
||||
|
||||
public KeyDrawParams mayCloneAndUpdateParams(final int keyHeight,
|
||||
final KeyVisualAttributes attr) {
|
||||
if (attr == null) {
|
||||
return this;
|
||||
}
|
||||
final KeyDrawParams newParams = new KeyDrawParams(this);
|
||||
newParams.updateParams(keyHeight, attr);
|
||||
return newParams;
|
||||
}
|
||||
|
||||
private static int selectTextSizeFromDimensionOrRatio(final int keyHeight,
|
||||
final int dimens, final float ratio, final int defaultDimens) {
|
||||
if (ResourceUtils.isValidDimensionPixelSize(dimens)) {
|
||||
return dimens;
|
||||
}
|
||||
if (ResourceUtils.isValidFraction(ratio)) {
|
||||
return (int)(keyHeight * ratio);
|
||||
}
|
||||
return defaultDimens;
|
||||
}
|
||||
|
||||
private static int selectTextSize(final int keyHeight, final float ratio,
|
||||
final int defaultSize) {
|
||||
if (ResourceUtils.isValidFraction(ratio)) {
|
||||
return (int)(keyHeight * ratio);
|
||||
}
|
||||
return defaultSize;
|
||||
}
|
||||
|
||||
private static int selectColor(final int attrColor, final int defaultColor) {
|
||||
if (attrColor != 0) {
|
||||
return attrColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
private static float selectFloatIfNonZero(final float attrFloat, final float defaultFloat) {
|
||||
if (attrFloat != 0) {
|
||||
return attrFloat;
|
||||
}
|
||||
return defaultFloat;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.latin.common.CoordinateUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.ViewLayoutUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashMap;
|
||||
;
|
||||
|
||||
/**
|
||||
* This class controls pop up key previews. This class decides:
|
||||
* - what kind of key previews should be shown.
|
||||
* - where key previews should be placed.
|
||||
* - how key previews should be shown and dismissed.
|
||||
*/
|
||||
public final class KeyPreviewChoreographer {
|
||||
// Free {@link KeyPreviewView} pool that can be used for key preview.
|
||||
private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>();
|
||||
// Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key
|
||||
// preview.
|
||||
private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>();
|
||||
|
||||
private final KeyPreviewDrawParams mParams;
|
||||
|
||||
public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
|
||||
mParams = params;
|
||||
}
|
||||
|
||||
public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) {
|
||||
KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key);
|
||||
if (keyPreviewView != null) {
|
||||
keyPreviewView.setScaleX(1);
|
||||
keyPreviewView.setScaleY(1);
|
||||
return keyPreviewView;
|
||||
}
|
||||
keyPreviewView = mFreeKeyPreviewViews.poll();
|
||||
if (keyPreviewView != null) {
|
||||
keyPreviewView.setScaleX(1);
|
||||
keyPreviewView.setScaleY(1);
|
||||
return keyPreviewView;
|
||||
}
|
||||
final Context context = placerView.getContext();
|
||||
keyPreviewView = new KeyPreviewView(context, null /* attrs */);
|
||||
keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId);
|
||||
placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
|
||||
return keyPreviewView;
|
||||
}
|
||||
|
||||
public void dismissKeyPreview(final Key key, final boolean withAnimation) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key);
|
||||
if (keyPreviewView == null) {
|
||||
return;
|
||||
}
|
||||
final Object tag = keyPreviewView.getTag();
|
||||
if (withAnimation) {
|
||||
if (tag instanceof KeyPreviewAnimators) {
|
||||
final KeyPreviewAnimators animators = (KeyPreviewAnimators)tag;
|
||||
animators.startDismiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Dismiss preview without animation.
|
||||
mShowingKeyPreviewViews.remove(key);
|
||||
if (tag instanceof Animator) {
|
||||
((Animator)tag).cancel();
|
||||
}
|
||||
keyPreviewView.setTag(null);
|
||||
keyPreviewView.setVisibility(View.INVISIBLE);
|
||||
mFreeKeyPreviewViews.add(keyPreviewView);
|
||||
}
|
||||
|
||||
public void placeAndShowKeyPreview(final Key key, final KeyboardIconsSet iconsSet,
|
||||
final KeyDrawParams drawParams, final int[] keyboardOrigin,
|
||||
final ViewGroup placerView, final boolean withAnimation) {
|
||||
final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView);
|
||||
placeKeyPreview(
|
||||
key, keyPreviewView, iconsSet, drawParams, keyboardOrigin);
|
||||
showKeyPreview(key, keyPreviewView, withAnimation);
|
||||
}
|
||||
|
||||
private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
|
||||
final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
|
||||
final int[] originCoords) {
|
||||
keyPreviewView.setPreviewVisual(key, iconsSet, drawParams);
|
||||
keyPreviewView.measure(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mParams.setGeometry(keyPreviewView);
|
||||
final int previewWidth = Math.max(keyPreviewView.getMeasuredWidth(), mParams.mMinPreviewWidth);
|
||||
final int previewHeight = mParams.mPreviewHeight;
|
||||
final int keyWidth = key.getWidth();
|
||||
// The key preview is horizontally aligned with the center of the visible part of the
|
||||
// parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
|
||||
// the left/right background is used if such background is specified.
|
||||
int previewX = key.getX() - (previewWidth - keyWidth) / 2
|
||||
+ CoordinateUtils.x(originCoords);
|
||||
// The key preview is placed vertically above the top edge of the parent key with an
|
||||
// arbitrary offset.
|
||||
final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
|
||||
+ CoordinateUtils.y(originCoords);
|
||||
|
||||
ViewLayoutUtils.placeViewAt(
|
||||
keyPreviewView, previewX, previewY, previewWidth, previewHeight);
|
||||
//keyPreviewView.setPivotX(previewWidth / 2.0f);
|
||||
//keyPreviewView.setPivotY(previewHeight);
|
||||
}
|
||||
|
||||
void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
|
||||
final boolean withAnimation) {
|
||||
if (!withAnimation) {
|
||||
keyPreviewView.setVisibility(View.VISIBLE);
|
||||
mShowingKeyPreviewViews.put(key, keyPreviewView);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show preview with animation.
|
||||
final Animator dismissAnimator = createDismissAnimator(key, keyPreviewView);
|
||||
final KeyPreviewAnimators animators = new KeyPreviewAnimators(dismissAnimator);
|
||||
keyPreviewView.setTag(animators);
|
||||
showKeyPreview(key, keyPreviewView, false /* withAnimation */);
|
||||
}
|
||||
|
||||
private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) {
|
||||
final Animator dismissAnimator = mParams.createDismissAnimator(keyPreviewView);
|
||||
dismissAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animator) {
|
||||
dismissKeyPreview(key, false /* withAnimation */);
|
||||
}
|
||||
});
|
||||
return dismissAnimator;
|
||||
}
|
||||
|
||||
private static class KeyPreviewAnimators extends AnimatorListenerAdapter {
|
||||
private final Animator mDismissAnimator;
|
||||
|
||||
public KeyPreviewAnimators(final Animator dismissAnimator) {
|
||||
mDismissAnimator = dismissAnimator;
|
||||
}
|
||||
|
||||
public void startDismiss() {
|
||||
mDismissAnimator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.content.res.TypedArray;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
|
||||
public final class KeyPreviewDrawParams {
|
||||
// XML attributes of {@link MainKeyboardView}.
|
||||
public final int mPreviewOffset;
|
||||
public final int mPreviewHeight;
|
||||
public final int mMinPreviewWidth;
|
||||
public final int mPreviewBackgroundResId;
|
||||
private final int mDismissAnimatorResId;
|
||||
private int mLingerTimeout;
|
||||
private boolean mShowPopup = true;
|
||||
|
||||
// The graphical geometry of the key preview.
|
||||
// <-width->
|
||||
// +-------+ ^
|
||||
// | | |
|
||||
// |preview| height (visible)
|
||||
// | | |
|
||||
// + + ^ v
|
||||
// \ / |offset
|
||||
// +-\ /-+ v
|
||||
// | +-+ |
|
||||
// |parent |
|
||||
// | key|
|
||||
// +-------+
|
||||
// The background of a {@link TextView} being used for a key preview may have invisible
|
||||
// paddings. To align the more keys keyboard panel's visible part with the visible part of
|
||||
// the background, we need to record the width and height of key preview that don't include
|
||||
// invisible paddings.
|
||||
private int mVisibleWidth;
|
||||
private int mVisibleHeight;
|
||||
// The key preview may have an arbitrary offset and its background that may have a bottom
|
||||
// padding. To align the more keys keyboard and the key preview we also need to record the
|
||||
// offset between the top edge of parent key and the bottom of the visible part of key
|
||||
// preview background.
|
||||
private int mVisibleOffset;
|
||||
|
||||
public KeyPreviewDrawParams(final TypedArray mainKeyboardViewAttr) {
|
||||
mPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
|
||||
R.styleable.MainKeyboardView_keyPreviewOffset, 0);
|
||||
mPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
|
||||
R.styleable.MainKeyboardView_keyPreviewHeight, 0);
|
||||
mMinPreviewWidth = mainKeyboardViewAttr.getDimensionPixelSize(
|
||||
R.styleable.MainKeyboardView_keyPreviewWidth, 0);
|
||||
mPreviewBackgroundResId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_keyPreviewBackground, 0);
|
||||
mLingerTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
|
||||
mDismissAnimatorResId = mainKeyboardViewAttr.getResourceId(
|
||||
R.styleable.MainKeyboardView_keyPreviewDismissAnimator, 0);
|
||||
}
|
||||
|
||||
public void setVisibleOffset(final int previewVisibleOffset) {
|
||||
mVisibleOffset = previewVisibleOffset;
|
||||
}
|
||||
|
||||
public int getVisibleOffset() {
|
||||
return mVisibleOffset;
|
||||
}
|
||||
|
||||
public void setGeometry(final View previewTextView) {
|
||||
final int previewWidth = Math.max(previewTextView.getMeasuredWidth(), mMinPreviewWidth);
|
||||
|
||||
// The width and height of visible part of the key preview background. The content marker
|
||||
// of the background 9-patch have to cover the visible part of the background.
|
||||
mVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
|
||||
- previewTextView.getPaddingRight();
|
||||
mVisibleHeight = mPreviewHeight - previewTextView.getPaddingTop()
|
||||
- previewTextView.getPaddingBottom();
|
||||
// The distance between the top edge of the parent key and the bottom of the visible part
|
||||
// of the key preview background.
|
||||
setVisibleOffset(mPreviewOffset - previewTextView.getPaddingBottom());
|
||||
}
|
||||
|
||||
public int getVisibleWidth() {
|
||||
return mVisibleWidth;
|
||||
}
|
||||
|
||||
public int getVisibleHeight() {
|
||||
return mVisibleHeight;
|
||||
}
|
||||
|
||||
public void setPopupEnabled(final boolean enabled, final int lingerTimeout) {
|
||||
mShowPopup = enabled;
|
||||
mLingerTimeout = lingerTimeout;
|
||||
}
|
||||
|
||||
public boolean isPopupEnabled() {
|
||||
return mShowPopup;
|
||||
}
|
||||
|
||||
public int getLingerTimeout() {
|
||||
return mLingerTimeout;
|
||||
}
|
||||
|
||||
private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
|
||||
new AccelerateInterpolator();
|
||||
|
||||
public Animator createDismissAnimator(final View target) {
|
||||
final Animator animator = AnimatorInflater.loadAnimator(
|
||||
target.getContext(), mDismissAnimatorResId);
|
||||
animator.setTarget(target);
|
||||
animator.setInterpolator(ACCELERATE_INTERPOLATOR);
|
||||
return animator;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* The pop up key preview view.
|
||||
*/
|
||||
public class KeyPreviewView extends androidx.appcompat.widget.AppCompatTextView {
|
||||
private final Rect mBackgroundPadding = new Rect();
|
||||
private static final HashSet<String> sNoScaleXTextSet = new HashSet<>();
|
||||
|
||||
public KeyPreviewView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public KeyPreviewView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setGravity(Gravity.CENTER);
|
||||
}
|
||||
|
||||
public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet,
|
||||
final KeyDrawParams drawParams) {
|
||||
// What we show as preview should match what we show on a key top in onDraw().
|
||||
final int iconId = key.getIconId();
|
||||
if (iconId != KeyboardIconsSet.ICON_UNDEFINED) {
|
||||
setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
|
||||
setText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setCompoundDrawables(null, null, null, null);
|
||||
setTextColor(drawParams.mPreviewTextColor);
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams));
|
||||
setTypeface(key.selectPreviewTypeface(drawParams));
|
||||
// TODO Should take care of temporaryShiftLabel here.
|
||||
setTextAndScaleX(key.getPreviewLabel());
|
||||
}
|
||||
|
||||
private void setTextAndScaleX(final String text) {
|
||||
setTextScaleX(1.0f);
|
||||
setText(text);
|
||||
if (sNoScaleXTextSet.contains(text)) {
|
||||
return;
|
||||
}
|
||||
// TODO: Override {@link #setBackground(Drawable)} that is supported from API 16 and
|
||||
// calculate maximum text width.
|
||||
final Drawable background = getBackground();
|
||||
if (background == null) {
|
||||
return;
|
||||
}
|
||||
background.getPadding(mBackgroundPadding);
|
||||
final int maxWidth = background.getIntrinsicWidth() - mBackgroundPadding.left
|
||||
- mBackgroundPadding.right;
|
||||
final float width = getTextWidth(text, getPaint());
|
||||
if (width <= maxWidth) {
|
||||
sNoScaleXTextSet.add(text);
|
||||
return;
|
||||
}
|
||||
setTextScaleX(maxWidth / width);
|
||||
}
|
||||
|
||||
public static void clearTextCache() {
|
||||
sNoScaleXTextSet.clear();
|
||||
}
|
||||
|
||||
private static float getTextWidth(final String text, final TextPaint paint) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return 0.0f;
|
||||
}
|
||||
final int len = text.length();
|
||||
final float[] widths = new float[len];
|
||||
final int count = paint.getTextWidths(text, 0, len, widths);
|
||||
float width = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
width += widths[i];
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/*public void setPreviewBackground(boolean customColorEnabled, int customColor) {
|
||||
final Drawable background = getBackground();
|
||||
if (customColorEnabled)
|
||||
background.setColorFilter(customColor, PorterDuff.Mode.OVERLAY);
|
||||
}*/
|
||||
}
|
||||
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_OUTPUT_TEXT;
|
||||
import static com.colorful.keyboard.theme.latin.common.Constants.CODE_UNSPECIFIED;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
|
||||
// TODO: Rename to KeySpec and make this class to the key specification object.
|
||||
public final class KeySpecParser {
|
||||
// Constants for parsing.
|
||||
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
|
||||
private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR;
|
||||
private static final String PREFIX_HEX = "0x";
|
||||
|
||||
private KeySpecParser() {
|
||||
// Intentional empty constructor for utility class.
|
||||
}
|
||||
|
||||
private static boolean hasIcon(final String keySpec) {
|
||||
return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
|
||||
}
|
||||
|
||||
private static boolean hasCode(final String keySpec, final int labelEnd) {
|
||||
if (labelEnd <= 0 || labelEnd + 1 >= keySpec.length()) {
|
||||
return false;
|
||||
}
|
||||
if (keySpec.startsWith(KeyboardCodesSet.PREFIX_CODE, labelEnd + 1)) {
|
||||
return true;
|
||||
}
|
||||
// This is a workaround to have a key that has a supplementary code point. We can't put a
|
||||
// string in resource as a XML entity of a supplementary code point or a surrogate pair.
|
||||
return keySpec.startsWith(PREFIX_HEX, labelEnd + 1);
|
||||
}
|
||||
|
||||
private static String parseEscape(final String text) {
|
||||
if (text.indexOf(BACKSLASH) < 0) {
|
||||
return text;
|
||||
}
|
||||
final int length = text.length();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int pos = 0; pos < length; pos++) {
|
||||
final char c = text.charAt(pos);
|
||||
if (c == BACKSLASH && pos + 1 < length) {
|
||||
// Skip escape char
|
||||
pos++;
|
||||
sb.append(text.charAt(pos));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static int indexOfLabelEnd(final String keySpec) {
|
||||
final int length = keySpec.length();
|
||||
if (keySpec.indexOf(BACKSLASH) < 0) {
|
||||
final int labelEnd = keySpec.indexOf(VERTICAL_BAR);
|
||||
if (labelEnd == 0) {
|
||||
if (length == 1) {
|
||||
// Treat a sole vertical bar as a special case of key label.
|
||||
return -1;
|
||||
}
|
||||
throw new KeySpecParserError("Empty label");
|
||||
}
|
||||
return labelEnd;
|
||||
}
|
||||
for (int pos = 0; pos < length; pos++) {
|
||||
final char c = keySpec.charAt(pos);
|
||||
if (c == BACKSLASH && pos + 1 < length) {
|
||||
// Skip escape char
|
||||
pos++;
|
||||
} else if (c == VERTICAL_BAR) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String getBeforeLabelEnd(final String keySpec, final int labelEnd) {
|
||||
return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd);
|
||||
}
|
||||
|
||||
private static String getAfterLabelEnd(final String keySpec, final int labelEnd) {
|
||||
return keySpec.substring(labelEnd + /* VERTICAL_BAR */1);
|
||||
}
|
||||
|
||||
private static void checkDoubleLabelEnd(final String keySpec, final int labelEnd) {
|
||||
if (indexOfLabelEnd(getAfterLabelEnd(keySpec, labelEnd)) < 0) {
|
||||
return;
|
||||
}
|
||||
throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
|
||||
}
|
||||
|
||||
public static String getLabel(final String keySpec) {
|
||||
if (keySpec == null) {
|
||||
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
|
||||
return null;
|
||||
}
|
||||
if (hasIcon(keySpec)) {
|
||||
return null;
|
||||
}
|
||||
final int labelEnd = indexOfLabelEnd(keySpec);
|
||||
final String label = parseEscape(getBeforeLabelEnd(keySpec, labelEnd));
|
||||
if (label.isEmpty()) {
|
||||
throw new KeySpecParserError("Empty label: " + keySpec);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private static String getOutputTextInternal(final String keySpec, final int labelEnd) {
|
||||
if (labelEnd <= 0) {
|
||||
return null;
|
||||
}
|
||||
checkDoubleLabelEnd(keySpec, labelEnd);
|
||||
return parseEscape(getAfterLabelEnd(keySpec, labelEnd));
|
||||
}
|
||||
|
||||
public static String getOutputText(final String keySpec) {
|
||||
if (keySpec == null) {
|
||||
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
|
||||
return null;
|
||||
}
|
||||
final int labelEnd = indexOfLabelEnd(keySpec);
|
||||
if (hasCode(keySpec, labelEnd)) {
|
||||
return null;
|
||||
}
|
||||
final String outputText = getOutputTextInternal(keySpec, labelEnd);
|
||||
if (outputText != null) {
|
||||
if (StringUtils.codePointCount(outputText) == 1) {
|
||||
// If output text is one code point, it should be treated as a code.
|
||||
// See {@link #getCode(Resources, String)}.
|
||||
return null;
|
||||
}
|
||||
if (outputText.isEmpty()) {
|
||||
throw new KeySpecParserError("Empty outputText: " + keySpec);
|
||||
}
|
||||
return outputText;
|
||||
}
|
||||
final String label = getLabel(keySpec);
|
||||
if (label == null) {
|
||||
throw new KeySpecParserError("Empty label: " + keySpec);
|
||||
}
|
||||
// Code is automatically generated for one letter label. See {@link getCode()}.
|
||||
return (StringUtils.codePointCount(label) == 1) ? null : label;
|
||||
}
|
||||
|
||||
public static int getCode(final String keySpec) {
|
||||
if (keySpec == null) {
|
||||
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
|
||||
return CODE_UNSPECIFIED;
|
||||
}
|
||||
final int labelEnd = indexOfLabelEnd(keySpec);
|
||||
if (hasCode(keySpec, labelEnd)) {
|
||||
checkDoubleLabelEnd(keySpec, labelEnd);
|
||||
return parseCode(getAfterLabelEnd(keySpec, labelEnd), CODE_UNSPECIFIED);
|
||||
}
|
||||
final String outputText = getOutputTextInternal(keySpec, labelEnd);
|
||||
if (outputText != null) {
|
||||
// If output text is one code point, it should be treated as a code.
|
||||
// See {@link #getOutputText(String)}.
|
||||
if (StringUtils.codePointCount(outputText) == 1) {
|
||||
return outputText.codePointAt(0);
|
||||
}
|
||||
return CODE_OUTPUT_TEXT;
|
||||
}
|
||||
final String label = getLabel(keySpec);
|
||||
if (label == null) {
|
||||
throw new KeySpecParserError("Empty label: " + keySpec);
|
||||
}
|
||||
// Code is automatically generated for one letter label.
|
||||
return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT;
|
||||
}
|
||||
|
||||
public static int parseCode(final String text, final int defaultCode) {
|
||||
if (text == null) {
|
||||
return defaultCode;
|
||||
}
|
||||
if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) {
|
||||
return KeyboardCodesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length()));
|
||||
}
|
||||
// This is a workaround to have a key that has a supplementary code point. We can't put a
|
||||
// string in resource as a XML entity of a supplementary code point or a surrogate pair.
|
||||
if (text.startsWith(PREFIX_HEX)) {
|
||||
return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
|
||||
}
|
||||
return defaultCode;
|
||||
}
|
||||
|
||||
public static int getIconId(final String keySpec) {
|
||||
if (keySpec == null) {
|
||||
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
|
||||
return KeyboardIconsSet.ICON_UNDEFINED;
|
||||
}
|
||||
if (!hasIcon(keySpec)) {
|
||||
return KeyboardIconsSet.ICON_UNDEFINED;
|
||||
}
|
||||
final int labelEnd = indexOfLabelEnd(keySpec);
|
||||
final String iconName = getBeforeLabelEnd(keySpec, labelEnd)
|
||||
.substring(KeyboardIconsSet.PREFIX_ICON.length());
|
||||
return KeyboardIconsSet.getIconId(iconName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final class KeySpecParserError extends RuntimeException {
|
||||
public KeySpecParserError(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.TypedArray;
|
||||
|
||||
public abstract class KeyStyle {
|
||||
private final KeyboardTextsSet mTextsSet;
|
||||
|
||||
public abstract String[] getStringArray(TypedArray a, int index);
|
||||
public abstract String getString(TypedArray a, int index);
|
||||
public abstract int getInt(TypedArray a, int index, int defaultValue);
|
||||
public abstract int getFlags(TypedArray a, int index);
|
||||
|
||||
protected KeyStyle(final KeyboardTextsSet textsSet) {
|
||||
mTextsSet = textsSet;
|
||||
}
|
||||
|
||||
protected String parseString(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
return mTextsSet.resolveTextReference(a.getString(index));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String[] parseStringArray(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
final String text = mTextsSet.resolveTextReference(a.getString(index));
|
||||
return MoreKeySpec.splitKeySpecs(text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.latin.utils.XmlParseUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class KeyStylesSet {
|
||||
private static final String TAG = KeyStylesSet.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final HashMap<String, KeyStyle> mStyles = new HashMap<>();
|
||||
|
||||
private final KeyboardTextsSet mTextsSet;
|
||||
private final KeyStyle mEmptyKeyStyle;
|
||||
private static final String EMPTY_STYLE_NAME = "<empty>";
|
||||
|
||||
public KeyStylesSet(final KeyboardTextsSet textsSet) {
|
||||
mTextsSet = textsSet;
|
||||
mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
|
||||
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
|
||||
}
|
||||
|
||||
private static final class EmptyKeyStyle extends KeyStyle {
|
||||
EmptyKeyStyle(final KeyboardTextsSet textsSet) {
|
||||
super(textsSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStringArray(final TypedArray a, final int index) {
|
||||
return parseStringArray(a, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(final TypedArray a, final int index) {
|
||||
return parseString(a, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(final TypedArray a, final int index, final int defaultValue) {
|
||||
return a.getInt(index, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlags(final TypedArray a, final int index) {
|
||||
return a.getInt(index, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DeclaredKeyStyle extends KeyStyle {
|
||||
private final HashMap<String, KeyStyle> mStyles;
|
||||
private final String mParentStyleName;
|
||||
private final SparseArray<Object> mStyleAttributes = new SparseArray<>();
|
||||
|
||||
public DeclaredKeyStyle(final String parentStyleName,
|
||||
final KeyboardTextsSet textsSet,
|
||||
final HashMap<String, KeyStyle> styles) {
|
||||
super(textsSet);
|
||||
mParentStyleName = parentStyleName;
|
||||
mStyles = styles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStringArray(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
return parseStringArray(a, index);
|
||||
}
|
||||
final Object value = mStyleAttributes.get(index);
|
||||
if (value != null) {
|
||||
final String[] array = (String[])value;
|
||||
return Arrays.copyOf(array, array.length);
|
||||
}
|
||||
final KeyStyle parentStyle = mStyles.get(mParentStyleName);
|
||||
return parentStyle.getStringArray(a, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
return parseString(a, index);
|
||||
}
|
||||
final Object value = mStyleAttributes.get(index);
|
||||
if (value != null) {
|
||||
return (String)value;
|
||||
}
|
||||
final KeyStyle parentStyle = mStyles.get(mParentStyleName);
|
||||
return parentStyle.getString(a, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(final TypedArray a, final int index, final int defaultValue) {
|
||||
if (a.hasValue(index)) {
|
||||
return a.getInt(index, defaultValue);
|
||||
}
|
||||
final Object value = mStyleAttributes.get(index);
|
||||
if (value != null) {
|
||||
return (Integer)value;
|
||||
}
|
||||
final KeyStyle parentStyle = mStyles.get(mParentStyleName);
|
||||
return parentStyle.getInt(a, index, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlags(final TypedArray a, final int index) {
|
||||
final int parentFlags = mStyles.get(mParentStyleName).getFlags(a, index);
|
||||
final Integer value = (Integer)mStyleAttributes.get(index);
|
||||
final int styleFlags = (value != null) ? value : 0;
|
||||
final int flags = a.getInt(index, 0);
|
||||
return flags | styleFlags | parentFlags;
|
||||
}
|
||||
|
||||
public void readKeyAttributes(final TypedArray keyAttr) {
|
||||
// TODO: Currently not all Key attributes can be declared as style.
|
||||
readString(keyAttr, R.styleable.Keyboard_Key_altCode);
|
||||
readString(keyAttr, R.styleable.Keyboard_Key_keySpec);
|
||||
readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
|
||||
readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
|
||||
readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
|
||||
readFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
|
||||
readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
|
||||
readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
|
||||
readFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
|
||||
}
|
||||
|
||||
private void readString(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
mStyleAttributes.put(index, parseString(a, index));
|
||||
}
|
||||
}
|
||||
|
||||
private void readInt(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
mStyleAttributes.put(index, a.getInt(index, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void readFlags(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
final Integer value = (Integer)mStyleAttributes.get(index);
|
||||
final int styleFlags = value != null ? value : 0;
|
||||
mStyleAttributes.put(index, a.getInt(index, 0) | styleFlags);
|
||||
}
|
||||
}
|
||||
|
||||
private void readStringArray(final TypedArray a, final int index) {
|
||||
if (a.hasValue(index)) {
|
||||
mStyleAttributes.put(index, parseStringArray(a, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
|
||||
final XmlPullParser parser) throws XmlPullParserException {
|
||||
final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
|
||||
if (styleName == null) {
|
||||
throw new XmlParseUtils.ParseException(
|
||||
KeyboardBuilder.TAG_KEY_STYLE + " has no styleName attribute", parser);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format("<%s styleName=%s />",
|
||||
KeyboardBuilder.TAG_KEY_STYLE, styleName));
|
||||
if (mStyles.containsKey(styleName)) {
|
||||
Log.d(TAG, KeyboardBuilder.TAG_KEY_STYLE + " " + styleName + " is overridden at "
|
||||
+ parser.getPositionDescription());
|
||||
}
|
||||
}
|
||||
|
||||
final String parentStyleInAttr = keyStyleAttr.getString(
|
||||
R.styleable.Keyboard_KeyStyle_parentStyle);
|
||||
if (parentStyleInAttr != null && !mStyles.containsKey(parentStyleInAttr)) {
|
||||
throw new XmlParseUtils.ParseException(
|
||||
"Unknown parentStyle " + parentStyleInAttr, parser);
|
||||
}
|
||||
final String parentStyleName = (parentStyleInAttr == null) ? EMPTY_STYLE_NAME
|
||||
: parentStyleInAttr;
|
||||
final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
|
||||
style.readKeyAttributes(keyAttrs);
|
||||
mStyles.put(styleName, style);
|
||||
}
|
||||
|
||||
public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
|
||||
throws XmlParseUtils.ParseException {
|
||||
final String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
|
||||
if (styleName == null) {
|
||||
return mEmptyKeyStyle;
|
||||
}
|
||||
final KeyStyle style = mStyles.get(styleName);
|
||||
if (style == null) {
|
||||
throw new XmlParseUtils.ParseException("Unknown key style: " + styleName, parser);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.latin.utils.ResourceUtils;
|
||||
|
||||
public final class KeyVisualAttributes {
|
||||
public final Typeface mTypeface;
|
||||
|
||||
public final float mLetterRatio;
|
||||
public final int mLetterSize;
|
||||
public final float mLabelRatio;
|
||||
public final int mLabelSize;
|
||||
public final float mLargeLetterRatio;
|
||||
public final float mHintLetterRatio;
|
||||
public final float mShiftedLetterHintRatio;
|
||||
public final float mHintLabelRatio;
|
||||
public final float mPreviewTextRatio;
|
||||
|
||||
public final int mTextColor;
|
||||
public final int mTextInactivatedColor;
|
||||
public final int mTextShadowColor;
|
||||
public final int mFunctionalTextColor;
|
||||
public final int mHintLetterColor;
|
||||
public final int mHintLabelColor;
|
||||
public final int mShiftedLetterHintInactivatedColor;
|
||||
public final int mShiftedLetterHintActivatedColor;
|
||||
public final int mPreviewTextColor;
|
||||
|
||||
public final float mHintLabelVerticalAdjustment;
|
||||
public final float mLabelOffCenterRatio;
|
||||
public final float mHintLabelOffCenterRatio;
|
||||
|
||||
private static final int[] VISUAL_ATTRIBUTE_IDS = {
|
||||
R.styleable.Keyboard_Key_keyTypeface,
|
||||
R.styleable.Keyboard_Key_keyLetterSize,
|
||||
R.styleable.Keyboard_Key_keyLabelSize,
|
||||
R.styleable.Keyboard_Key_keyLargeLetterRatio,
|
||||
R.styleable.Keyboard_Key_keyHintLetterRatio,
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintRatio,
|
||||
R.styleable.Keyboard_Key_keyHintLabelRatio,
|
||||
R.styleable.Keyboard_Key_keyPreviewTextRatio,
|
||||
R.styleable.Keyboard_Key_keyTextColor,
|
||||
R.styleable.Keyboard_Key_keyTextInactivatedColor,
|
||||
R.styleable.Keyboard_Key_keyTextShadowColor,
|
||||
R.styleable.Keyboard_Key_functionalTextColor,
|
||||
R.styleable.Keyboard_Key_keyHintLetterColor,
|
||||
R.styleable.Keyboard_Key_keyHintLabelColor,
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor,
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor,
|
||||
R.styleable.Keyboard_Key_keyPreviewTextColor,
|
||||
R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment,
|
||||
R.styleable.Keyboard_Key_keyLabelOffCenterRatio,
|
||||
R.styleable.Keyboard_Key_keyHintLabelOffCenterRatio
|
||||
};
|
||||
private static final SparseIntArray sVisualAttributeIds = new SparseIntArray();
|
||||
private static final int ATTR_DEFINED = 1;
|
||||
private static final int ATTR_NOT_FOUND = 0;
|
||||
static {
|
||||
for (final int attrId : VISUAL_ATTRIBUTE_IDS) {
|
||||
sVisualAttributeIds.put(attrId, ATTR_DEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyVisualAttributes newInstance(final TypedArray keyAttr) {
|
||||
final int indexCount = keyAttr.getIndexCount();
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
final int attrId = keyAttr.getIndex(i);
|
||||
if (sVisualAttributeIds.get(attrId, ATTR_NOT_FOUND) == ATTR_NOT_FOUND) {
|
||||
continue;
|
||||
}
|
||||
return new KeyVisualAttributes(keyAttr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private KeyVisualAttributes(final TypedArray keyAttr) {
|
||||
if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) {
|
||||
mTypeface = Typeface.defaultFromStyle(
|
||||
keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL));
|
||||
} else {
|
||||
mTypeface = null;
|
||||
}
|
||||
|
||||
mLetterRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLetterSize);
|
||||
mLetterSize = ResourceUtils.getDimensionPixelSize(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLetterSize);
|
||||
mLabelRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLabelSize);
|
||||
mLabelSize = ResourceUtils.getDimensionPixelSize(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLabelSize);
|
||||
mLargeLetterRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLargeLetterRatio);
|
||||
mHintLetterRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyHintLetterRatio);
|
||||
mShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintRatio);
|
||||
mHintLabelRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyHintLabelRatio);
|
||||
mPreviewTextRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyPreviewTextRatio);
|
||||
|
||||
mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0);
|
||||
mTextInactivatedColor = keyAttr.getColor(
|
||||
R.styleable.Keyboard_Key_keyTextInactivatedColor, 0);
|
||||
mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0);
|
||||
mFunctionalTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_functionalTextColor, 0);
|
||||
mHintLetterColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLetterColor, 0);
|
||||
mHintLabelColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLabelColor, 0);
|
||||
mShiftedLetterHintInactivatedColor = keyAttr.getColor(
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, 0);
|
||||
mShiftedLetterHintActivatedColor = keyAttr.getColor(
|
||||
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
|
||||
mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0);
|
||||
|
||||
mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f);
|
||||
mLabelOffCenterRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyLabelOffCenterRatio, 0.0f);
|
||||
mHintLabelOffCenterRatio = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyHintLabelOffCenterRatio, 0.0f);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,762 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.keyboard.Keyboard;
|
||||
import com.colorful.keyboard.theme.keyboard.KeyboardId;
|
||||
import com.colorful.keyboard.theme.keyboard.KeyboardTheme;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.ResourceUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.XmlParseUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* Keyboard Building helper.
|
||||
*
|
||||
* This class parses Keyboard XML file and eventually build a Keyboard.
|
||||
* The Keyboard XML file looks like:
|
||||
* <pre>
|
||||
* <!-- xml/keyboard.xml -->
|
||||
* <Keyboard keyboard_attributes*>
|
||||
* <!-- Keyboard Content -->
|
||||
* <Row row_attributes*>
|
||||
* <!-- Row Content -->
|
||||
* <Key key_attributes* />
|
||||
* <Spacer horizontalGap="32.0dp" />
|
||||
* <include keyboardLayout="@xml/other_keys">
|
||||
* ...
|
||||
* </Row>
|
||||
* <include keyboardLayout="@xml/other_rows">
|
||||
* ...
|
||||
* </Keyboard>
|
||||
* </pre>
|
||||
* The XML file which is included in other file must have <merge> as root element,
|
||||
* such as:
|
||||
* <pre>
|
||||
* <!-- xml/other_keys.xml -->
|
||||
* <merge>
|
||||
* <Key key_attributes* />
|
||||
* ...
|
||||
* </merge>
|
||||
* </pre>
|
||||
* and
|
||||
* <pre>
|
||||
* <!-- xml/other_rows.xml -->
|
||||
* <merge>
|
||||
* <Row row_attributes*>
|
||||
* <Key key_attributes* />
|
||||
* </Row>
|
||||
* ...
|
||||
* </merge>
|
||||
* </pre>
|
||||
* You can also use switch-case-default tags to select Rows and Keys.
|
||||
* <pre>
|
||||
* <switch>
|
||||
* <case case_attribute*>
|
||||
* <!-- Any valid tags at switch position -->
|
||||
* </case>
|
||||
* ...
|
||||
* <default>
|
||||
* <!-- Any valid tags at switch position -->
|
||||
* </default>
|
||||
* </switch>
|
||||
* </pre>
|
||||
* You can declare Key style and specify styles within Key tags.
|
||||
* <pre>
|
||||
* <switch>
|
||||
* <case mode="email">
|
||||
* <key-style styleName="f1-key" parentStyle="modifier-key"
|
||||
* keyLabel=".com"
|
||||
* />
|
||||
* </case>
|
||||
* <case mode="url">
|
||||
* <key-style styleName="f1-key" parentStyle="modifier-key"
|
||||
* keyLabel="http://"
|
||||
* />
|
||||
* </case>
|
||||
* </switch>
|
||||
* ...
|
||||
* <Key keyStyle="shift-key" ... />
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
// TODO: Write unit tests for this class.
|
||||
public class KeyboardBuilder<KP extends KeyboardParams> {
|
||||
private static final String BUILDER_TAG = "Keyboard.Builder";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// Keyboard XML Tags
|
||||
private static final String TAG_KEYBOARD = "Keyboard";
|
||||
private static final String TAG_ROW = "Row";
|
||||
private static final String TAG_KEY = "Key";
|
||||
private static final String TAG_SPACER = "Spacer";
|
||||
private static final String TAG_INCLUDE = "include";
|
||||
private static final String TAG_MERGE = "merge";
|
||||
private static final String TAG_SWITCH = "switch";
|
||||
private static final String TAG_CASE = "case";
|
||||
private static final String TAG_DEFAULT = "default";
|
||||
public static final String TAG_KEY_STYLE = "key-style";
|
||||
|
||||
private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
|
||||
private static final int DEFAULT_KEYBOARD_ROWS = 4;
|
||||
|
||||
protected final KP mParams;
|
||||
protected final Context mContext;
|
||||
protected final Resources mResources;
|
||||
|
||||
private float mCurrentY = 0;
|
||||
private KeyboardRow mCurrentRow = null;
|
||||
private Key mPreviousKeyInRow = null;
|
||||
private boolean mKeyboardDefined = false;
|
||||
|
||||
public KeyboardBuilder(final Context context, final KP params) {
|
||||
mContext = context;
|
||||
final Resources res = context.getResources();
|
||||
mResources = res;
|
||||
|
||||
mParams = params;
|
||||
|
||||
params.mGridWidth = res.getInteger(R.integer.config_keyboard_grid_width);
|
||||
params.mGridHeight = res.getInteger(R.integer.config_keyboard_grid_height);
|
||||
}
|
||||
|
||||
public void setAllowRedundantMoreKes(final boolean enabled) {
|
||||
mParams.mAllowRedundantMoreKeys = enabled;
|
||||
}
|
||||
|
||||
public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
|
||||
mParams.mId = id;
|
||||
final XmlResourceParser parser = mResources.getXml(xmlId);
|
||||
try {
|
||||
parseKeyboard(parser, false);
|
||||
if (!mKeyboardDefined) {
|
||||
throw new XmlParseUtils.ParseException("No " + TAG_KEYBOARD + " tag was found");
|
||||
}
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.w(BUILDER_TAG, "keyboard XML parse error", e);
|
||||
throw new IllegalArgumentException(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
Log.w(BUILDER_TAG, "keyboard XML parse error", e);
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Keyboard build() {
|
||||
return new Keyboard(mParams);
|
||||
}
|
||||
|
||||
private int mIndent;
|
||||
private static final String SPACES = " ";
|
||||
|
||||
private static String spaces(final int count) {
|
||||
return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
|
||||
}
|
||||
|
||||
private void startTag(final String format, final Object ... args) {
|
||||
Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
|
||||
}
|
||||
|
||||
private void endTag(final String format, final Object ... args) {
|
||||
Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
|
||||
}
|
||||
|
||||
private void startEndTag(final String format, final Object ... args) {
|
||||
Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
|
||||
mIndent--;
|
||||
}
|
||||
|
||||
private void parseKeyboard(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_KEYBOARD.equals(tag)) {
|
||||
if (DEBUG) startTag("<%s> %s%s", TAG_KEYBOARD, mParams.mId,
|
||||
skip ? " skipped" : "");
|
||||
if (!skip) {
|
||||
if (mKeyboardDefined) {
|
||||
throw new XmlParseUtils.ParseException("Only one " + TAG_KEYBOARD
|
||||
+ " tag can be defined", parser);
|
||||
}
|
||||
mKeyboardDefined = true;
|
||||
parseKeyboardAttributes(parser);
|
||||
startKeyboard();
|
||||
}
|
||||
parseKeyboardContent(parser, skip);
|
||||
} else if (TAG_SWITCH.equals(tag)) {
|
||||
parseSwitchKeyboard(parser, skip);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD);
|
||||
}
|
||||
} else if (event == XmlPullParser.END_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (DEBUG) endTag("</%s>", tag);
|
||||
if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) {
|
||||
return;
|
||||
}
|
||||
throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKeyboardAttributes(final XmlPullParser parser) {
|
||||
final AttributeSet attr = Xml.asAttributeSet(parser);
|
||||
final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
|
||||
attr, R.styleable.Keyboard, R.attr.keyboardStyle, R.style.Keyboard);
|
||||
final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
|
||||
try {
|
||||
final KeyboardParams params = mParams;
|
||||
final int height = params.mId.mHeight;
|
||||
final int width = params.mId.mWidth;
|
||||
// The bonus height isn't used to determine the other dimensions (gap/padding) to allow
|
||||
// those to stay consistent between layouts with and without the bonus height added.
|
||||
final int bonusHeight = (int)keyboardAttr.getFraction(R.styleable.Keyboard_bonusHeight,
|
||||
height, height, 0);
|
||||
params.mOccupiedHeight = height + bonusHeight;
|
||||
params.mOccupiedWidth = width;
|
||||
params.mTopPadding = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_keyboardTopPadding, height, 0);
|
||||
params.mBottomPadding = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_keyboardBottomPadding, height, 0);
|
||||
params.mLeftPadding = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_keyboardLeftPadding, width, 0);
|
||||
params.mRightPadding = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_keyboardRightPadding, width, 0);
|
||||
|
||||
params.mHorizontalGap = keyboardAttr.getFraction(
|
||||
R.styleable.Keyboard_horizontalGap, width, width, 0);
|
||||
final float baseWidth = params.mOccupiedWidth - params.mLeftPadding
|
||||
- params.mRightPadding + params.mHorizontalGap;
|
||||
params.mBaseWidth = baseWidth;
|
||||
params.mDefaultKeyPaddedWidth = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyWidth, baseWidth,
|
||||
baseWidth / DEFAULT_KEYBOARD_COLUMNS);
|
||||
// TODO: Fix keyboard geometry calculation clearer. Historically vertical gap between
|
||||
// rows are determined based on the entire keyboard height including top and bottom
|
||||
// paddings.
|
||||
params.mVerticalGap = keyboardAttr.getFraction(
|
||||
R.styleable.Keyboard_verticalGap, height, height, 0);
|
||||
final float baseHeight = params.mOccupiedHeight - params.mTopPadding
|
||||
- params.mBottomPadding + params.mVerticalGap;
|
||||
params.mBaseHeight = baseHeight;
|
||||
params.mDefaultRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_rowHeight, baseHeight, baseHeight / DEFAULT_KEYBOARD_ROWS);
|
||||
|
||||
params.mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
|
||||
|
||||
params.mMoreKeysTemplate = keyboardAttr.getResourceId(
|
||||
R.styleable.Keyboard_moreKeysTemplate, 0);
|
||||
params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
|
||||
R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
|
||||
|
||||
params.mIconsSet.loadIcons(keyboardAttr);
|
||||
params.mTextsSet.setLocale(params.mId.getLocale(), mContext);
|
||||
} finally {
|
||||
keyAttr.recycle();
|
||||
keyboardAttr.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKeyboardContent(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_ROW.equals(tag)) {
|
||||
final KeyboardRow row = parseRowAttributes(parser);
|
||||
if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
|
||||
if (!skip) {
|
||||
startRow(row);
|
||||
}
|
||||
parseRowContent(parser, row, skip);
|
||||
} else if (TAG_INCLUDE.equals(tag)) {
|
||||
parseIncludeKeyboardContent(parser, skip);
|
||||
} else if (TAG_SWITCH.equals(tag)) {
|
||||
parseSwitchKeyboardContent(parser, skip);
|
||||
} else if (TAG_KEY_STYLE.equals(tag)) {
|
||||
parseKeyStyle(parser, skip);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW);
|
||||
}
|
||||
} else if (event == XmlPullParser.END_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (DEBUG) endTag("</%s>", tag);
|
||||
if (TAG_KEYBOARD.equals(tag)) {
|
||||
endKeyboard();
|
||||
return;
|
||||
}
|
||||
if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) {
|
||||
return;
|
||||
}
|
||||
throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private KeyboardRow parseRowAttributes(final XmlPullParser parser)
|
||||
throws XmlPullParserException {
|
||||
final AttributeSet attr = Xml.asAttributeSet(parser);
|
||||
final TypedArray keyboardAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard);
|
||||
try {
|
||||
if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap)) {
|
||||
throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "horizontalGap");
|
||||
}
|
||||
if (keyboardAttr.hasValue(R.styleable.Keyboard_verticalGap)) {
|
||||
throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "verticalGap");
|
||||
}
|
||||
return new KeyboardRow(mResources, mParams, parser, mCurrentY);
|
||||
} finally {
|
||||
keyboardAttr.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseRowContent(final XmlPullParser parser, final KeyboardRow row,
|
||||
final boolean skip) throws XmlPullParserException, IOException {
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_KEY.equals(tag)) {
|
||||
parseKey(parser, row, skip);
|
||||
} else if (TAG_SPACER.equals(tag)) {
|
||||
parseSpacer(parser, row, skip);
|
||||
} else if (TAG_INCLUDE.equals(tag)) {
|
||||
parseIncludeRowContent(parser, row, skip);
|
||||
} else if (TAG_SWITCH.equals(tag)) {
|
||||
parseSwitchRowContent(parser, row, skip);
|
||||
} else if (TAG_KEY_STYLE.equals(tag)) {
|
||||
parseKeyStyle(parser, skip);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW);
|
||||
}
|
||||
} else if (event == XmlPullParser.END_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (DEBUG) endTag("</%s>", tag);
|
||||
if (TAG_ROW.equals(tag)) {
|
||||
if (!skip) {
|
||||
endRow(row);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) {
|
||||
return;
|
||||
}
|
||||
throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (skip) {
|
||||
XmlParseUtils.checkEndTag(TAG_KEY, parser);
|
||||
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
|
||||
return;
|
||||
}
|
||||
final TypedArray keyAttr = mResources.obtainAttributes(
|
||||
Xml.asAttributeSet(parser), R.styleable.Keyboard_Key);
|
||||
final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser);
|
||||
final String keySpec = keyStyle.getString(keyAttr, R.styleable.Keyboard_Key_keySpec);
|
||||
if (TextUtils.isEmpty(keySpec)) {
|
||||
throw new XmlParseUtils.ParseException("Empty keySpec", parser);
|
||||
}
|
||||
final Key key = new Key(keySpec, keyAttr, keyStyle, mParams, row);
|
||||
keyAttr.recycle();
|
||||
if (DEBUG) {
|
||||
startEndTag("<%s %s moreKeys=%s />", TAG_KEY, key, Arrays.toString(key.getMoreKeys()));
|
||||
}
|
||||
XmlParseUtils.checkEndTag(TAG_KEY, parser);
|
||||
endKey(key, row);
|
||||
}
|
||||
|
||||
private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (skip) {
|
||||
XmlParseUtils.checkEndTag(TAG_SPACER, parser);
|
||||
if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
|
||||
return;
|
||||
}
|
||||
final TypedArray keyAttr = mResources.obtainAttributes(
|
||||
Xml.asAttributeSet(parser), R.styleable.Keyboard_Key);
|
||||
final KeyStyle keyStyle = mParams.mKeyStyles.getKeyStyle(keyAttr, parser);
|
||||
final Key spacer = new Key.Spacer(keyAttr, keyStyle, mParams, row);
|
||||
keyAttr.recycle();
|
||||
if (DEBUG) startEndTag("<%s />", TAG_SPACER);
|
||||
XmlParseUtils.checkEndTag(TAG_SPACER, parser);
|
||||
endKey(spacer, row);
|
||||
}
|
||||
|
||||
private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
parseIncludeInternal(parser, null, skip);
|
||||
}
|
||||
|
||||
private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row,
|
||||
final boolean skip) throws XmlPullParserException, IOException {
|
||||
parseIncludeInternal(parser, row, skip);
|
||||
}
|
||||
|
||||
private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row,
|
||||
final boolean skip) throws XmlPullParserException, IOException {
|
||||
if (skip) {
|
||||
XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
|
||||
if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
|
||||
return;
|
||||
}
|
||||
final AttributeSet attr = Xml.asAttributeSet(parser);
|
||||
final TypedArray keyboardAttr = mResources.obtainAttributes(
|
||||
attr, R.styleable.Keyboard_Include);
|
||||
final TypedArray includeAttr = mResources.obtainAttributes(
|
||||
attr, R.styleable.Keyboard);
|
||||
mParams.mDefaultRowHeight = ResourceUtils.getDimensionOrFraction(includeAttr,
|
||||
R.styleable.Keyboard_rowHeight, mParams.mBaseHeight, mParams.mDefaultRowHeight);
|
||||
|
||||
final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
|
||||
int keyboardLayout = 0;
|
||||
try {
|
||||
XmlParseUtils.checkAttributeExists(
|
||||
keyboardAttr, R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
|
||||
TAG_INCLUDE, parser);
|
||||
keyboardLayout = keyboardAttr.getResourceId(
|
||||
R.styleable.Keyboard_Include_keyboardLayout, 0);
|
||||
if (row != null) {
|
||||
// Override current x coordinate.
|
||||
row.updateXPos(keyAttr);
|
||||
// Push current Row attributes and update with new attributes.
|
||||
row.pushRowAttributes(keyAttr);
|
||||
}
|
||||
} finally {
|
||||
keyboardAttr.recycle();
|
||||
keyAttr.recycle();
|
||||
includeAttr.recycle();
|
||||
}
|
||||
|
||||
XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
|
||||
if (DEBUG) {
|
||||
startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
|
||||
mResources.getResourceEntryName(keyboardLayout));
|
||||
}
|
||||
final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
|
||||
try {
|
||||
parseMerge(parserForInclude, row, skip);
|
||||
} finally {
|
||||
if (row != null) {
|
||||
// Restore Row attributes.
|
||||
row.popRowAttributes();
|
||||
}
|
||||
parserForInclude.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (DEBUG) startTag("<%s>", TAG_MERGE);
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_MERGE.equals(tag)) {
|
||||
if (row == null) {
|
||||
parseKeyboardContent(parser, skip);
|
||||
} else {
|
||||
parseRowContent(parser, row, skip);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new XmlParseUtils.ParseException(
|
||||
"Included keyboard layout must have <merge> root element", parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSwitchKeyboard(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
parseSwitchInternal(parser, true, null, skip);
|
||||
}
|
||||
|
||||
private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
parseSwitchInternal(parser, false, null, skip);
|
||||
}
|
||||
|
||||
private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row,
|
||||
final boolean skip) throws XmlPullParserException, IOException {
|
||||
parseSwitchInternal(parser, false, row, skip);
|
||||
}
|
||||
|
||||
private void parseSwitchInternal(final XmlPullParser parser, final boolean parseKeyboard,
|
||||
final KeyboardRow row, final boolean skip) throws XmlPullParserException, IOException {
|
||||
if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
|
||||
boolean selected = false;
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
final int event = parser.next();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_CASE.equals(tag)) {
|
||||
selected |= parseCase(parser, parseKeyboard, row, selected || skip);
|
||||
} else if (TAG_DEFAULT.equals(tag)) {
|
||||
selected |= parseDefault(parser, parseKeyboard, row, selected || skip);
|
||||
} else {
|
||||
throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_SWITCH);
|
||||
}
|
||||
} else if (event == XmlPullParser.END_TAG) {
|
||||
final String tag = parser.getName();
|
||||
if (TAG_SWITCH.equals(tag)) {
|
||||
if (DEBUG) endTag("</%s>", TAG_SWITCH);
|
||||
return;
|
||||
}
|
||||
throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_SWITCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean parseCase(final XmlPullParser parser, final boolean parseKeyboard,
|
||||
final KeyboardRow row, final boolean skip) throws XmlPullParserException, IOException {
|
||||
final boolean selected = parseCaseCondition(parser);
|
||||
if (parseKeyboard) {
|
||||
// Processing Keyboard root.
|
||||
parseKeyboard(parser, !selected || skip);
|
||||
} else if (row == null) {
|
||||
// Processing Rows.
|
||||
parseKeyboardContent(parser, !selected || skip);
|
||||
} else {
|
||||
// Processing Keys.
|
||||
parseRowContent(parser, row, !selected || skip);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private boolean parseCaseCondition(final XmlPullParser parser) {
|
||||
final KeyboardId id = mParams.mId;
|
||||
if (id == null) {
|
||||
return true;
|
||||
}
|
||||
final AttributeSet attr = Xml.asAttributeSet(parser);
|
||||
final TypedArray caseAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Case);
|
||||
if (DEBUG) startTag("<%s>", TAG_CASE);
|
||||
try {
|
||||
final boolean keyboardLayoutSetMatched = matchString(caseAttr,
|
||||
R.styleable.Keyboard_Case_keyboardLayoutSet,
|
||||
id.mSubtype.getKeyboardLayoutSet());
|
||||
final boolean keyboardLayoutSetElementMatched = matchTypedValue(caseAttr,
|
||||
R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
|
||||
KeyboardId.elementIdToName(id.mElementId));
|
||||
final boolean keyboardThemeMatched = matchTypedValue(caseAttr,
|
||||
R.styleable.Keyboard_Case_keyboardTheme, id.mThemeId,
|
||||
KeyboardTheme.getKeyboardThemeName(id.mThemeId));
|
||||
final boolean modeMatched = matchTypedValue(caseAttr,
|
||||
R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
|
||||
final boolean navigateNextMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
|
||||
final boolean navigatePreviousMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
|
||||
final boolean passwordInputMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
|
||||
final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
|
||||
id.mLanguageSwitchKeyEnabled);
|
||||
final boolean isMultiLineMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
|
||||
final boolean imeActionMatched = matchInteger(caseAttr,
|
||||
R.styleable.Keyboard_Case_imeAction, id.imeAction());
|
||||
final boolean isIconDefinedMatched = isIconDefined(caseAttr,
|
||||
R.styleable.Keyboard_Case_isIconDefined, mParams.mIconsSet);
|
||||
final Locale locale = id.getLocale();
|
||||
final boolean localeCodeMatched = matchLocaleCodes(caseAttr, locale);
|
||||
final boolean languageCodeMatched = matchLanguageCodes(caseAttr, locale);
|
||||
final boolean countryCodeMatched = matchCountryCodes(caseAttr, locale);
|
||||
final boolean showMoreKeysMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_showExtraChars, id.mShowMoreKeys);
|
||||
final boolean showNumberRowMatched = matchBoolean(caseAttr,
|
||||
R.styleable.Keyboard_Case_showNumberRow, id.mShowNumberRow);
|
||||
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
|
||||
&& keyboardThemeMatched && modeMatched && navigateNextMatched
|
||||
&& navigatePreviousMatched && passwordInputMatched
|
||||
&& languageSwitchKeyEnabledMatched
|
||||
&& isMultiLineMatched && imeActionMatched && isIconDefinedMatched
|
||||
&& localeCodeMatched && languageCodeMatched && countryCodeMatched
|
||||
&& showMoreKeysMatched && showNumberRowMatched;
|
||||
|
||||
return selected;
|
||||
} finally {
|
||||
caseAttr.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean matchLocaleCodes(TypedArray caseAttr, final Locale locale) {
|
||||
return matchString(caseAttr, R.styleable.Keyboard_Case_localeCode, locale.toString());
|
||||
}
|
||||
|
||||
private static boolean matchLanguageCodes(TypedArray caseAttr, Locale locale) {
|
||||
return matchString(caseAttr, R.styleable.Keyboard_Case_languageCode, locale.getLanguage());
|
||||
}
|
||||
|
||||
private static boolean matchCountryCodes(TypedArray caseAttr, Locale locale) {
|
||||
return matchString(caseAttr, R.styleable.Keyboard_Case_countryCode, locale.getCountry());
|
||||
}
|
||||
|
||||
private static boolean matchInteger(final TypedArray a, final int index, final int value) {
|
||||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||
// the attribute.
|
||||
return !a.hasValue(index) || a.getInt(index, 0) == value;
|
||||
}
|
||||
|
||||
private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) {
|
||||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||
// the attribute.
|
||||
return !a.hasValue(index) || a.getBoolean(index, false) == value;
|
||||
}
|
||||
|
||||
private static boolean matchString(final TypedArray a, final int index, final String value) {
|
||||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||
// the attribute.
|
||||
return !a.hasValue(index)
|
||||
|| StringUtils.containsInArray(value, a.getString(index).split("\\|"));
|
||||
}
|
||||
|
||||
private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue,
|
||||
final String strValue) {
|
||||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||
// the attribute.
|
||||
final TypedValue v = a.peekValue(index);
|
||||
if (v == null) {
|
||||
return true;
|
||||
}
|
||||
if (ResourceUtils.isIntegerValue(v)) {
|
||||
return intValue == a.getInt(index, 0);
|
||||
}
|
||||
if (ResourceUtils.isStringValue(v)) {
|
||||
return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isIconDefined(final TypedArray a, final int index,
|
||||
final KeyboardIconsSet iconsSet) {
|
||||
if (!a.hasValue(index)) {
|
||||
return true;
|
||||
}
|
||||
final String iconName = a.getString(index);
|
||||
final int iconId = KeyboardIconsSet.getIconId(iconName);
|
||||
return iconsSet.getIconDrawable(iconId) != null;
|
||||
}
|
||||
|
||||
private boolean parseDefault(final XmlPullParser parser, final boolean parseKeyboard,
|
||||
final KeyboardRow row, final boolean skip) throws XmlPullParserException, IOException {
|
||||
if (DEBUG) startTag("<%s>", TAG_DEFAULT);
|
||||
if (parseKeyboard) {
|
||||
parseKeyboard(parser, skip);
|
||||
} else if (row == null) {
|
||||
parseKeyboardContent(parser, skip);
|
||||
} else {
|
||||
parseRowContent(parser, row, skip);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
|
||||
throws XmlPullParserException, IOException {
|
||||
final AttributeSet attr = Xml.asAttributeSet(parser);
|
||||
final TypedArray keyStyleAttr = mResources.obtainAttributes(
|
||||
attr, R.styleable.Keyboard_KeyStyle);
|
||||
final TypedArray keyAttrs = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
|
||||
try {
|
||||
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
|
||||
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
|
||||
+ "/> needs styleName attribute", parser);
|
||||
}
|
||||
if (DEBUG) {
|
||||
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
|
||||
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
|
||||
skip ? " skipped" : "");
|
||||
}
|
||||
if (!skip) {
|
||||
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
|
||||
}
|
||||
} finally {
|
||||
keyStyleAttr.recycle();
|
||||
keyAttrs.recycle();
|
||||
}
|
||||
XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
|
||||
}
|
||||
|
||||
private void startKeyboard() {
|
||||
|
||||
}
|
||||
|
||||
private void startRow(final KeyboardRow row) {
|
||||
mCurrentRow = row;
|
||||
mPreviousKeyInRow = null;
|
||||
}
|
||||
|
||||
private void endRow(final KeyboardRow row) {
|
||||
if (mCurrentRow == null) {
|
||||
throw new RuntimeException("orphan end row tag");
|
||||
}
|
||||
if (mPreviousKeyInRow != null && !mPreviousKeyInRow.isSpacer()) {
|
||||
setKeyHitboxRightEdge(mPreviousKeyInRow, mParams.mOccupiedWidth);
|
||||
mPreviousKeyInRow = null;
|
||||
}
|
||||
mCurrentY += row.getRowHeight();
|
||||
mCurrentRow = null;
|
||||
}
|
||||
|
||||
private void endKey(final Key key, final KeyboardRow row) {
|
||||
mParams.onAddKey(key);
|
||||
if (mPreviousKeyInRow != null && !mPreviousKeyInRow.isSpacer()) {
|
||||
// Make the last key span the gap so there isn't un-clickable space. The current key's
|
||||
// hitbox left edge is based on the previous key, so this will make the gap between
|
||||
// them split evenly.
|
||||
setKeyHitboxRightEdge(mPreviousKeyInRow, row.getKeyX() - row.getKeyLeftPadding());
|
||||
}
|
||||
mPreviousKeyInRow = key;
|
||||
}
|
||||
|
||||
private void setKeyHitboxRightEdge(final Key key, final float xPos) {
|
||||
final int keyRight = key.getX() + key.getWidth();
|
||||
final float padding = xPos - keyRight;
|
||||
key.setHitboxRightEdge(Math.round(padding) + keyRight);
|
||||
}
|
||||
|
||||
private void endKeyboard() {
|
||||
mParams.removeRedundantMoreKeys();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
public final class KeyboardCodesSet {
|
||||
public static final String PREFIX_CODE = "!code/";
|
||||
|
||||
private static final HashMap<String, Integer> sNameToIdMap = new HashMap<>();
|
||||
|
||||
private KeyboardCodesSet() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
public static int getCode(final String name) {
|
||||
Integer id = sNameToIdMap.get(name);
|
||||
if (id == null) throw new RuntimeException("Unknown key code: " + name);
|
||||
return DEFAULT[id];
|
||||
}
|
||||
|
||||
private static final String[] ID_TO_NAME = {
|
||||
"key_tab",
|
||||
"key_enter",
|
||||
"key_space",
|
||||
"key_shift",
|
||||
"key_capslock",
|
||||
"key_switch_alpha_symbol",
|
||||
"key_output_text",
|
||||
"key_delete",
|
||||
"key_settings",
|
||||
"key_action_next",
|
||||
"key_action_previous",
|
||||
"key_shift_enter",
|
||||
"key_language_switch",
|
||||
"key_left",
|
||||
"key_right",
|
||||
"key_unspecified",
|
||||
};
|
||||
|
||||
private static final int[] DEFAULT = {
|
||||
Constants.CODE_TAB,
|
||||
Constants.CODE_ENTER,
|
||||
Constants.CODE_SPACE,
|
||||
Constants.CODE_SHIFT,
|
||||
Constants.CODE_CAPSLOCK,
|
||||
Constants.CODE_SWITCH_ALPHA_SYMBOL,
|
||||
Constants.CODE_OUTPUT_TEXT,
|
||||
Constants.CODE_DELETE,
|
||||
Constants.CODE_SETTINGS,
|
||||
Constants.CODE_ACTION_NEXT,
|
||||
Constants.CODE_ACTION_PREVIOUS,
|
||||
Constants.CODE_SHIFT_ENTER,
|
||||
Constants.CODE_LANGUAGE_SWITCH,
|
||||
Constants.CODE_UNSPECIFIED,
|
||||
};
|
||||
|
||||
static {
|
||||
for (int i = 0; i < ID_TO_NAME.length; i++) {
|
||||
sNameToIdMap.put(ID_TO_NAME[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
public final class KeyboardIconsSet {
|
||||
private static final String TAG = KeyboardIconsSet.class.getSimpleName();
|
||||
|
||||
public static final String PREFIX_ICON = "!icon/";
|
||||
public static final int ICON_UNDEFINED = 0;
|
||||
private static final int ATTR_UNDEFINED = 0;
|
||||
|
||||
private static final String NAME_UNDEFINED = "undefined";
|
||||
public static final String NAME_SHIFT_KEY = "shift_key";
|
||||
public static final String NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted";
|
||||
public static final String NAME_DELETE_KEY = "delete_key";
|
||||
public static final String NAME_SETTINGS_KEY = "settings_key";
|
||||
public static final String NAME_SPACE_KEY = "space_key";
|
||||
public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
|
||||
public static final String NAME_ENTER_KEY = "enter_key";
|
||||
public static final String NAME_GO_KEY = "go_key";
|
||||
public static final String NAME_SEARCH_KEY = "search_key";
|
||||
public static final String NAME_SEND_KEY = "send_key";
|
||||
public static final String NAME_NEXT_KEY = "next_key";
|
||||
public static final String NAME_DONE_KEY = "done_key";
|
||||
public static final String NAME_PREVIOUS_KEY = "previous_key";
|
||||
public static final String NAME_TAB_KEY = "tab_key";
|
||||
public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key";
|
||||
public static final String NAME_ZWNJ_KEY = "zwnj_key";
|
||||
public static final String NAME_ZWJ_KEY = "zwj_key";
|
||||
|
||||
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
|
||||
|
||||
// Icon name to icon id map.
|
||||
private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<>();
|
||||
|
||||
private static final Object[] NAMES_AND_ATTR_IDS = {
|
||||
NAME_UNDEFINED, ATTR_UNDEFINED,
|
||||
NAME_SHIFT_KEY, R.styleable.Keyboard_iconShiftKey,
|
||||
NAME_DELETE_KEY, R.styleable.Keyboard_iconDeleteKey,
|
||||
NAME_SETTINGS_KEY, R.styleable.Keyboard_iconSettingsKey,
|
||||
NAME_SPACE_KEY, R.styleable.Keyboard_iconSpaceKey,
|
||||
NAME_ENTER_KEY, R.styleable.Keyboard_iconEnterKey,
|
||||
NAME_GO_KEY, R.styleable.Keyboard_iconGoKey,
|
||||
NAME_SEARCH_KEY, R.styleable.Keyboard_iconSearchKey,
|
||||
NAME_SEND_KEY, R.styleable.Keyboard_iconSendKey,
|
||||
NAME_NEXT_KEY, R.styleable.Keyboard_iconNextKey,
|
||||
NAME_DONE_KEY, R.styleable.Keyboard_iconDoneKey,
|
||||
NAME_PREVIOUS_KEY, R.styleable.Keyboard_iconPreviousKey,
|
||||
NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey,
|
||||
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
|
||||
NAME_SHIFT_KEY_SHIFTED, R.styleable.Keyboard_iconShiftKeyShifted,
|
||||
NAME_LANGUAGE_SWITCH_KEY, R.styleable.Keyboard_iconLanguageSwitchKey,
|
||||
NAME_ZWNJ_KEY, R.styleable.Keyboard_iconZwnjKey,
|
||||
NAME_ZWJ_KEY, R.styleable.Keyboard_iconZwjKey,
|
||||
};
|
||||
|
||||
private static int NUM_ICONS = NAMES_AND_ATTR_IDS.length / 2;
|
||||
private static final String[] ICON_NAMES = new String[NUM_ICONS];
|
||||
private final Drawable[] mIcons = new Drawable[NUM_ICONS];
|
||||
private final int[] mIconResourceIds = new int[NUM_ICONS];
|
||||
|
||||
static {
|
||||
int iconId = ICON_UNDEFINED;
|
||||
for (int i = 0; i < NAMES_AND_ATTR_IDS.length; i += 2) {
|
||||
final String name = (String)NAMES_AND_ATTR_IDS[i];
|
||||
final Integer attrId = (Integer)NAMES_AND_ATTR_IDS[i + 1];
|
||||
if (attrId != ATTR_UNDEFINED) {
|
||||
ATTR_ID_TO_ICON_ID.put(attrId, iconId);
|
||||
}
|
||||
sNameToIdsMap.put(name, iconId);
|
||||
ICON_NAMES[iconId] = name;
|
||||
iconId++;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcons(final TypedArray keyboardAttrs) {
|
||||
final int size = ATTR_ID_TO_ICON_ID.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
final int attrId = ATTR_ID_TO_ICON_ID.keyAt(index);
|
||||
try {
|
||||
final Drawable icon = keyboardAttrs.getDrawable(attrId);
|
||||
setDefaultBounds(icon);
|
||||
final Integer iconId = ATTR_ID_TO_ICON_ID.get(attrId);
|
||||
mIcons[iconId] = icon;
|
||||
mIconResourceIds[iconId] = keyboardAttrs.getResourceId(attrId, 0);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
Log.w(TAG, "Drawable resource for icon #"
|
||||
+ keyboardAttrs.getResources().getResourceEntryName(attrId)
|
||||
+ " not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidIconId(final int iconId) {
|
||||
return iconId >= 0 && iconId < ICON_NAMES.length;
|
||||
}
|
||||
|
||||
public static String getIconName(final int iconId) {
|
||||
return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">";
|
||||
}
|
||||
|
||||
public static int getIconId(final String name) {
|
||||
Integer iconId = sNameToIdsMap.get(name);
|
||||
if (iconId != null) {
|
||||
return iconId;
|
||||
}
|
||||
throw new RuntimeException("unknown icon name: " + name);
|
||||
}
|
||||
|
||||
public Drawable getIconDrawable(final int iconId) {
|
||||
if (isValidIconId(iconId)) {
|
||||
return mIcons[iconId];
|
||||
}
|
||||
throw new RuntimeException("unknown icon id: " + getIconName(iconId));
|
||||
}
|
||||
|
||||
private static void setDefaultBounds(final Drawable icon) {
|
||||
if (icon != null) {
|
||||
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.keyboard.KeyboardId;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
||||
public class KeyboardParams {
|
||||
public KeyboardId mId;
|
||||
|
||||
/** Total height and width of the keyboard, including the paddings and keys */
|
||||
public int mOccupiedHeight;
|
||||
public int mOccupiedWidth;
|
||||
|
||||
/** Base height and width of the keyboard used to calculate rows' or keys' heights and
|
||||
* widths
|
||||
*/
|
||||
public float mBaseHeight;
|
||||
public float mBaseWidth;
|
||||
|
||||
public float mTopPadding;
|
||||
public float mBottomPadding;
|
||||
public float mLeftPadding;
|
||||
public float mRightPadding;
|
||||
|
||||
public KeyVisualAttributes mKeyVisualAttributes;
|
||||
|
||||
public float mDefaultRowHeight;
|
||||
public float mDefaultKeyPaddedWidth;
|
||||
public float mHorizontalGap;
|
||||
public float mVerticalGap;
|
||||
|
||||
public int mMoreKeysTemplate;
|
||||
public int mMaxMoreKeysKeyboardColumn;
|
||||
|
||||
public int mGridWidth;
|
||||
public int mGridHeight;
|
||||
|
||||
// Keys are sorted from top-left to bottom-right order.
|
||||
public final SortedSet<Key> mSortedKeys = new TreeSet<>(ROW_COLUMN_COMPARATOR);
|
||||
public final ArrayList<Key> mShiftKeys = new ArrayList<>();
|
||||
public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<>();
|
||||
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
|
||||
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
|
||||
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
|
||||
|
||||
private final UniqueKeysCache mUniqueKeysCache;
|
||||
public boolean mAllowRedundantMoreKeys;
|
||||
|
||||
public int mMostCommonKeyHeight = 0;
|
||||
public int mMostCommonKeyWidth = 0;
|
||||
|
||||
// Comparator to sort {@link Key}s from top-left to bottom-right order.
|
||||
private static final Comparator<Key> ROW_COLUMN_COMPARATOR = new Comparator<Key>() {
|
||||
@Override
|
||||
public int compare(final Key lhs, final Key rhs) {
|
||||
if (lhs.getY() < rhs.getY()) return -1;
|
||||
if (lhs.getY() > rhs.getY()) return 1;
|
||||
if (lhs.getX() < rhs.getX()) return -1;
|
||||
if (lhs.getX() > rhs.getX()) return 1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
public KeyboardParams() {
|
||||
this(UniqueKeysCache.NO_CACHE);
|
||||
}
|
||||
|
||||
public KeyboardParams(final UniqueKeysCache keysCache) {
|
||||
mUniqueKeysCache = keysCache;
|
||||
}
|
||||
|
||||
public void onAddKey(final Key newKey) {
|
||||
final Key key = mUniqueKeysCache.getUniqueKey(newKey);
|
||||
final boolean isSpacer = key.isSpacer();
|
||||
if (isSpacer && key.getWidth() == 0) {
|
||||
// Ignore zero width {@link Spacer}.
|
||||
return;
|
||||
}
|
||||
mSortedKeys.add(key);
|
||||
if (isSpacer) {
|
||||
return;
|
||||
}
|
||||
updateHistogram(key);
|
||||
if (key.getCode() == Constants.CODE_SHIFT) {
|
||||
mShiftKeys.add(key);
|
||||
}
|
||||
if (key.altCodeWhileTyping()) {
|
||||
mAltCodeKeysWhileTyping.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRedundantMoreKeys() {
|
||||
if (mAllowRedundantMoreKeys) {
|
||||
return;
|
||||
}
|
||||
final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout =
|
||||
new MoreKeySpec.LettersOnBaseLayout();
|
||||
for (final Key key : mSortedKeys) {
|
||||
lettersOnBaseLayout.addLetter(key);
|
||||
}
|
||||
final ArrayList<Key> allKeys = new ArrayList<>(mSortedKeys);
|
||||
mSortedKeys.clear();
|
||||
for (final Key key : allKeys) {
|
||||
final Key filteredKey = Key.removeRedundantMoreKeys(key, lettersOnBaseLayout);
|
||||
mSortedKeys.add(mUniqueKeysCache.getUniqueKey(filteredKey));
|
||||
}
|
||||
}
|
||||
|
||||
private int mMaxHeightCount = 0;
|
||||
private int mMaxWidthCount = 0;
|
||||
private final SparseIntArray mHeightHistogram = new SparseIntArray();
|
||||
private final SparseIntArray mWidthHistogram = new SparseIntArray();
|
||||
|
||||
private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
|
||||
final int index = histogram.indexOfKey(key);
|
||||
final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
|
||||
histogram.put(key, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
private void updateHistogram(final Key key) {
|
||||
final int height = Math.round(key.getDefinedHeight());
|
||||
final int heightCount = updateHistogramCounter(mHeightHistogram, height);
|
||||
if (heightCount > mMaxHeightCount) {
|
||||
mMaxHeightCount = heightCount;
|
||||
mMostCommonKeyHeight = height;
|
||||
}
|
||||
|
||||
final int width = Math.round(key.getDefinedWidth());
|
||||
final int widthCount = updateHistogramCounter(mWidthHistogram, width);
|
||||
if (widthCount > mMaxWidthCount) {
|
||||
mMaxWidthCount = widthCount;
|
||||
mMostCommonKeyWidth = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.colorful.keyboard.theme.R;
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.latin.utils.ResourceUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
public final class KeyboardRow {
|
||||
private static final String TAG = KeyboardRow.class.getSimpleName();
|
||||
private static final float FLOAT_THRESHOLD = 0.0001f;
|
||||
|
||||
// keyWidth enum constants
|
||||
private static final int KEYWIDTH_NOT_ENUM = 0;
|
||||
private static final int KEYWIDTH_FILL_RIGHT = -1;
|
||||
|
||||
private final KeyboardParams mParams;
|
||||
|
||||
/** The y coordinate of the top edge of all keys in the row, excluding the top padding. */
|
||||
private final float mY;
|
||||
/** The height of this row and all keys in it, including the top and bottom padding. */
|
||||
private final float mRowHeight;
|
||||
/** The top padding of all of the keys in the row. */
|
||||
private final float mKeyTopPadding;
|
||||
/** The bottom padding of all of the keys in the row. */
|
||||
private final float mKeyBottomPadding;
|
||||
|
||||
/** A tracker for where the next key should start, excluding padding. */
|
||||
private float mNextKeyXPos;
|
||||
|
||||
/** The x coordinate of the left edge of the current key, excluding the left padding. */
|
||||
private float mCurrentX;
|
||||
/** The width of the current key excluding the left and right padding. */
|
||||
private float mCurrentKeyWidth;
|
||||
/** The left padding of the current key. */
|
||||
private float mCurrentKeyLeftPadding;
|
||||
/** The right padding of the current key. */
|
||||
private float mCurrentKeyRightPadding;
|
||||
|
||||
/** Flag indicating whether the previous key in the row was a spacer. */
|
||||
private boolean mLastKeyWasSpacer = false;
|
||||
/** The x coordinate of the right edge of the previous key, excluding the right padding. */
|
||||
private float mLastKeyRightEdge = 0;
|
||||
|
||||
private final ArrayDeque<RowAttributes> mRowAttributesStack = new ArrayDeque<>();
|
||||
|
||||
// TODO: Add keyActionFlags.
|
||||
private static class RowAttributes {
|
||||
/** Default padded width of a key in this row. */
|
||||
public final float mDefaultKeyPaddedWidth;
|
||||
/** Default keyLabelFlags in this row. */
|
||||
public final int mDefaultKeyLabelFlags;
|
||||
/** Default backgroundType for this row */
|
||||
public final int mDefaultBackgroundType;
|
||||
|
||||
/**
|
||||
* Parse and create key attributes. This constructor is used to parse Row tag.
|
||||
*
|
||||
* @param keyAttr an attributes array of Row tag.
|
||||
* @param defaultKeyPaddedWidth a default padded key width.
|
||||
* @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
|
||||
*/
|
||||
public RowAttributes(final TypedArray keyAttr, final float defaultKeyPaddedWidth,
|
||||
final float keyboardWidth) {
|
||||
mDefaultKeyPaddedWidth = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyWidth, keyboardWidth, defaultKeyPaddedWidth);
|
||||
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
|
||||
mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
|
||||
Key.BACKGROUND_TYPE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and update key attributes using default attributes. This constructor is used
|
||||
* to parse include tag.
|
||||
*
|
||||
* @param keyAttr an attributes array of include tag.
|
||||
* @param defaultRowAttr default Row attributes.
|
||||
* @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
|
||||
*/
|
||||
public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr,
|
||||
final float keyboardWidth) {
|
||||
mDefaultKeyPaddedWidth = ResourceUtils.getFraction(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyWidth, keyboardWidth,
|
||||
defaultRowAttr.mDefaultKeyPaddedWidth);
|
||||
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0)
|
||||
| defaultRowAttr.mDefaultKeyLabelFlags;
|
||||
mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
|
||||
defaultRowAttr.mDefaultBackgroundType);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyboardRow(final Resources res, final KeyboardParams params,
|
||||
final XmlPullParser parser, final float y) {
|
||||
mParams = params;
|
||||
final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard);
|
||||
if (y < FLOAT_THRESHOLD) {
|
||||
// The top row should use the keyboard's top padding instead of the vertical gap
|
||||
mKeyTopPadding = params.mTopPadding;
|
||||
} else {
|
||||
// All of the vertical gap will be used as bottom padding rather than split between the
|
||||
// top and bottom because it is probably more likely for users to click below a key
|
||||
mKeyTopPadding = 0;
|
||||
}
|
||||
final float baseRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_rowHeight, params.mBaseHeight, params.mDefaultRowHeight);
|
||||
float keyHeight = baseRowHeight - params.mVerticalGap;
|
||||
final float rowEndY = y + mKeyTopPadding + keyHeight + params.mVerticalGap;
|
||||
final float keyboardBottomEdge = params.mOccupiedHeight - params.mBottomPadding;
|
||||
if (rowEndY > keyboardBottomEdge - FLOAT_THRESHOLD) {
|
||||
// The bottom row's padding should go to the bottom of the keyboard (this might be
|
||||
// slightly more than the keyboard's bottom padding if the rows don't add up to 100%).
|
||||
// We'll consider it the bottom row as long as the row's normal bottom padding overlaps
|
||||
// with the keyboard's bottom padding any amount.
|
||||
final float keyEndY = y + mKeyTopPadding + keyHeight;
|
||||
final float keyOverflow = keyEndY - keyboardBottomEdge;
|
||||
if (keyOverflow > FLOAT_THRESHOLD) {
|
||||
if (Math.round(keyOverflow) > 0) {
|
||||
// Only bother logging an error when expected rounding wouldn't resolve this
|
||||
Log.e(TAG, "The row is too tall to fit in the keyboard (" + keyOverflow
|
||||
+ " px). The height was reduced to fit.");
|
||||
}
|
||||
keyHeight = Math.max(keyboardBottomEdge - y - mKeyTopPadding, 0);
|
||||
}
|
||||
mKeyBottomPadding = Math.max(params.mOccupiedHeight - keyEndY, 0);
|
||||
} else {
|
||||
mKeyBottomPadding = params.mVerticalGap;
|
||||
}
|
||||
mRowHeight = mKeyTopPadding + keyHeight + mKeyBottomPadding;
|
||||
keyboardAttr.recycle();
|
||||
final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard_Key);
|
||||
mRowAttributesStack.push(new RowAttributes(
|
||||
keyAttr, params.mDefaultKeyPaddedWidth, params.mBaseWidth));
|
||||
keyAttr.recycle();
|
||||
|
||||
mY = y + mKeyTopPadding;
|
||||
mLastKeyRightEdge = 0;
|
||||
mNextKeyXPos = params.mLeftPadding;
|
||||
}
|
||||
|
||||
public void pushRowAttributes(final TypedArray keyAttr) {
|
||||
final RowAttributes newAttributes = new RowAttributes(
|
||||
keyAttr, mRowAttributesStack.peek(), mParams.mBaseWidth);
|
||||
mRowAttributesStack.push(newAttributes);
|
||||
}
|
||||
|
||||
public void popRowAttributes() {
|
||||
mRowAttributesStack.pop();
|
||||
}
|
||||
|
||||
private float getDefaultKeyPaddedWidth() {
|
||||
return mRowAttributesStack.peek().mDefaultKeyPaddedWidth;
|
||||
}
|
||||
|
||||
public int getDefaultKeyLabelFlags() {
|
||||
return mRowAttributesStack.peek().mDefaultKeyLabelFlags;
|
||||
}
|
||||
|
||||
public int getDefaultBackgroundType() {
|
||||
return mRowAttributesStack.peek().mDefaultBackgroundType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the x position for the next key based on what is set in the keyXPos attribute.
|
||||
* @param keyAttr the Key XML attributes array.
|
||||
*/
|
||||
public void updateXPos(final TypedArray keyAttr) {
|
||||
if (keyAttr == null || !keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keyXPos is based on the base width, but we need to add in the keyboard padding to
|
||||
// determine the actual position in the keyboard.
|
||||
final float keyXPos = ResourceUtils.getFraction(keyAttr, R.styleable.Keyboard_Key_keyXPos,
|
||||
mParams.mBaseWidth, 0) + mParams.mLeftPadding;
|
||||
// keyXPos shouldn't be less than mLastKeyRightEdge or this key will overlap the adjacent
|
||||
// key on its left hand side.
|
||||
if (keyXPos + FLOAT_THRESHOLD < mLastKeyRightEdge) {
|
||||
Log.e(TAG, "The specified keyXPos (" + keyXPos
|
||||
+ ") is smaller than the next available x position (" + mLastKeyRightEdge
|
||||
+ "). The x position was increased to avoid overlapping keys.");
|
||||
mNextKeyXPos = mLastKeyRightEdge;
|
||||
} else {
|
||||
mNextKeyXPos = keyXPos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the next key's dimensions so they can be retrieved using {@link #getKeyX()},
|
||||
* {@link #getKeyWidth()}, etc.
|
||||
* @param keyAttr the Key XML attributes array.
|
||||
* @param isSpacer flag indicating if the key is a spacer.
|
||||
*/
|
||||
public void setCurrentKey(final TypedArray keyAttr, final boolean isSpacer) {
|
||||
// Split gap on both sides of key
|
||||
final float defaultGap = mParams.mHorizontalGap / 2;
|
||||
|
||||
updateXPos(keyAttr);
|
||||
final float keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
|
||||
float keyWidth;
|
||||
if (isSpacer) {
|
||||
final float leftGap = Math.min(mNextKeyXPos - mLastKeyRightEdge - defaultGap,
|
||||
defaultGap);
|
||||
// Spacers don't have horizontal gaps but should include that space in its width
|
||||
mCurrentX = mNextKeyXPos - leftGap;
|
||||
keyWidth = getKeyWidth(keyAttr) + leftGap;
|
||||
if (mCurrentX + keyWidth + FLOAT_THRESHOLD < keyboardRightEdge) {
|
||||
// Add what is normally the default right gap for non-edge spacers
|
||||
keyWidth += defaultGap;
|
||||
}
|
||||
mCurrentKeyLeftPadding = 0;
|
||||
mCurrentKeyRightPadding = 0;
|
||||
} else {
|
||||
mCurrentX = mNextKeyXPos;
|
||||
if (mLastKeyRightEdge < FLOAT_THRESHOLD || mLastKeyWasSpacer) {
|
||||
// The first key in the row and a key next to a spacer should have a left padding
|
||||
// that spans the available distance
|
||||
mCurrentKeyLeftPadding = mCurrentX - mLastKeyRightEdge;
|
||||
} else {
|
||||
// Split the gap between the adjacent keys
|
||||
mCurrentKeyLeftPadding = (mCurrentX - mLastKeyRightEdge) / 2;
|
||||
}
|
||||
keyWidth = getKeyWidth(keyAttr);
|
||||
// We can't know this before seeing the next key, so just use the default. The key can
|
||||
// be updated later.
|
||||
mCurrentKeyRightPadding = defaultGap;
|
||||
}
|
||||
final float keyOverflow = mCurrentX + keyWidth - keyboardRightEdge;
|
||||
if (keyOverflow > FLOAT_THRESHOLD) {
|
||||
if (Math.round(keyOverflow) > 0) {
|
||||
// Only bother logging an error when expected rounding wouldn't resolve this
|
||||
Log.e(TAG, "The " + (isSpacer ? "spacer" : "key")
|
||||
+ " is too wide to fit in the keyboard (" + keyOverflow
|
||||
+ " px). The width was reduced to fit.");
|
||||
}
|
||||
keyWidth = Math.max(keyboardRightEdge - mCurrentX, 0);
|
||||
}
|
||||
|
||||
mCurrentKeyWidth = keyWidth;
|
||||
|
||||
// Calculations for the current key are done. Prep for the next key.
|
||||
mLastKeyRightEdge = mCurrentX + keyWidth;
|
||||
mLastKeyWasSpacer = isSpacer;
|
||||
// Set the next key's default position. Spacers only add half because their width includes
|
||||
// what is normally the horizontal gap.
|
||||
mNextKeyXPos = mLastKeyRightEdge + (isSpacer ? defaultGap : mParams.mHorizontalGap);
|
||||
}
|
||||
|
||||
private float getKeyWidth(final TypedArray keyAttr) {
|
||||
if (keyAttr == null) {
|
||||
return getDefaultKeyPaddedWidth() - mParams.mHorizontalGap;
|
||||
}
|
||||
final int widthType = ResourceUtils.getEnumValue(keyAttr,
|
||||
R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
|
||||
switch (widthType) {
|
||||
case KEYWIDTH_FILL_RIGHT:
|
||||
// If keyWidth is fillRight, the actual key width will be determined to fill
|
||||
// out the area up to the right edge of the keyboard.
|
||||
final float keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
|
||||
return keyboardRightEdge - mCurrentX;
|
||||
default: // KEYWIDTH_NOT_ENUM
|
||||
return ResourceUtils.getFraction(keyAttr, R.styleable.Keyboard_Key_keyWidth,
|
||||
mParams.mBaseWidth, getDefaultKeyPaddedWidth()) - mParams.mHorizontalGap;
|
||||
}
|
||||
}
|
||||
|
||||
public float getRowHeight() {
|
||||
return mRowHeight;
|
||||
}
|
||||
|
||||
public float getKeyY() {
|
||||
return mY;
|
||||
}
|
||||
|
||||
public float getKeyX() {
|
||||
return mCurrentX;
|
||||
}
|
||||
|
||||
public float getKeyWidth() {
|
||||
return mCurrentKeyWidth;
|
||||
}
|
||||
|
||||
public float getKeyHeight() {
|
||||
return mRowHeight - mKeyTopPadding - mKeyBottomPadding;
|
||||
}
|
||||
|
||||
public float getKeyTopPadding() {
|
||||
return mKeyTopPadding;
|
||||
}
|
||||
|
||||
public float getKeyBottomPadding() {
|
||||
return mKeyBottomPadding;
|
||||
}
|
||||
|
||||
public float getKeyLeftPadding() {
|
||||
return mCurrentKeyLeftPadding;
|
||||
}
|
||||
|
||||
public float getKeyRightPadding() {
|
||||
return mCurrentKeyRightPadding;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,649 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.colorful.keyboard.theme.event.Event;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.utils.CapsModeUtils;
|
||||
import com.colorful.keyboard.theme.latin.utils.RecapitalizeStatus;
|
||||
|
||||
public final class KeyboardState {
|
||||
private static final String TAG = KeyboardState.class.getSimpleName();
|
||||
private static final boolean DEBUG_EVENT = false;
|
||||
private static final boolean DEBUG_INTERNAL_ACTION = false;
|
||||
|
||||
public interface SwitchActions {
|
||||
boolean DEBUG_ACTION = false;
|
||||
|
||||
void setAlphabetKeyboard();
|
||||
void setAlphabetManualShiftedKeyboard();
|
||||
void setAlphabetAutomaticShiftedKeyboard();
|
||||
void setAlphabetShiftLockedKeyboard();
|
||||
void setSymbolsKeyboard();
|
||||
void setSymbolsShiftedKeyboard();
|
||||
|
||||
/**
|
||||
* Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
|
||||
*/
|
||||
void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode);
|
||||
|
||||
boolean DEBUG_TIMER_ACTION = false;
|
||||
|
||||
void startDoubleTapShiftKeyTimer();
|
||||
boolean isInDoubleTapShiftKeyTimeout();
|
||||
void cancelDoubleTapShiftKeyTimer();
|
||||
}
|
||||
|
||||
private final SwitchActions mSwitchActions;
|
||||
|
||||
private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
|
||||
private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
|
||||
|
||||
// TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState},
|
||||
// {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and
|
||||
// {@link #mPrevSymbolsKeyboardWasShifted} into single state variable.
|
||||
private static final int SWITCH_STATE_ALPHA = 0;
|
||||
private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
|
||||
private static final int SWITCH_STATE_SYMBOL = 2;
|
||||
private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
|
||||
private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
|
||||
private int mSwitchState = SWITCH_STATE_ALPHA;
|
||||
|
||||
private boolean mIsAlphabetMode;
|
||||
private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
|
||||
private boolean mIsSymbolShifted;
|
||||
private boolean mPrevMainKeyboardWasShiftLocked;
|
||||
private boolean mPrevSymbolsKeyboardWasShifted;
|
||||
private int mRecapitalizeMode;
|
||||
|
||||
// For handling double tap.
|
||||
private boolean mIsInAlphabetUnshiftedFromShifted;
|
||||
private boolean mIsInDoubleTapShiftKey;
|
||||
|
||||
private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
|
||||
|
||||
static final class SavedKeyboardState {
|
||||
public boolean mIsValid;
|
||||
public boolean mIsAlphabetMode;
|
||||
public boolean mIsAlphabetShiftLocked;
|
||||
public int mShiftMode;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!mIsValid) {
|
||||
return "INVALID";
|
||||
}
|
||||
if (mIsAlphabetMode) {
|
||||
return mIsAlphabetShiftLocked ? "ALPHABET_SHIFT_LOCKED"
|
||||
: "ALPHABET_" + shiftModeToString(mShiftMode);
|
||||
}
|
||||
return "SYMBOLS_" + shiftModeToString(mShiftMode);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyboardState(final SwitchActions switchActions) {
|
||||
mSwitchActions = switchActions;
|
||||
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
||||
}
|
||||
|
||||
public void onLoadKeyboard(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onLoadKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
// Reset alphabet shift state.
|
||||
mAlphabetShiftState.setShiftLocked(false);
|
||||
mPrevMainKeyboardWasShiftLocked = false;
|
||||
mPrevSymbolsKeyboardWasShifted = false;
|
||||
mShiftKeyState.onRelease();
|
||||
mSymbolKeyState.onRelease();
|
||||
if (mSavedKeyboardState.mIsValid) {
|
||||
onRestoreKeyboardState(autoCapsFlags, recapitalizeMode);
|
||||
mSavedKeyboardState.mIsValid = false;
|
||||
} else {
|
||||
// Reset keyboard to alphabet mode.
|
||||
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Constants for {@link SavedKeyboardState#mShiftMode} and {@link #setShifted(int)}.
|
||||
private static final int UNSHIFT = 0;
|
||||
private static final int MANUAL_SHIFT = 1;
|
||||
private static final int AUTOMATIC_SHIFT = 2;
|
||||
private static final int SHIFT_LOCK_SHIFTED = 3;
|
||||
|
||||
public void onSaveKeyboardState() {
|
||||
final SavedKeyboardState state = mSavedKeyboardState;
|
||||
state.mIsAlphabetMode = mIsAlphabetMode;
|
||||
if (mIsAlphabetMode) {
|
||||
state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked();
|
||||
state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT
|
||||
: (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT);
|
||||
} else {
|
||||
state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked;
|
||||
state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT;
|
||||
}
|
||||
state.mIsValid = true;
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRestoreKeyboardState(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
final SavedKeyboardState state = mSavedKeyboardState;
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onRestoreKeyboardState: saved=" + state
|
||||
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
|
||||
if (state.mIsAlphabetMode) {
|
||||
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
|
||||
setShiftLocked(state.mIsAlphabetShiftLocked);
|
||||
if (!state.mIsAlphabetShiftLocked) {
|
||||
setShifted(state.mShiftMode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Symbol mode
|
||||
if (state.mShiftMode == MANUAL_SHIFT) {
|
||||
setSymbolsShiftedKeyboard();
|
||||
} else {
|
||||
setSymbolsKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
private void setShifted(final int shiftMode) {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
|
||||
}
|
||||
if (!mIsAlphabetMode) return;
|
||||
final int prevShiftMode;
|
||||
if (mAlphabetShiftState.isAutomaticShifted()) {
|
||||
prevShiftMode = AUTOMATIC_SHIFT;
|
||||
} else if (mAlphabetShiftState.isManualShifted()) {
|
||||
prevShiftMode = MANUAL_SHIFT;
|
||||
} else {
|
||||
prevShiftMode = UNSHIFT;
|
||||
}
|
||||
switch (shiftMode) {
|
||||
case AUTOMATIC_SHIFT:
|
||||
mAlphabetShiftState.setAutomaticShifted();
|
||||
if (shiftMode != prevShiftMode) {
|
||||
mSwitchActions.setAlphabetAutomaticShiftedKeyboard();
|
||||
}
|
||||
break;
|
||||
case MANUAL_SHIFT:
|
||||
mAlphabetShiftState.setShifted(true);
|
||||
if (shiftMode != prevShiftMode) {
|
||||
mSwitchActions.setAlphabetManualShiftedKeyboard();
|
||||
}
|
||||
break;
|
||||
case UNSHIFT:
|
||||
mAlphabetShiftState.setShifted(false);
|
||||
if (shiftMode != prevShiftMode) {
|
||||
mSwitchActions.setAlphabetKeyboard();
|
||||
}
|
||||
break;
|
||||
case SHIFT_LOCK_SHIFTED:
|
||||
mAlphabetShiftState.setShifted(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setShiftLocked(final boolean shiftLocked) {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
|
||||
}
|
||||
if (!mIsAlphabetMode) return;
|
||||
if (shiftLocked && (!mAlphabetShiftState.isShiftLocked()
|
||||
|| mAlphabetShiftState.isShiftLockShifted())) {
|
||||
mSwitchActions.setAlphabetShiftLockedKeyboard();
|
||||
}
|
||||
if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) {
|
||||
mSwitchActions.setAlphabetKeyboard();
|
||||
}
|
||||
mAlphabetShiftState.setShiftLocked(shiftLocked);
|
||||
}
|
||||
|
||||
private void toggleAlphabetAndSymbols(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "toggleAlphabetAndSymbols: "
|
||||
+ stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
if (mIsAlphabetMode) {
|
||||
mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
|
||||
if (mPrevSymbolsKeyboardWasShifted) {
|
||||
setSymbolsShiftedKeyboard();
|
||||
} else {
|
||||
setSymbolsKeyboard();
|
||||
}
|
||||
mPrevSymbolsKeyboardWasShifted = false;
|
||||
} else {
|
||||
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
|
||||
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
|
||||
if (mPrevMainKeyboardWasShiftLocked) {
|
||||
setShiftLocked(true);
|
||||
}
|
||||
mPrevMainKeyboardWasShiftLocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
|
||||
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
|
||||
private void resetKeyboardStateToAlphabet(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "resetKeyboardStateToAlphabet: "
|
||||
+ stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
if (mIsAlphabetMode) return;
|
||||
|
||||
mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
|
||||
setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
|
||||
if (mPrevMainKeyboardWasShiftLocked) {
|
||||
setShiftLocked(true);
|
||||
}
|
||||
mPrevMainKeyboardWasShiftLocked = false;
|
||||
}
|
||||
|
||||
private void toggleShiftInSymbols() {
|
||||
if (mIsSymbolShifted) {
|
||||
setSymbolsKeyboard();
|
||||
} else {
|
||||
setSymbolsShiftedKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
private void setAlphabetKeyboard(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "setAlphabetKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
|
||||
mSwitchActions.setAlphabetKeyboard();
|
||||
mIsAlphabetMode = true;
|
||||
mIsSymbolShifted = false;
|
||||
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
||||
mSwitchState = SWITCH_STATE_ALPHA;
|
||||
mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
|
||||
private void setSymbolsKeyboard() {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "setSymbolsKeyboard");
|
||||
}
|
||||
mSwitchActions.setSymbolsKeyboard();
|
||||
mIsAlphabetMode = false;
|
||||
mIsSymbolShifted = false;
|
||||
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
||||
// Reset alphabet shift state.
|
||||
mAlphabetShiftState.setShiftLocked(false);
|
||||
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
|
||||
}
|
||||
|
||||
private void setSymbolsShiftedKeyboard() {
|
||||
if (DEBUG_INTERNAL_ACTION) {
|
||||
Log.d(TAG, "setSymbolsShiftedKeyboard");
|
||||
}
|
||||
mSwitchActions.setSymbolsShiftedKeyboard();
|
||||
mIsAlphabetMode = false;
|
||||
mIsSymbolShifted = true;
|
||||
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
||||
// Reset alphabet shift state.
|
||||
mAlphabetShiftState.setShiftLocked(false);
|
||||
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
|
||||
}
|
||||
|
||||
public void onPressKey(final int code, final boolean isSinglePointer, final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
|
||||
+ " single=" + isSinglePointer
|
||||
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
if (code != Constants.CODE_SHIFT) {
|
||||
// Because the double tap shift key timer is to detect two consecutive shift key press,
|
||||
// it should be canceled when a non-shift key is pressed.
|
||||
mSwitchActions.cancelDoubleTapShiftKeyTimer();
|
||||
}
|
||||
if (code == Constants.CODE_SHIFT) {
|
||||
onPressShift();
|
||||
} else if (code == Constants.CODE_CAPSLOCK) {
|
||||
// Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
|
||||
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
|
||||
onPressSymbol(autoCapsFlags, recapitalizeMode);
|
||||
} else {
|
||||
mShiftKeyState.onOtherKeyPressed();
|
||||
mSymbolKeyState.onOtherKeyPressed();
|
||||
// It is required to reset the auto caps state when all of the following conditions
|
||||
// are met:
|
||||
// 1) two or more fingers are in action
|
||||
// 2) in alphabet layout
|
||||
// 3) not in all characters caps mode
|
||||
// As for #3, please note that it's required to check even when the auto caps mode is
|
||||
// off because, for example, we may be in the #1 state within the manual temporary
|
||||
// shifted mode.
|
||||
if (!isSinglePointer && mIsAlphabetMode
|
||||
&& autoCapsFlags != TextUtils.CAP_MODE_CHARACTERS) {
|
||||
final boolean needsToResetAutoCaps =
|
||||
(mAlphabetShiftState.isAutomaticShifted() && !mShiftKeyState.isChording())
|
||||
|| (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing());
|
||||
if (needsToResetAutoCaps) {
|
||||
mSwitchActions.setAlphabetKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onReleaseKey(final int code, final boolean withSliding, final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
|
||||
+ " sliding=" + withSliding
|
||||
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
if (code == Constants.CODE_SHIFT) {
|
||||
onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode);
|
||||
} else if (code == Constants.CODE_CAPSLOCK) {
|
||||
setShiftLocked(!mAlphabetShiftState.isShiftLocked());
|
||||
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
|
||||
onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPressSymbol(final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
|
||||
mSymbolKeyState.onPress();
|
||||
mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
|
||||
}
|
||||
|
||||
private void onReleaseSymbol(final boolean withSliding, final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
if (mSymbolKeyState.isChording()) {
|
||||
// Switch back to the previous keyboard mode if the user chords the mode change key and
|
||||
// another key, then releases the mode change key.
|
||||
toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
|
||||
} else if (!withSliding) {
|
||||
// If the mode change key is being released without sliding, we should forget the
|
||||
// previous symbols keyboard shift state and simply switch back to symbols layout
|
||||
// (never symbols shifted) next time the mode gets changed to symbols layout.
|
||||
mPrevSymbolsKeyboardWasShifted = false;
|
||||
}
|
||||
mSymbolKeyState.onRelease();
|
||||
}
|
||||
|
||||
public void onUpdateShiftState(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onUpdateShiftState: " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
mRecapitalizeMode = recapitalizeMode;
|
||||
updateAlphabetShiftState(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
|
||||
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
|
||||
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
|
||||
public void onResetKeyboardStateToAlphabet(final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onResetKeyboardStateToAlphabet: "
|
||||
+ stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
resetKeyboardStateToAlphabet(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
|
||||
private void updateShiftStateForRecapitalize(final int recapitalizeMode) {
|
||||
switch (recapitalizeMode) {
|
||||
case RecapitalizeStatus.CAPS_MODE_ALL_UPPER:
|
||||
setShifted(SHIFT_LOCK_SHIFTED);
|
||||
break;
|
||||
case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER:
|
||||
setShifted(AUTOMATIC_SHIFT);
|
||||
break;
|
||||
case RecapitalizeStatus.CAPS_MODE_ALL_LOWER:
|
||||
case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE:
|
||||
default:
|
||||
setShifted(UNSHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAlphabetShiftState(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (!mIsAlphabetMode) return;
|
||||
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) {
|
||||
// We are recapitalizing. Match the keyboard to the current recapitalize state.
|
||||
updateShiftStateForRecapitalize(recapitalizeMode);
|
||||
return;
|
||||
}
|
||||
if (!mShiftKeyState.isReleasing()) {
|
||||
// Ignore update shift state event while the shift key is being pressed (including
|
||||
// chording).
|
||||
return;
|
||||
}
|
||||
if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) {
|
||||
if (mShiftKeyState.isReleasing() && autoCapsFlags != Constants.TextUtils.CAP_MODE_OFF) {
|
||||
// Only when shift key is releasing, automatic temporary upper case will be set.
|
||||
setShifted(AUTOMATIC_SHIFT);
|
||||
} else {
|
||||
setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPressShift() {
|
||||
// If we are recapitalizing, we don't do any of the normal processing, including
|
||||
// importantly the double tap timer.
|
||||
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
|
||||
return;
|
||||
}
|
||||
if (mIsAlphabetMode) {
|
||||
mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
|
||||
if (!mIsInDoubleTapShiftKey) {
|
||||
// This is first tap.
|
||||
mSwitchActions.startDoubleTapShiftKeyTimer();
|
||||
}
|
||||
if (mIsInDoubleTapShiftKey) {
|
||||
if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
|
||||
// Shift key has been double tapped while in manual shifted or automatic
|
||||
// shifted state.
|
||||
setShiftLocked(true);
|
||||
} else {
|
||||
// Shift key has been double tapped while in normal state. This is the second
|
||||
// tap to disable shift locked state, so just ignore this.
|
||||
}
|
||||
} else {
|
||||
if (mAlphabetShiftState.isShiftLocked()) {
|
||||
// Shift key is pressed while shift locked state, we will treat this state as
|
||||
// shift lock shifted state and mark as if shift key pressed while normal
|
||||
// state.
|
||||
setShifted(SHIFT_LOCK_SHIFTED);
|
||||
mShiftKeyState.onPress();
|
||||
} else if (mAlphabetShiftState.isAutomaticShifted()) {
|
||||
// Shift key pressed while automatic shifted isn't considered a manual shift
|
||||
// since it doesn't change the keyboard into a shifted state.
|
||||
mShiftKeyState.onPress();
|
||||
} else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
|
||||
// In manual shifted state, we just record shift key has been pressing while
|
||||
// shifted state.
|
||||
mShiftKeyState.onPressOnShifted();
|
||||
} else {
|
||||
// In base layout, chording or manual shifted mode is started.
|
||||
setShifted(MANUAL_SHIFT);
|
||||
mShiftKeyState.onPress();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In symbol mode, just toggle symbol and symbol more keyboard.
|
||||
toggleShiftInSymbols();
|
||||
mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
|
||||
mShiftKeyState.onPress();
|
||||
}
|
||||
}
|
||||
|
||||
private void onReleaseShift(final boolean withSliding, final int autoCapsFlags,
|
||||
final int recapitalizeMode) {
|
||||
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
|
||||
// We are recapitalizing. We should match the keyboard state to the recapitalize
|
||||
// state in priority.
|
||||
updateShiftStateForRecapitalize(mRecapitalizeMode);
|
||||
} else if (mIsAlphabetMode) {
|
||||
final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
|
||||
mIsInAlphabetUnshiftedFromShifted = false;
|
||||
if (mIsInDoubleTapShiftKey) {
|
||||
// Double tap shift key has been handled in {@link #onPressShift}, so that just
|
||||
// ignore this release shift key here.
|
||||
mIsInDoubleTapShiftKey = false;
|
||||
} else if (mShiftKeyState.isChording()) {
|
||||
if (mAlphabetShiftState.isShiftLockShifted()) {
|
||||
// After chording input while shift locked state.
|
||||
setShiftLocked(true);
|
||||
} else {
|
||||
// After chording input while normal state.
|
||||
setShifted(UNSHIFT);
|
||||
}
|
||||
// After chording input, automatic shift state may have been changed depending on
|
||||
// what characters were input.
|
||||
mShiftKeyState.onRelease();
|
||||
mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode);
|
||||
return;
|
||||
} else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted()
|
||||
&& (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted())
|
||||
&& !withSliding) {
|
||||
// Shift has been long pressed, ignore this release.
|
||||
} else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) {
|
||||
// Shift has been pressed without chording while shift locked state.
|
||||
setShiftLocked(false);
|
||||
} else if (mAlphabetShiftState.isShiftedOrShiftLocked()
|
||||
&& mShiftKeyState.isPressingOnShifted() && !withSliding) {
|
||||
// Shift has been pressed without chording while shifted state.
|
||||
setShifted(UNSHIFT);
|
||||
mIsInAlphabetUnshiftedFromShifted = true;
|
||||
} else if (mAlphabetShiftState.isAutomaticShifted() && mShiftKeyState.isPressing()
|
||||
&& !withSliding) {
|
||||
// Shift has been pressed without chording while automatic shifted
|
||||
setShifted(UNSHIFT);
|
||||
mIsInAlphabetUnshiftedFromShifted = true;
|
||||
}
|
||||
} else {
|
||||
// In symbol mode, switch back to the previous keyboard mode if the user chords the
|
||||
// shift key and another key, then releases the shift key.
|
||||
if (mShiftKeyState.isChording()) {
|
||||
toggleShiftInSymbols();
|
||||
}
|
||||
}
|
||||
mShiftKeyState.onRelease();
|
||||
}
|
||||
|
||||
public void onFinishSlidingInput(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onFinishSlidingInput: " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
// Switch back to the previous keyboard mode if the user cancels sliding input.
|
||||
switch (mSwitchState) {
|
||||
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
|
||||
toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
|
||||
break;
|
||||
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
|
||||
toggleShiftInSymbols();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSpaceOrEnter(final int c) {
|
||||
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
|
||||
}
|
||||
|
||||
public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) {
|
||||
final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint;
|
||||
if (DEBUG_EVENT) {
|
||||
Log.d(TAG, "onEvent: code=" + Constants.printableCode(code)
|
||||
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||
}
|
||||
|
||||
switch (mSwitchState) {
|
||||
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
|
||||
if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
|
||||
// Detected only the mode change key has been pressed, and then released.
|
||||
if (mIsAlphabetMode) {
|
||||
mSwitchState = SWITCH_STATE_ALPHA;
|
||||
} else {
|
||||
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
|
||||
if (code == Constants.CODE_SHIFT) {
|
||||
// Detected only the shift key has been pressed on symbol layout, and then
|
||||
// released.
|
||||
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
|
||||
}
|
||||
break;
|
||||
case SWITCH_STATE_SYMBOL_BEGIN:
|
||||
if (!isSpaceOrEnter(code) && (Constants.isLetterCode(code)
|
||||
|| code == Constants.CODE_OUTPUT_TEXT)) {
|
||||
mSwitchState = SWITCH_STATE_SYMBOL;
|
||||
}
|
||||
break;
|
||||
case SWITCH_STATE_SYMBOL:
|
||||
// Switch back to alpha keyboard mode if user types one or more non-space/enter
|
||||
// characters followed by a space/enter.
|
||||
if (isSpaceOrEnter(code)) {
|
||||
toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode);
|
||||
mPrevSymbolsKeyboardWasShifted = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If the code is a letter, update keyboard shift state.
|
||||
if (Constants.isLetterCode(code)) {
|
||||
updateAlphabetShiftState(autoCapsFlags, recapitalizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
static String shiftModeToString(final int shiftMode) {
|
||||
switch (shiftMode) {
|
||||
case UNSHIFT: return "UNSHIFT";
|
||||
case MANUAL_SHIFT: return "MANUAL";
|
||||
case AUTOMATIC_SHIFT: return "AUTOMATIC";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String switchStateToString(final int switchState) {
|
||||
switch (switchState) {
|
||||
case SWITCH_STATE_ALPHA: return "ALPHA";
|
||||
case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
|
||||
case SWITCH_STATE_SYMBOL: return "SYMBOL";
|
||||
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL";
|
||||
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString()
|
||||
: (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS"))
|
||||
+ " shift=" + mShiftKeyState
|
||||
+ " symbol=" + mSymbolKeyState
|
||||
+ " switch=" + switchStateToString(mSwitchState) + "]";
|
||||
}
|
||||
|
||||
private String stateToString(final int autoCapsFlags, final int recapitalizeMode) {
|
||||
return this + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags)
|
||||
+ " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
// TODO: Make this an immutable class.
|
||||
public final class KeyboardTextsSet {
|
||||
public static final String PREFIX_TEXT = "!text/";
|
||||
private static final String PREFIX_RESOURCE = "!string/";
|
||||
|
||||
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
|
||||
private static final int MAX_REFERENCE_INDIRECTION = 10;
|
||||
|
||||
private Resources mResources;
|
||||
private String mResourcePackageName;
|
||||
private String[] mTextsTable;
|
||||
|
||||
public void setLocale(final Locale locale, final Context context) {
|
||||
final Resources res = context.getResources();
|
||||
// Null means the current system locale.
|
||||
final String resourcePackageName = res.getResourcePackageName(
|
||||
context.getApplicationInfo().labelRes);
|
||||
setLocale(locale, res, resourcePackageName);
|
||||
}
|
||||
|
||||
public void setLocale(final Locale locale, final Resources res,
|
||||
final String resourcePackageName) {
|
||||
mResources = res;
|
||||
// Null means the current system locale.
|
||||
mResourcePackageName = resourcePackageName;
|
||||
mTextsTable = KeyboardTextsTable.getTextsTable(locale);
|
||||
}
|
||||
|
||||
public String getText(final String name) {
|
||||
return KeyboardTextsTable.getText(name, mTextsTable);
|
||||
}
|
||||
|
||||
private static int searchTextNameEnd(final String text, final int start) {
|
||||
final int size = text.length();
|
||||
for (int pos = start; pos < size; pos++) {
|
||||
final char c = text.charAt(pos);
|
||||
// Label name should be consisted of [a-zA-Z_0-9].
|
||||
if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
|
||||
continue;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// TODO: Resolve text reference when creating {@link KeyboardTextsTable} class.
|
||||
public String resolveTextReference(final String rawText) {
|
||||
if (TextUtils.isEmpty(rawText)) {
|
||||
return null;
|
||||
}
|
||||
int level = 0;
|
||||
String text = rawText;
|
||||
StringBuilder sb;
|
||||
do {
|
||||
level++;
|
||||
if (level >= MAX_REFERENCE_INDIRECTION) {
|
||||
throw new RuntimeException("Too many " + PREFIX_TEXT + " or " + PREFIX_RESOURCE +
|
||||
" reference indirection: " + text);
|
||||
}
|
||||
|
||||
final int prefixLength = PREFIX_TEXT.length();
|
||||
final int size = text.length();
|
||||
if (size < prefixLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
sb = null;
|
||||
for (int pos = 0; pos < size; pos++) {
|
||||
final char c = text.charAt(pos);
|
||||
if (text.startsWith(PREFIX_TEXT, pos)) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(text.substring(0, pos));
|
||||
}
|
||||
pos = expandReference(text, pos, PREFIX_TEXT, sb);
|
||||
} else if (text.startsWith(PREFIX_RESOURCE, pos)) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(text.substring(0, pos));
|
||||
}
|
||||
pos = expandReference(text, pos, PREFIX_RESOURCE, sb);
|
||||
} else if (c == BACKSLASH) {
|
||||
if (sb != null) {
|
||||
// Append both escape character and escaped character.
|
||||
sb.append(text.substring(pos, Math.min(pos + 2, size)));
|
||||
}
|
||||
pos++;
|
||||
} else if (sb != null) {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb != null) {
|
||||
text = sb.toString();
|
||||
}
|
||||
} while (sb != null);
|
||||
return TextUtils.isEmpty(text) ? null : text;
|
||||
}
|
||||
|
||||
private int expandReference(final String text, final int pos, final String prefix,
|
||||
final StringBuilder sb) {
|
||||
final int prefixLength = prefix.length();
|
||||
final int end = searchTextNameEnd(text, pos + prefixLength);
|
||||
final String name = text.substring(pos + prefixLength, end);
|
||||
if (prefix.equals(PREFIX_TEXT)) {
|
||||
sb.append(getText(name));
|
||||
} else { // PREFIX_RESOURCE
|
||||
final int resId = mResources.getIdentifier(name, "string", mResourcePackageName);
|
||||
sb.append(mResources.getString(resId));
|
||||
}
|
||||
return end - 1;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/* package */ class ModifierKeyState {
|
||||
protected static final String TAG = ModifierKeyState.class.getSimpleName();
|
||||
protected static final boolean DEBUG = false;
|
||||
|
||||
protected static final int RELEASING = 0;
|
||||
protected static final int PRESSING = 1;
|
||||
protected static final int CHORDING = 2;
|
||||
|
||||
protected final String mName;
|
||||
protected int mState = RELEASING;
|
||||
|
||||
public ModifierKeyState(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public void onPress() {
|
||||
mState = PRESSING;
|
||||
}
|
||||
|
||||
public void onRelease() {
|
||||
mState = RELEASING;
|
||||
}
|
||||
|
||||
public void onOtherKeyPressed() {
|
||||
final int oldState = mState;
|
||||
if (oldState == PRESSING)
|
||||
mState = CHORDING;
|
||||
if (DEBUG)
|
||||
Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this);
|
||||
}
|
||||
|
||||
public boolean isPressing() {
|
||||
return mState == PRESSING;
|
||||
}
|
||||
|
||||
public boolean isReleasing() {
|
||||
return mState == RELEASING;
|
||||
}
|
||||
|
||||
public boolean isChording() {
|
||||
return mState == CHORDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(mState);
|
||||
}
|
||||
|
||||
protected String toString(int state) {
|
||||
switch (state) {
|
||||
case RELEASING: return "RELEASING";
|
||||
case PRESSING: return "PRESSING";
|
||||
case CHORDING: return "CHORDING";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.colorful.keyboard.theme.keyboard.internal;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.colorful.keyboard.theme.keyboard.Key;
|
||||
import com.colorful.keyboard.theme.latin.common.CollectionUtils;
|
||||
import com.colorful.keyboard.theme.latin.common.Constants;
|
||||
import com.colorful.keyboard.theme.latin.common.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The more key specification object. The more keys are an array of {@link MoreKeySpec}.
|
||||
*
|
||||
* The more keys specification is comma separated "key specification" each of which represents one
|
||||
* "more key".
|
||||
* The key specification might have label or string resource reference in it. These references are
|
||||
* expanded before parsing comma.
|
||||
* Special character, comma ',' backslash '\' can be escaped by '\' character.
|
||||
* Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)}
|
||||
* as well.
|
||||
*/
|
||||
// TODO: Should extend the key specification object.
|
||||
public final class MoreKeySpec {
|
||||
public final int mCode;
|
||||
public final String mLabel;
|
||||
public final String mOutputText;
|
||||
public final int mIconId;
|
||||
|
||||
public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase,
|
||||
final Locale locale) {
|
||||
if (moreKeySpec.isEmpty()) {
|
||||
throw new KeySpecParser.KeySpecParserError("Empty more key spec");
|
||||
}
|
||||
final String label = KeySpecParser.getLabel(moreKeySpec);
|
||||
mLabel = needsToUpperCase ? StringUtils.toTitleCaseOfKeyLabel(label, locale) : label;
|
||||
final int codeInSpec = KeySpecParser.getCode(moreKeySpec);
|
||||
final int code = needsToUpperCase ? StringUtils.toTitleCaseOfKeyCode(codeInSpec, locale)
|
||||
: codeInSpec;
|
||||
if (code == Constants.CODE_UNSPECIFIED) {
|
||||
// Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
|
||||
// upper case representation ("SS").
|
||||
mCode = Constants.CODE_OUTPUT_TEXT;
|
||||
mOutputText = mLabel;
|
||||
} else {
|
||||
mCode = code;
|
||||
final String outputText = KeySpecParser.getOutputText(moreKeySpec);
|
||||
mOutputText = needsToUpperCase
|
||||
? StringUtils.toTitleCaseOfKeyLabel(outputText, locale) : outputText;
|
||||
}
|
||||
mIconId = KeySpecParser.getIconId(moreKeySpec);
|
||||
}
|
||||
|
||||
public Key buildKey(final float x, final float y, final float width, final float height,
|
||||
final float leftPadding, final float rightPadding, final float topPadding,
|
||||
final float bottomPadding, final int labelFlags) {
|
||||
return new Key(mLabel, mIconId, mCode, mOutputText, null /* hintLabel */, labelFlags,
|
||||
Key.BACKGROUND_TYPE_NORMAL, x, y, width, height, leftPadding, rightPadding,
|
||||
topPadding, bottomPadding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = 31 + mCode;
|
||||
hashCode = hashCode * 31 + mIconId;
|
||||
final String label = mLabel;
|
||||
hashCode = hashCode * 31 + (label == null ? 0 : label.hashCode());
|
||||
final String outputText = mOutputText;
|
||||
hashCode = hashCode * 31 + (outputText == null ? 0 : outputText.hashCode());
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof MoreKeySpec) {
|
||||
final MoreKeySpec other = (MoreKeySpec)o;
|
||||
return mCode == other.mCode
|
||||
&& mIconId == other.mIconId
|
||||
&& TextUtils.equals(mLabel, other.mLabel)
|
||||
&& TextUtils.equals(mOutputText, other.mOutputText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
|
||||
: KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
|
||||
final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText
|
||||
: Constants.printableCode(mCode));
|
||||
if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
|
||||
return output;
|
||||
}
|
||||
return label + "|" + output;
|
||||
}
|
||||
|
||||
public static class LettersOnBaseLayout {
|
||||
private final SparseIntArray mCodes = new SparseIntArray();
|
||||
private final HashSet<String> mTexts = new HashSet<>();
|
||||
|
||||
public void addLetter(final Key key) {
|
||||
final int code = key.getCode();
|
||||
if (Character.isAlphabetic(code)) {
|
||||
mCodes.put(code, 0);
|
||||
} else if (code == Constants.CODE_OUTPUT_TEXT) {
|
||||
mTexts.add(key.getOutputText());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(final MoreKeySpec moreKey) {
|
||||
final int code = moreKey.mCode;
|
||||
if (Character.isAlphabetic(code) && mCodes.indexOfKey(code) >= 0) {
|
||||
return true;
|
||||
} else if (code == Constants.CODE_OUTPUT_TEXT && mTexts.contains(moreKey.mOutputText)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static MoreKeySpec[] removeRedundantMoreKeys(final MoreKeySpec[] moreKeys,
|
||||
final LettersOnBaseLayout lettersOnBaseLayout) {
|
||||
if (moreKeys == null) {
|
||||
return null;
|
||||
}
|
||||
final ArrayList<MoreKeySpec> filteredMoreKeys = new ArrayList<>();
|
||||
for (final MoreKeySpec moreKey : moreKeys) {
|
||||
if (!lettersOnBaseLayout.contains(moreKey)) {
|
||||
filteredMoreKeys.add(moreKey);
|
||||
}
|
||||
}
|
||||
final int size = filteredMoreKeys.size();
|
||||
if (size == moreKeys.length) {
|
||||
return moreKeys;
|
||||
}
|
||||
if (size == 0) {
|
||||
return null;
|
||||
}
|
||||
return filteredMoreKeys.toArray(new MoreKeySpec[size]);
|
||||
}
|
||||
|
||||
// Constants for parsing.
|
||||
private static final char COMMA = Constants.CODE_COMMA;
|
||||
private static final char BACKSLASH = Constants.CODE_BACKSLASH;
|
||||
private static final String ADDITIONAL_MORE_KEY_MARKER =
|
||||
StringUtils.newSingleCodePointString(Constants.CODE_PERCENT);
|
||||
|
||||
/**
|
||||
* Split the text containing multiple key specifications separated by commas into an array of
|
||||
* key specifications.
|
||||
* A key specification can contain a character escaped by the backslash character, including a
|
||||
* comma character.
|
||||
* Note that an empty key specification will be eliminated from the result array.
|
||||
*
|
||||
* @param text the text containing multiple key specifications.
|
||||
* @return an array of key specification text. Null if the specified <code>text</code> is empty
|
||||
* or has no key specifications.
|
||||
*/
|
||||
public static String[] splitKeySpecs(final String text) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
final int size = text.length();
|
||||
// Optimization for one-letter key specification.
|
||||
if (size == 1) {
|
||||
return text.charAt(0) == COMMA ? null : new String[] { text };
|
||||
}
|
||||
|
||||
ArrayList<String> list = null;
|
||||
int start = 0;
|
||||
// The characters in question in this loop are COMMA and BACKSLASH. These characters never
|
||||
// match any high or low surrogate character. So it is OK to iterate through with char
|
||||
// index.
|
||||
for (int pos = 0; pos < size; pos++) {
|
||||
final char c = text.charAt(pos);
|
||||
if (c == COMMA) {
|
||||
// Skip empty entry.
|
||||
if (pos - start > 0) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(text.substring(start, pos));
|
||||
}
|
||||
// Skip comma
|
||||
start = pos + 1;
|
||||
} else if (c == BACKSLASH) {
|
||||
// Skip escape character and escaped character.
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
final String remain = (size - start > 0) ? text.substring(start) : null;
|
||||
if (list == null) {
|
||||
return remain != null ? new String[] { remain } : null;
|
||||
}
|
||||
if (remain != null) {
|
||||
list.add(remain);
|
||||
}
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
private static String[] filterOutEmptyString(final String[] array) {
|
||||
if (array == null) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
ArrayList<String> out = null;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
final String entry = array[i];
|
||||
if (TextUtils.isEmpty(entry)) {
|
||||
if (out == null) {
|
||||
out = CollectionUtils.arrayAsList(array, 0, i);
|
||||
}
|
||||
} else if (out != null) {
|
||||
out.add(entry);
|
||||
}
|
||||
}
|
||||
if (out == null) {
|
||||
return array;
|
||||
}
|
||||
return out.toArray(new String[out.size()]);
|
||||
}
|
||||
|
||||
public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
|
||||
final String[] additionalMoreKeySpecs) {
|
||||
final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
|
||||
final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
|
||||
final int moreKeysCount = moreKeys.length;
|
||||
final int additionalCount = additionalMoreKeys.length;
|
||||
ArrayList<String> out = null;
|
||||
int additionalIndex = 0;
|
||||
for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
|
||||
final String moreKeySpec = moreKeys[moreKeyIndex];
|
||||
if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
|
||||
if (additionalIndex < additionalCount) {
|
||||
// Replace '%' marker with additional more key specification.
|
||||
final String additionalMoreKey = additionalMoreKeys[additionalIndex];
|
||||
if (out != null) {
|
||||
out.add(additionalMoreKey);
|
||||
} else {
|
||||
moreKeys[moreKeyIndex] = additionalMoreKey;
|
||||
}
|
||||
additionalIndex++;
|
||||
} else {
|
||||
// Filter out excessive '%' marker.
|
||||
if (out == null) {
|
||||
out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (out != null) {
|
||||
out.add(moreKeySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (additionalCount > 0 && additionalIndex == 0) {
|
||||
// No '%' marker is found in more keys.
|
||||
// Insert all additional more keys to the head of more keys.
|
||||
out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
|
||||
for (int i = 0; i < moreKeysCount; i++) {
|
||||
out.add(moreKeys[i]);
|
||||
}
|
||||
} else if (additionalIndex < additionalCount) {
|
||||
// The number of '%' markers are less than additional more keys.
|
||||
// Append remained additional more keys to the tail of more keys.
|
||||
out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount);
|
||||
for (int i = additionalIndex; i < additionalCount; i++) {
|
||||
out.add(additionalMoreKeys[additionalIndex]);
|
||||
}
|
||||
}
|
||||
if (out == null && moreKeysCount > 0) {
|
||||
return moreKeys;
|
||||
} else if (out != null && out.size() > 0) {
|
||||
return out.toArray(new String[out.size()]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getIntValue(final String[] moreKeys, final String key,
|
||||
final int defaultValue) {
|
||||
if (moreKeys == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
final int keyLen = key.length();
|
||||
boolean foundValue = false;
|
||||
int value = defaultValue;
|
||||
for (int i = 0; i < moreKeys.length; i++) {
|
||||
final String moreKeySpec = moreKeys[i];
|
||||
if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
|
||||
continue;
|
||||
}
|
||||
moreKeys[i] = null;
|
||||
try {
|
||||
if (!foundValue) {
|
||||
value = Integer.parseInt(moreKeySpec.substring(keyLen));
|
||||
foundValue = true;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RuntimeException(
|
||||
"integer should follow after " + key + ": " + moreKeySpec);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static boolean getBooleanValue(final String[] moreKeys, final String key) {
|
||||
if (moreKeys == null) {
|
||||
return false;
|
||||
}
|
||||
boolean value = false;
|
||||
for (int i = 0; i < moreKeys.length; i++) {
|
||||
final String moreKeySpec = moreKeys[i];
|
||||
if (moreKeySpec == null || !moreKeySpec.equals(key)) {
|
||||
continue;
|
||||
}
|
||||
moreKeys[i] = null;
|
||||
value = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user