commit 305b1d4e337a5076d092a53569a88aade26ea339 Author: Yuriy Liskov Date: Sat Jan 20 23:25:33 2018 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47c91b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +notes.txt +/files +/misc +/TODO.txt +tmp/ +*_bak* +*_tmp +*.bak* +/.idea +*.apk +*.7z +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild +fabric.properties +*BAK.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea691bc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# LeanKey Keyboard sources + +Original app can be found on the [Google Play](https://play.google.com/store/apps/details?id=org.liskovsoft.androidtv.rukeyboard) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..353b1a6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,40 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } + // WARNING: don't use 'project.ext' because IDE 'Cannot infer argument type' + // https://stackoverflow.com/questions/20404476/how-to-define-common-android-properties-for-all-modules-using-gradle + // Gradle constants example: https://github.com/google/ExoPlayer + ext { + compileSdkVersion = 27 + buildToolsVersion = "27.0.3" + minSdkVersion = 14 + targetSdkVersion = 27 + appCompatVersion = 'com.android.support:appcompat-v7:27.+' + espressoCoreVersion = 'com.android.support.test.espresso:espresso-core:2.2.2' + junitVersion = 'junit:junit:4.12' + supportVersion = 'com.android.support:support-v4:27.+' + robolectricVersion = 'org.robolectric:robolectric:3.5.1' + crashlyticsVersion = 'com.crashlytics.sdk.android:crashlytics:2.8.0@aar' + } +} + +allprojects { + repositories { + jcenter() + // com.android.support libs + maven { url 'https://maven.google.com' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..d9ae40f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9a778d6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/leankeykeyboard/.gitignore b/leankeykeyboard/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/leankeykeyboard/.gitignore @@ -0,0 +1 @@ +/build diff --git a/leankeykeyboard/build.gradle b/leankeykeyboard/build.gradle new file mode 100644 index 0000000..601fcae --- /dev/null +++ b/leankeykeyboard/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion project.properties.compileSdkVersion + buildToolsVersion project.properties.buildToolsVersion + + defaultConfig { + applicationId "com.liskovsoft.leankeykeyboard" + minSdkVersion project.properties.minSdkVersion + targetSdkVersion project.properties.targetSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile(project.properties.espressoCoreVersion, { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile project.properties.appCompatVersion + testCompile project.properties.junitVersion +} diff --git a/leankeykeyboard/proguard-rules.pro b/leankeykeyboard/proguard-rules.pro new file mode 100644 index 0000000..57bc3d6 --- /dev/null +++ b/leankeykeyboard/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\portable\dev\android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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 diff --git a/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java b/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java new file mode 100644 index 0000000..fe154da --- /dev/null +++ b/leankeykeyboard/src/androidTest/java/com/liskovsoft/leankeykeyboard/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.liskovsoft.leankeykeyboard; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.liskovsoft.leankeykeyboard", appContext.getPackageName()); + } +} diff --git a/leankeykeyboard/src/main/AndroidManifest.xml b/leankeykeyboard/src/main/AndroidManifest.xml new file mode 100644 index 0000000..18b3fef --- /dev/null +++ b/leankeykeyboard/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java new file mode 100644 index 0000000..706e908 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOn.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public interface AddOn { + interface AddOnResourceMapping { + /*@AttrRes + int getLocalAttrIdFromRemote(@AttrRes int remoteAttributeResourceId);*/ + + int[] getRemoteStyleableArrayFromLocal(int[] localStyleableArray); + } + + int INVALID_RES_ID = 0; + + String getId(); + + String getName(); + + String getDescription(); + + String getPackageName(); + + @Nullable + Context getPackageContext(); + + int getSortIndex(); + + @NonNull + AddOnResourceMapping getResourceMapping(); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java new file mode 100644 index 0000000..1c9f08c --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnImpl.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.SparseArrayCompat; +import android.util.SparseIntArray; + +import com.anysoftkeyboard.utils.Logger; + +import java.lang.ref.WeakReference; +import java.util.Arrays; + +public abstract class AddOnImpl implements AddOn { + + private static final String TAG = "ASK_AddOnImpl"; + private final String mId; + private final String mName; + private final String mDescription; + private final String mPackageName; + private final Context mAskAppContext; + private WeakReference mPackageContext; + private final int mSortIndex; + private final AddOnResourceMappingImpl mAddOnResourceMapping; + + protected AddOnImpl(Context askContext, Context packageContext, String id, int nameResId, + String description, int sortIndex) { + mId = id; + mAskAppContext = askContext; + mName = packageContext.getString(nameResId); + mDescription = description; + mPackageName = packageContext.getPackageName(); + mPackageContext = new WeakReference<>(packageContext); + mSortIndex = sortIndex; + mAddOnResourceMapping = new AddOnResourceMappingImpl(this); + } + + public final String getId() { + return mId; + } + + public final String getDescription() { + return mDescription; + } + + public String getPackageName() { + return mPackageName; + } + + @Nullable + public final Context getPackageContext() { + Context c = mPackageContext.get(); + if (c == null) { + try { + c = mAskAppContext.createPackageContext(mPackageName, Context.CONTEXT_IGNORE_SECURITY); + mPackageContext = new WeakReference<>(c); + } catch (NameNotFoundException e) { + Logger.w(TAG, "Failed to find package %s!", mPackageName); + Logger.w(TAG, "Failed to find package! ", e); + } + } + return c; + } + + public final int getSortIndex() { + return mSortIndex; + } + + public String getName() { + return mName; + } + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof AddOn && + ((AddOn) o).getId().equals(getId()); + } + + @NonNull + @Override + public AddOnResourceMapping getResourceMapping() { + return mAddOnResourceMapping; + } + + private static class AddOnResourceMappingImpl implements AddOnResourceMapping { + private final WeakReference mAddOnWeakReference; + private final SparseIntArray mAttributesMapping = new SparseIntArray(); + private final SparseArrayCompat mStyleableArrayMapping = new SparseArrayCompat<>(); + + private AddOnResourceMappingImpl(@NonNull AddOnImpl addOn) { + mAddOnWeakReference = new WeakReference<>(addOn); + } + + @Override + public int[] getRemoteStyleableArrayFromLocal(int[] localStyleableArray) { + int localStyleableId = Arrays.hashCode(localStyleableArray); + int indexOfRemoteArray = mStyleableArrayMapping.indexOfKey(localStyleableId); + if (indexOfRemoteArray >= 0) return mStyleableArrayMapping.valueAt(indexOfRemoteArray); + AddOnImpl addOn = mAddOnWeakReference.get(); + if (addOn == null) return new int[0]; + Context askContext = addOn.mAskAppContext; + Context remoteContext = addOn.getPackageContext(); + if (remoteContext == null) return new int[0]; + int[] remoteAttrIds = Support.createBackwardCompatibleStyleable(localStyleableArray, askContext, remoteContext, mAttributesMapping); + mStyleableArrayMapping.put(localStyleableId, remoteAttrIds); + return remoteAttrIds; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java new file mode 100644 index 0000000..81c90ac --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/AddOnsFactory.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.util.AttributeSet; +import android.util.Xml; + +//import com.anysoftkeyboard.AnySoftKeyboard; +import com.anysoftkeyboard.utils.Logger; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +public abstract class AddOnsFactory { + + private static final class AddOnsComparator implements Comparator { + private final String mAskPackageName; + + private AddOnsComparator(Context askContext) { + mAskPackageName = askContext.getPackageName(); + } + + public int compare(AddOn k1, AddOn k2) { + String c1 = k1.getPackageName(); + String c2 = k2.getPackageName(); + + if (c1.equals(c2)) + return k1.getSortIndex() - k2.getSortIndex(); + else if (c1.equals(mAskPackageName))//I want to make sure ASK packages are first + return -1; + else if (c2.equals(mAskPackageName)) + return 1; + else + return c1.compareToIgnoreCase(c2); + } + } + + private final static ArrayList> mActiveInstances = new ArrayList<>(); + + private static final String sTAG = "AddOnsFactory"; + + //public static void onPackageChanged(final Intent eventIntent, final AnySoftKeyboard ask) { + // boolean cleared = false; + // boolean recreateView = false; + // for (AddOnsFactory factory : mActiveInstances) { + // try { + // if (factory.isEventRequiresCacheRefresh(eventIntent, ask.getApplicationContext())) { + // cleared = true; + // if (factory.isEventRequiresViewReset(eventIntent, ask.getApplicationContext())) recreateView = true; + // Logger.d(sTAG, factory.getClass().getName() + " will handle this package-changed event. Also recreate view? " + recreateView); + // factory.clearAddOnList(); + // } + // } catch (NameNotFoundException e) { + // e.printStackTrace(); + // } + // } + // if (cleared) ask.resetKeyboardView(recreateView); + //} + + public static AddOn locateAddOn(String id, Context askContext) { + for (AddOnsFactory factory : mActiveInstances) { + AddOn addOn = factory.getAddOnById(id, askContext); + if (addOn != null) { + Logger.d(sTAG, "Located addon with id " + addOn.getId() + " of type " + addOn.getClass().getName()); + return addOn; + } + } + + return null; + } + + protected final String TAG; + + /** + * This is the interface name that a broadcast receiver implementing an + * external addon should say that it supports -- that is, this is the + * action it uses for its intent filter. + */ + private final String RECEIVER_INTERFACE; + + /** + * Name under which an external addon broadcast receiver component + * publishes information about itself. + */ + private final String RECEIVER_META_DATA; + + private final ArrayList mAddOns = new ArrayList<>(); + private final HashMap mAddOnsById = new HashMap<>(); + + private final boolean mReadExternalPacksToo; + private final String ROOT_NODE_TAG; + private final String ADDON_NODE_TAG; + //private final int mBuildInAddOnsResId; + + private static final String XML_PREF_ID_ATTRIBUTE = "id"; + private static final String XML_NAME_RES_ID_ATTRIBUTE = "nameResId"; + private static final String XML_DESCRIPTION_ATTRIBUTE = "description"; + private static final String XML_SORT_INDEX_ATTRIBUTE = "index"; + + protected AddOnsFactory(String tag, String receiverInterface, String receiverMetaData, String rootNodeTag, String addonNodeTag, int buildInAddonResId, boolean readExternalPacksToo) { + TAG = tag; + RECEIVER_INTERFACE = receiverInterface; + RECEIVER_META_DATA = receiverMetaData; + ROOT_NODE_TAG = rootNodeTag; + ADDON_NODE_TAG = addonNodeTag; + //mBuildInAddOnsResId = buildInAddonResId; + mReadExternalPacksToo = readExternalPacksToo; + + mActiveInstances.add(this); + } + + protected boolean isEventRequiresCacheRefresh(Intent eventIntent, Context context) { + String action = eventIntent.getAction(); + String packageNameSchemePart = eventIntent.getData().getSchemeSpecificPart(); + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + //will reset only if the new package has my addons + boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); + if (hasAddon) { + Logger.d(TAG, "It seems that an addon exists in a newly installed package " + packageNameSchemePart + ". I need to reload stuff."); + return true; + } + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + //If I'm managing OR it contains an addon (could be new feature in the package), I want to reset. + boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); + if (isPackagedManaged) { + Logger.d(TAG, "It seems that an addon I use (in package " + packageNameSchemePart + ") has been changed. I need to reload stuff."); + return true; + } else { + boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); + if (hasAddon) { + Logger.d(TAG, "It seems that an addon exists in an updated package " + packageNameSchemePart + ". I need to reload stuff."); + return true; + } + } + } else //removed + { + //so only if I manage this package, I want to reset + boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); + if (isPackagedManaged) { + Logger.d(TAG, "It seems that an addon I use (in package " + packageNameSchemePart + ") has been removed. I need to reload stuff."); + return true; + } + } + return false; + } + + protected boolean isPackageManaged(String packageNameSchemePart) { + for (AddOn addOn : mAddOns) { + if (addOn.getPackageName().equals(packageNameSchemePart)) { + return true; + } + } + + return false; + } + + protected boolean isPackageContainAnAddon(Context context, String packageNameSchemePart) { + PackageInfo newPackage; + try { + newPackage = context.getPackageManager().getPackageInfo(packageNameSchemePart, PackageManager.GET_RECEIVERS + PackageManager + .GET_META_DATA); + } catch (NameNotFoundException e) { + e.printStackTrace(); + return false; + } + if (newPackage.receivers != null) { + ActivityInfo[] receivers = newPackage.receivers; + for (ActivityInfo aReceiver : receivers) { + //issue 904 + if (aReceiver == null || aReceiver.applicationInfo == null || !aReceiver.enabled || !aReceiver.applicationInfo.enabled) + continue; + final XmlPullParser xml = aReceiver.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); + if (xml != null) { + return true; + } + } + } + + return false; + } + + protected boolean isEventRequiresViewReset(Intent eventIntent, Context context) { + return false; + } + + protected synchronized void clearAddOnList() { + mAddOns.clear(); + mAddOnsById.clear(); + } + + public synchronized E getAddOnById(String id, Context askContext) { + if (mAddOnsById.size() == 0) { + loadAddOns(askContext); + } + return mAddOnsById.get(id); + } + + public synchronized final List getAllAddOns(Context askContext) { + Logger.d(TAG, "getAllAddOns has %d add on for %s", mAddOns.size(), getClass().getName()); + if (mAddOns.size() == 0) { + loadAddOns(askContext); + } + Logger.d(TAG, "getAllAddOns will return %d add on for %s", mAddOns.size(), getClass().getName()); + return Collections.unmodifiableList(mAddOns); + } + + protected void loadAddOns(final Context askContext) { + clearAddOnList(); + + //ArrayList local = getAddOnsFromResId(askContext, askContext, mBuildInAddOnsResId); + //for (E addon : local) { + // Logger.d(TAG, "Local add-on %s loaded", addon.getId()); + //} + //mAddOns.addAll(local); + ArrayList external = getExternalAddOns(askContext); + for (E addon : external) { + Logger.d(TAG, "External add-on %s loaded", addon.getId()); + } + mAddOns.addAll(external); + Logger.d(TAG, "Have %d add on for %s", mAddOns.size(), getClass().getName()); + + buildOtherDataBasedOnNewAddOns(mAddOns); + + //sorting the keyboards according to the requested + //sort order (from minimum to maximum) + Collections.sort(mAddOns, new AddOnsComparator(askContext)); + Logger.d(TAG, "Have %d add on for %s (after sort)", mAddOns.size(), getClass().getName()); + } + + protected void buildOtherDataBasedOnNewAddOns(ArrayList newAddOns) { + for (E addOn : newAddOns) + mAddOnsById.put(addOn.getId(), addOn); + } + + private ArrayList getExternalAddOns(Context askContext) { + final ArrayList externalAddOns = new ArrayList<>(); + + if (!mReadExternalPacksToo)//this will disable external packs (API careful stage) + return externalAddOns; + + final List broadcastReceivers = + askContext.getPackageManager().queryBroadcastReceivers(new Intent(RECEIVER_INTERFACE), PackageManager.GET_META_DATA); + + + for (final ResolveInfo receiver : broadcastReceivers) { + if (receiver.activityInfo == null) { + Logger.e(TAG, "BroadcastReceiver has null ActivityInfo. Receiver's label is " + + receiver.loadLabel(askContext.getPackageManager())); + Logger.e(TAG, "Is the external keyboard a service instead of BroadcastReceiver?"); + // Skip to next receiver + continue; + } + + if (!receiver.activityInfo.enabled || !receiver.activityInfo.applicationInfo.enabled) continue; + + try { + final Context externalPackageContext = askContext.createPackageContext(receiver.activityInfo.packageName, Context.CONTEXT_IGNORE_SECURITY); + final ArrayList packageAddOns = getAddOnsFromActivityInfo(askContext, externalPackageContext, receiver.activityInfo); + + externalAddOns.addAll(packageAddOns); + } catch (final NameNotFoundException e) { + Logger.e(TAG, "Did not find package: " + receiver.activityInfo.packageName); + } + + } + + return externalAddOns; + } + + private ArrayList getAddOnsFromResId(Context askContext, Context context, int addOnsResId) { + final XmlPullParser xml = context.getResources().getXml(addOnsResId); + if (xml == null) + return new ArrayList<>(); + return parseAddOnsFromXml(askContext, context, xml); + } + + private ArrayList getAddOnsFromActivityInfo(Context askContext, Context context, ActivityInfo ai) { + final XmlPullParser xml = ai.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); + if (xml == null)//issue 718: maybe a bad package? + return new ArrayList<>(); + return parseAddOnsFromXml(askContext, context, xml); + } + + private ArrayList parseAddOnsFromXml(Context askContext, Context context, XmlPullParser xml) { + final ArrayList addOns = new ArrayList<>(); + try { + int event; + boolean inRoot = false; + while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = xml.getName(); + if (event == XmlPullParser.START_TAG) { + if (ROOT_NODE_TAG.equals(tag)) { + inRoot = true; + } else if (inRoot && ADDON_NODE_TAG.equals(tag)) { + final AttributeSet attrs = Xml.asAttributeSet(xml); + E addOn = createAddOnFromXmlAttributes(askContext, attrs, context); + if (addOn != null) { + addOns.add(addOn); + } + } + } else if (event == XmlPullParser.END_TAG) { + if (ROOT_NODE_TAG.equals(tag)) { + inRoot = false; + break; + } + } + } + } catch (final IOException e) { + Logger.e(TAG, "IO error:" + e); + e.printStackTrace(); + } catch (final XmlPullParserException e) { + Logger.e(TAG, "Parse error:" + e); + e.printStackTrace(); + } + + return addOns; + } + + private E createAddOnFromXmlAttributes(Context askContext, AttributeSet attrs, Context context) { + final String prefId = attrs.getAttributeValue(null, XML_PREF_ID_ATTRIBUTE); + final int nameId = attrs.getAttributeResourceValue(null, XML_NAME_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + final int descriptionInt = attrs.getAttributeResourceValue(null, XML_DESCRIPTION_ATTRIBUTE, AddOn.INVALID_RES_ID); + //NOTE, to be compatible we need this. because the most of descriptions are + //without @string/adb + String description; + if (descriptionInt != AddOn.INVALID_RES_ID) { + description = context.getResources().getString(descriptionInt); + } else { + description = attrs.getAttributeValue(null, XML_DESCRIPTION_ATTRIBUTE); + } + + final int sortIndex = attrs.getAttributeUnsignedIntValue(null, XML_SORT_INDEX_ATTRIBUTE, 1); + + // asserting + if ((prefId == null) || (nameId == AddOn.INVALID_RES_ID)) { + Logger.e(TAG, "External add-on does not include all mandatory details! Will not create add-on."); + return null; + } else { + Logger.d(TAG, "External addon details: prefId:" + prefId + " nameId:" + nameId); + return createConcreteAddOn(askContext, context, prefId, nameId, description, sortIndex, attrs); + } + } + + protected abstract E createConcreteAddOn(Context askContext, Context context, String prefId, int nameId, String description, int sortIndex, AttributeSet attrs); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java new file mode 100644 index 0000000..0eeba70 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/addons/Support.java @@ -0,0 +1,64 @@ +package com.anysoftkeyboard.addons; + +import android.content.Context; +import android.content.res.Resources; +import android.support.annotation.NonNull; +import android.util.SparseIntArray; + +import com.anysoftkeyboard.utils.Logger; + +import java.util.ArrayList; +import java.util.List; + +class Support { + private static final String TAG = Support.class.getName(); + + /** + * Creates a mapping between the local styleable and the remote. + * @param localStyleableArray the local styleable to map against + * @param localContext local APK's Context + * @param remoteContext remote package's Context + * @param attributeIdMap a mapping between the remote-id -> local-id + * @return Always returns the remote version of localStyleableArray + */ + public static int[] createBackwardCompatibleStyleable(@NonNull int[] localStyleableArray, @NonNull Context localContext, @NonNull Context remoteContext, @NonNull SparseIntArray attributeIdMap) { + if (localContext == null) throw new NullPointerException("askContext can not be null"); + if (remoteContext == null) throw new NullPointerException("context can not be null"); + + final String remotePackageName = remoteContext.getPackageName(); + if (localContext.getPackageName().equals(remotePackageName)) { + Logger.d(TAG, "This is a local context ("+remotePackageName+"), optimization will be done."); + //optimization + for(int attrId : localStyleableArray) { + attributeIdMap.put(attrId, attrId); + } + return localStyleableArray; + } + final Resources localRes = localContext.getResources(); + final Resources remoteRes = remoteContext.getResources(); + List styleableIdList = new ArrayList<>(localStyleableArray.length); + for(int attrId : localStyleableArray) { + final boolean isAndroidAttribute = localRes.getResourcePackageName(attrId).equals("android"); + final int remoteAttrId; + + if (isAndroidAttribute) { + //android attribute IDs are the same always. So, I can optimize. + remoteAttrId = attrId; + } else { + final String attributeName = localRes.getResourceEntryName(attrId); + remoteAttrId = remoteRes.getIdentifier(attributeName, "attr", remotePackageName); + Logger.d(TAG, "attr "+attributeName+", local id "+attrId+", remote id "+remoteAttrId); + } + if (remoteAttrId != 0) { + attributeIdMap.put(remoteAttrId, attrId); + styleableIdList.add(remoteAttrId); + } + } + final int[] remoteMappedStyleable = new int[styleableIdList.size()]; + for(int i=0; i { + private static final String TAG = "ASK_KF"; + + private static final String XML_LAYOUT_RES_ID_ATTRIBUTE = "layoutResId"; + private static final String XML_LANDSCAPE_LAYOUT_RES_ID_ATTRIBUTE = "landscapeResId"; + private static final String XML_ICON_RES_ID_ATTRIBUTE = "iconResId"; + private static final String XML_DICTIONARY_NAME_ATTRIBUTE = "defaultDictionaryLocale"; + private static final String XML_ADDITIONAL_IS_LETTER_EXCEPTIONS_ATTRIBUTE = "additionalIsLetterExceptions"; + private static final String XML_SENTENCE_SEPARATOR_CHARACTERS_ATTRIBUTE = "sentenceSeparators"; + private static final String DEFAULT_SENTENCE_SEPARATORS = ".,!?)]:;"; + private static final String XML_PHYSICAL_TRANSLATION_RES_ID_ATTRIBUTE = "physicalKeyboardMappingResId"; + private static final String XML_DEFAULT_ATTRIBUTE = "defaultEnabled"; + + public KeyboardFactory() { + super(TAG, "com.liskovsoft.leankey.langpack.KEYBOARD", "com.liskovsoft.leankey.langpack.keyboards", + "Keyboards", "Keyboard", + 0, true); + } + + public List getAllAvailableKeyboards(Context askContext) { + return getAllAddOns(askContext); + } + + public List getEnabledKeyboards(Context askContext) { + final List allAddOns = getAllAddOns(askContext); + Logger.i(TAG, "Creating enabled addons list. I have a total of " + allAddOns.size() + " addons"); + + //getting shared prefs to determine which to create. + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(askContext); + + final ArrayList enabledAddOns = new ArrayList<>(); + for (int addOnIndex = 0; addOnIndex < allAddOns.size(); addOnIndex++) { + final KeyboardAddOnAndBuilder addOn = allAddOns.get(addOnIndex); + + final boolean addOnEnabled = sharedPreferences.getBoolean(addOn.getId(), addOn.getKeyboardDefaultEnabled()); + + if (addOnEnabled) { + enabledAddOns.add(addOn); + } + } + + // Fix: issue 219 + // Check if there is any keyboards created if not, lets create a default english keyboard + if (enabledAddOns.size() == 0) { + final SharedPreferences.Editor editor = sharedPreferences.edit(); + final KeyboardAddOnAndBuilder addOn = allAddOns.get(0); + editor.putBoolean(addOn.getId(), true); + editor.commit(); + enabledAddOns.add(addOn); + } + + for (final KeyboardAddOnAndBuilder addOn : enabledAddOns) { + Logger.d(TAG, "Factory provided addon: %s", addOn.getId()); + } + + return enabledAddOns; + } + + @Override + protected KeyboardAddOnAndBuilder createConcreteAddOn(Context askContext, Context context, String prefId, int nameId, String description, int sortIndex, AttributeSet attrs) { + final int layoutResId = attrs.getAttributeResourceValue(null, XML_LAYOUT_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + final int landscapeLayoutResId = attrs.getAttributeResourceValue(null, XML_LANDSCAPE_LAYOUT_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + //final int iconResId = attrs.getAttributeResourceValue(null, XML_ICON_RES_ID_ATTRIBUTE, R.drawable.sym_keyboard_notification_icon); + final int iconResId = 0; + final String defaultDictionary = attrs.getAttributeValue(null, XML_DICTIONARY_NAME_ATTRIBUTE); + final String additionalIsLetterExceptions = attrs.getAttributeValue(null, XML_ADDITIONAL_IS_LETTER_EXCEPTIONS_ATTRIBUTE); + String sentenceSeparators = attrs.getAttributeValue(null, XML_SENTENCE_SEPARATOR_CHARACTERS_ATTRIBUTE); + if (TextUtils.isEmpty(sentenceSeparators)) sentenceSeparators = DEFAULT_SENTENCE_SEPARATORS; + final int physicalTranslationResId = attrs.getAttributeResourceValue(null, XML_PHYSICAL_TRANSLATION_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); + // A keyboard is enabled by default if it is the first one (index==1) + final boolean keyboardDefault = attrs.getAttributeBooleanValue(null, XML_DEFAULT_ATTRIBUTE, sortIndex == 1); + + // asserting + if ((prefId == null) || (nameId == AddOn.INVALID_RES_ID) || (layoutResId == AddOn.INVALID_RES_ID)) { + Logger.e(TAG, "External Keyboard does not include all mandatory details! Will not create keyboard."); + return null; + } else { + Logger.d(TAG, + "External keyboard details: prefId:" + prefId + " nameId:" + + nameId + " resId:" + layoutResId + + " landscapeResId:" + landscapeLayoutResId + + " iconResId:" + iconResId + " defaultDictionary:" + + defaultDictionary); + return new KeyboardAddOnAndBuilder(askContext, context, + prefId, nameId, layoutResId, landscapeLayoutResId, + defaultDictionary, iconResId, physicalTranslationResId, + additionalIsLetterExceptions, sentenceSeparators, + description, sortIndex, keyboardDefault); + } + } + + public boolean hasMultipleAlphabets(Context askContext) { + return getEnabledKeyboards(askContext).size() > 1; + } + + public Keyboard createKeyboard(Context context) { + List keyboardBuilders = getAllAvailableKeyboards(context); + if (keyboardBuilders.size() == 0) + return new Keyboard(context, 0x7f04000c); // ru keyboard resource id + // remember, only one external keyboard supported + return keyboardBuilders.get(0).createKeyboard(); + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java new file mode 100644 index 0000000..24f5118 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/BuildConfig.java @@ -0,0 +1,6 @@ +package com.anysoftkeyboard.utils; + +public class BuildConfig { + public final static boolean TESTING_BUILD = true; + public final static boolean DEBUG = true; +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java new file mode 100644 index 0000000..f76fcf4 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogCatLogProvider.java @@ -0,0 +1,48 @@ +package com.anysoftkeyboard.utils; + +import android.os.Build; +import android.util.Log; + +/** + * Logger messages to Android's LogCat. Should be used only in DEBUG builds. + */ +public class LogCatLogProvider implements LogProvider { + @Override + public void v(String TAG, String text) { + Log.v(TAG, text); + } + + @Override + public void d(String TAG, String text) { + Log.d(TAG, text); + } + + @Override + public void yell(String TAG, String text) { + Log.w(TAG+"-YELL", text); + } + + @Override + public void i(String TAG, String text) { + Log.i(TAG, text); + } + + @Override + public void w(String TAG, String text) { + Log.w(TAG, text); + } + + @Override + public void e(String TAG, String text) { + Log.e(TAG, text); + } + + @Override + public void wtf(String TAG, String text) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + Log.wtf(TAG, text); + } else { + Log.e(TAG+" WTF", text); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java new file mode 100644 index 0000000..32a4848 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/LogProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.utils; + +public interface LogProvider { + + void v(String TAG, String text); + + void d(String TAG, String text); + + void yell(String TAG, String text); + + void i(String TAG, String text); + + void w(String TAG, String text); + + void e(String TAG, String text); + + void wtf(String TAG, String text); +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java new file mode 100644 index 0000000..f3a2aa1 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/Logger.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.utils; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Locale; + +public class Logger { + public static final String NEW_LINE = System.getProperty("line.separator"); + + private static final StringBuilder msFormatBuilder = new StringBuilder(1024); + private static final java.util.Formatter msFormatter = new java.util.Formatter(msFormatBuilder, Locale.US); + + private static final String[] msLogs = new String[BuildConfig.TESTING_BUILD ? 225 : 0]; + private static final String LVL_V = "V"; + private static final String LVL_D = "D"; + private static final String LVL_YELL = "YELL"; + private static final String LVL_I = "I"; + private static final String LVL_W = "W"; + private static final String LVL_E = "E"; + private static final String LVL_WTF = "WTF"; + private static int msLogIndex = 0; + @NonNull + private static LogProvider msLogger = new LogCatLogProvider(); + + private Logger() { + //no instances please. + } + + public static void setLogProvider(@NonNull LogProvider logProvider) { + msLogger = logProvider; + } + + private synchronized static void addLog(String level, String tag, String message) { + if (BuildConfig.TESTING_BUILD) { + msLogs[msLogIndex] = System.currentTimeMillis() + "-" + level + "-[" + tag + "] " + message; + msLogIndex = (msLogIndex + 1) % msLogs.length; + } + } + + private synchronized static void addLog(String level, String tag, String message, Throwable t) { + if (BuildConfig.TESTING_BUILD) { + addLog(level, tag, message); + addLog(level, tag, getStackTrace(t)); + } + } + + @NonNull + public synchronized static ArrayList getAllLogLinesList() { + ArrayList lines = new ArrayList<>(msLogs.length); + if (msLogs.length > 0) { + int index = msLogIndex; + do { + index--; + if (index == -1) index = msLogs.length - 1; + String logLine = msLogs[index]; + if (logLine == null) + break; + lines.add(msLogs[index]); + } + while (index != msLogIndex); + } + return lines; + } + + @NonNull + public synchronized static String getAllLogLines() { + if (BuildConfig.TESTING_BUILD) { + ArrayList lines = getAllLogLinesList(); + //now to build the string + StringBuilder sb = new StringBuilder("Log contains " + lines.size() + " lines:"); + while (lines.size() > 0) { + String line = lines.remove(lines.size() - 1); + sb.append(NEW_LINE); + sb.append(line); + } + return sb.toString(); + } else { + return "Not supported in RELEASE mode!"; + } + } + + public synchronized static void v(String TAG, String text, Object... args) { + if (BuildConfig.DEBUG) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.v(TAG, msg); + addLog(LVL_V, TAG, msg); + } + } + + public synchronized static void v(String TAG, String text, Throwable t) { + if (BuildConfig.DEBUG) { + msLogger.v(TAG, text + NEW_LINE + t); + addLog(LVL_V, TAG, text, t); + } + } + + public synchronized static void d(String TAG, String text) { + if (BuildConfig.TESTING_BUILD) { + msLogger.d(TAG, text); + addLog(LVL_D, TAG, text); + } + } + + public synchronized static void d(String TAG, String text, Object... args) { + if (BuildConfig.TESTING_BUILD) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.d(TAG, msg); + addLog(LVL_D, TAG, msg); + } + } + + public synchronized static void d(String TAG, String text, Throwable t) { + if (BuildConfig.TESTING_BUILD) { + msLogger.d(TAG, text + NEW_LINE + t); + addLog(LVL_D, TAG, text, t); + } + } + + public synchronized static void yell(String TAG, String text, Object... args) { + if (BuildConfig.TESTING_BUILD) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.yell(TAG, msg); + addLog(LVL_YELL, TAG, msg); + } + } + + public synchronized static void i(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.i(TAG, msg); + addLog(LVL_I, TAG, msg); + } + + public synchronized static void i(String TAG, String text, Throwable t) { + msLogger.i(TAG, text + NEW_LINE + t); + addLog(LVL_I, TAG, text, t); + } + + public synchronized static void w(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.w(TAG, msg); + addLog(LVL_W, TAG, msg); + } + + public synchronized static void w(String TAG, String text, Throwable t) { + msLogger.w(TAG, text + NEW_LINE + t); + addLog(LVL_W, TAG, text, t); + } + + public synchronized static void e(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.e(TAG, msg); + addLog(LVL_E, TAG, msg); + } + + //TODO: remove this method + public synchronized static void e(String TAG, String text, Throwable t) { + msLogger.e(TAG, text + NEW_LINE + t); + addLog(LVL_E, TAG, text, t); + } + + public synchronized static void w(String TAG, Throwable e, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + msLogger.e(TAG, msg + NEW_LINE + e); + addLog(LVL_E, TAG, msg); + } + + public synchronized static void wtf(String TAG, String text, Object... args) { + String msg = args == null ? text : msFormatter.format(text, args).toString(); + msFormatBuilder.setLength(0); + addLog(LVL_WTF, TAG, msg); + msLogger.wtf(TAG, msg); + } + + public synchronized static void wtf(String TAG, String text, Throwable t) { + addLog(LVL_WTF, TAG, text, t); + msLogger.wtf(TAG, text + NEW_LINE + t); + } + + public static String getStackTrace(Throwable ex) { + StackTraceElement[] stackTrace = ex.getStackTrace(); + StringBuilder sb = new StringBuilder(); + + for (StackTraceElement element : stackTrace) { + sb.append("at ");//this is required for easy Proguard decoding. + sb.append(element.toString()); + sb.append(NEW_LINE); + } + + if (ex.getCause() == null) + return sb.toString(); + else { + ex = ex.getCause(); + String cause = getStackTrace(ex); + sb.append("*** Cause: ").append(ex.getClass().getName()); + sb.append(NEW_LINE); + sb.append("** Message: ").append(ex.getMessage()); + sb.append(NEW_LINE); + sb.append("** Stack track: ").append(cause); + sb.append(NEW_LINE); + return sb.toString(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java new file mode 100644 index 0000000..da8b669 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/NullLogProvider.java @@ -0,0 +1,39 @@ +package com.anysoftkeyboard.utils; + +/** + * Doesn't do anything. For release. + */ +public class NullLogProvider implements LogProvider { + @Override + public void v(String TAG, String text) { + } + + @Override + public void d(String TAG, String text) { + } + + @Override + public void yell(String TAG, String text) { + + } + + @Override + public void i(String TAG, String text) { + + } + + @Override + public void w(String TAG, String text) { + + } + + @Override + public void e(String TAG, String text) { + + } + + @Override + public void wtf(String TAG, String text) { + + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java new file mode 100644 index 0000000..850c7db --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlUtils.java @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.utils; + +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class XmlUtils { + + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + + public static int convertValueToList(CharSequence value, String[] options, int defaultValue) { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + + return defaultValue; + } + + public static boolean convertValueToBoolean(CharSequence value, boolean defaultValue) { + boolean result = false; + + if (null == value) + return defaultValue; + + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + + return result; + } + + public static int convertValueToInt(CharSequence charSeq, int defaultValue) { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } else if ('#' == nm.charAt(index)) { + index++; + base = 16; + } + + return Integer.parseInt(nm.substring(index), base) * sign; + } + + public static int convertValueToUnsignedInt(String value, int defaultValue) { + if (null == value) + return defaultValue; + + return parseUnsignedIntAttribute(value); + } + + public static int parseUnsignedIntAttribute(CharSequence charSeq) { + String value = charSeq.toString(); + + long bits; + int index = 0; + int len = value.length(); + int base = 10; + + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + + char c = value.charAt(index + 1); + + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + + return (int) Long.parseLong(value.substring(index), base); + } + + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ +// public static final void writeMapXml(Map val, OutputStream out) +// throws XmlPullParserException, java.io.IOException { +// XmlSerializer serializer = new FastXmlSerializer(); +// serializer.setOutput(out, "utf-8"); +// serializer.startDocument(null, true); +// serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); +// writeMapXml(val, null, serializer); +// serializer.endDocument(); +// } + + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static void writeListXml(List val, OutputStream out) + throws XmlPullParserException, java.io.IOException { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + writeValueXml(e.getValue(), (String) e.getKey(), out); + } + + out.endTag(null, "map"); + } + + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + + int N = val.size(); + int i = 0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + + out.endTag(null, "list"); + } + + public static void writeSetXml(Set val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "set"); + if (name != null) { + out.attribute(null, "name", name); + } + + for (Object v : val) { + writeValueXml(v, null, out); + } + + out.endTag(null, "set"); + } + + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * @see #writeMapXml + * @see #writeValueXml + */ + public static void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + StringBuilder sb = new StringBuilder(val.length * 2); + for (byte b : val) { + int h = b >> 4; + sb.append(h >= 10 ? ('a' + h - 10) : ('0' + h)); + h = b & 0xff; + sb.append(h >= 10 ? ('a' + h - 10) : ('0' + h)); + } + + out.text(sb.toString()); + + out.endTag(null, "byte-array"); + } + + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int aVal : val) { + out.startTag(null, "item"); + out.attribute(null, "value", Integer.toString(aVal)); + out.endTag(null, "item"); + } + + out.endTag(null, "int-array"); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + *

+ * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + public static void writeValueXml(Object v, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + String typeStr; + if (v == null) { + out.startTag(null, "null"); + if (name != null) { + out.attribute(null, "name", name); + } + out.endTag(null, "null"); + return; + } else if (v instanceof String) { + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else if (v instanceof Integer) { + typeStr = "int"; + } else if (v instanceof Long) { + typeStr = "long"; + } else if (v instanceof Float) { + typeStr = "float"; + } else if (v instanceof Double) { + typeStr = "double"; + } else if (v instanceof Boolean) { + typeStr = "boolean"; + } else if (v instanceof byte[]) { + writeByteArrayXml((byte[]) v, name, out); + return; + } else if (v instanceof int[]) { + writeIntArrayXml((int[]) v, name, out); + return; + } else if (v instanceof Map) { + writeMapXml((Map) v, name, out); + return; + } else if (v instanceof List) { + writeListXml((List) v, name, out); + return; + } else if (v instanceof Set) { + writeSetXml((Set) v, name, out); + return; + } else if (v instanceof CharSequence) { + // XXX This is to allow us to at least write something if + // we encounter styled text... but it means we will drop all + // of the styling information. :( + out.startTag(null, "string"); + if (name != null) { + out.attribute(null, "name", name); + } + out.text(v.toString()); + out.endTag(null, "string"); + return; + } else { + throw new RuntimeException("writeValueXml: unable to write value " + v); + } + + out.startTag(null, typeStr); + if (name != null) { + out.attribute(null, "name", name); + } + out.attribute(null, "value", v.toString()); + out.endTag(null, typeStr); + } + + /** + * Read a HashMap from an InputStream containing XML. The stream can + * previously have been written by writeMapXml(). + * + * @param in The InputStream from which to read. + * @return HashMap The resulting map. + * @see #readListXml + * @see #readValueXml + * @see #readThisMapXml + * #see #writeMapXml + */ + public static HashMap readMapXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashMap) readValueXml(parser, new String[1]); + } + + /** + * Read an ArrayList from an InputStream containing XML. The stream can + * previously have been written by writeListXml(). + * + * @param in The InputStream from which to read. + * @return ArrayList The resulting list. + * @see #readMapXml + * @see #readValueXml + * @see #readThisListXml + * @see #writeListXml + */ + public static ArrayList readListXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (ArrayList) readValueXml(parser, new String[1]); + } + + + /** + * Read a HashSet from an InputStream containing XML. The stream can + * previously have been written by writeSetXml(). + * + * @param in The InputStream from which to read. + * @return HashSet The resulting set. + * @throws XmlPullParserException + * @throws java.io.IOException + * @see #readValueXml + * @see #readThisSetXml + * @see #writeSetXml + */ + public static HashSet readSetXml(InputStream in) + throws XmlPullParserException, java.io.IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + return (HashSet) readValueXml(parser, new String[1]); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned after the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * @return HashMap The newly generated map. + * @see #readMapXml + */ + public static HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + HashMap map = new HashMap<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + if (name[0] != null) { + map.put(name[0], val); + } else { + throw new XmlPullParserException( + "Map value without name attribute: " + parser.getName()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * @return HashMap The newly generated list. + * @see #readListXml + */ + public static ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + ArrayList list = new ArrayList<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + list.add(val); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * @return HashSet The newly generated set. + * @throws XmlPullParserException + * @throws java.io.IOException + * @see #readSetXml + */ + public static HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + HashSet set = new HashSet<>(); + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + Object val = readThisValueXml(parser, name); + set.add(val); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return set; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * @return Returns a newly generated int[]. + * @see #readListXml + */ + public static int[] readThisIntArrayXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + + int[] array = new int[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned at the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * @return Object The newly generated value object. + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException { + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + return readThisValueXml(parser, name); + } else if (eventType == XmlPullParser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + throw new XmlPullParserException( + "Unexpected end of document"); + } + + private static Object readThisValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + + Object res; + + switch (tagName) { + case "null": + res = null; + break; + case "string": + String value = ""; + int eventType; + while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + return value; + } + throw new XmlPullParserException("Unexpected end tag in : " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + value += parser.getText(); + } else if (eventType == XmlPullParser.START_TAG) { + throw new XmlPullParserException("Unexpected start tag in : " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in "); + case "int": + res = Integer.parseInt(parser.getAttributeValue(null, "value")); + break; + case "long": + res = Long.valueOf(parser.getAttributeValue(null, "value")); + break; + case "float": + res = Float.valueOf(parser.getAttributeValue(null, "value")); + break; + case "double": + res = Double.valueOf(parser.getAttributeValue(null, "value")); + break; + case "boolean": + res = Boolean.valueOf(parser.getAttributeValue(null, "value")); + break; + case "int-array": + parser.next(); + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + return res; + case "map": + parser.next(); + res = readThisMapXml(parser, "map", name); + name[0] = valueName; + return res; + case "list": + parser.next(); + res = readThisListXml(parser, "list", name); + name[0] = valueName; + return res; + case "set": + parser.next(); + res = readThisSetXml(parser, "set", name); + name[0] = valueName; + return res; + default: + throw new XmlPullParserException( + "Unknown tag: " + tagName); + } + + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + return res; + } + throw new XmlPullParserException("Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == XmlPullParser.TEXT) { + throw new XmlPullParserException("Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == XmlPullParser.START_TAG) { + throw new XmlPullParserException("Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException("Unexpected end of document in <" + tagName + ">"); + } + + public static void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + public static void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + ; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java new file mode 100644 index 0000000..3fb6f36 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java @@ -0,0 +1,254 @@ + +/* + * Copyright (c) 2013 Menny Even-Danan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.anysoftkeyboard.utils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.Writer; +import java.util.Stack; + +/** + * Makes writing XML much much easier. + * + * @author Henri Yandell + * @author Menny Even Danan - just + * added some features on Henri's initial version + * @version 0.2 + */ +public class XmlWriter { + + private static final String INDENT_STRING = " "; + private final boolean thisIsWriterOwner;// is this instance the owner? + private final Writer writer; // underlying writer + private final int indentingOffset; + private final Stack stack; // of xml entity names + private final StringBuffer attrs; // current attribute string + private boolean empty; // is the current node empty + private boolean justWroteText; + private boolean closed; // is the current node closed... + + /** + * Create an XmlWriter on top of an existing java.io.Writer. + * + * @throws IOException + */ + public XmlWriter(Writer writer, boolean takeOwnership, int indentingOffset, boolean addXmlPrefix) + throws IOException { + thisIsWriterOwner = takeOwnership; + this.indentingOffset = indentingOffset; + this.writer = writer; + this.closed = true; + this.stack = new Stack(); + this.attrs = new StringBuffer(); + if (addXmlPrefix) + this.writer.write("\n"); + } + + public XmlWriter(File outputFile) throws IOException { + this(new FileWriter(outputFile), true, 0, true); + } + + /** + * Begin to output an entity. + * + * @param String name of entity. + */ + public XmlWriter writeEntity(String name) throws IOException { + closeOpeningTag(true); + this.closed = false; + for (int tabIndex = 0; tabIndex < stack.size() + indentingOffset; tabIndex++) + this.writer.write(INDENT_STRING); + this.writer.write("<"); + this.writer.write(name); + stack.add(name); + this.empty = true; + this.justWroteText = false; + return this; + } + + // close off the opening tag + private void closeOpeningTag(final boolean newLine) throws IOException { + if (!this.closed) { + writeAttributes(); + this.closed = true; + this.writer.write(">"); + if (newLine) + this.writer.write("\n"); + } + } + + // write out all current attributes + private void writeAttributes() throws IOException { + this.writer.write(this.attrs.toString()); + this.attrs.setLength(0); + this.empty = false; + } + + /** + * Write an attribute out for the current entity. Any xml characters in the + * value are escaped. Currently it does not actually throw the exception, + * but the api is set that way for future changes. + * + * @param String name of attribute. + * @param String value of attribute. + */ + public XmlWriter writeAttribute(String attr, String value) { + this.attrs.append(" "); + this.attrs.append(attr); + this.attrs.append("=\""); + this.attrs.append(escapeXml(value)); + this.attrs.append("\""); + return this; + } + + /** + * End the current entity. This will throw an exception if it is called when + * there is not a currently open entity. + * + * @throws IOException + */ + public XmlWriter endEntity() throws IOException { + if (this.stack.empty()) { + throw new InvalidObjectException("Called endEntity too many times. "); + } + String name = this.stack.pop(); + if (name != null) { + if (this.empty) { + writeAttributes(); + this.writer.write("/>\n"); + } else { + if (!this.justWroteText) { + for (int tabIndex = 0; tabIndex < stack.size() + indentingOffset; tabIndex++) + this.writer.write(INDENT_STRING); + } + this.writer.write("\n"); + } + this.empty = false; + this.closed = true; + this.justWroteText = false; + } + return this; + } + + /** + * Close this writer. It does not close the underlying writer, but does + * throw an exception if there are as yet unclosed tags. + * + * @throws IOException + */ + public void close() throws IOException { + if (!this.stack.empty()) { + throw new InvalidObjectException("Tags are not all closed. " + + "Possibly, " + this.stack.pop() + " is unclosed. "); + } + if (thisIsWriterOwner) { + this.writer.flush(); + this.writer.close(); + } + } + + /** + * Output body text. Any xml characters are escaped. + */ + public XmlWriter writeText(String text) throws IOException { + closeOpeningTag(false); + this.empty = false; + this.justWroteText = true; + this.writer.write(escapeXml(text)); + return this; + } + + // Static functions lifted from generationjava helper classes + // to make the jar smaller. + + // from XmlW + static public String escapeXml(String str) { + str = replaceString(str, "&", "&"); + str = replaceString(str, "<", "<"); + str = replaceString(str, ">", ">"); + str = replaceString(str, "\"", """); + str = replaceString(str, "'", "'"); + return str; + } + + // from StringW + static public String replaceString(String text, String repl, String with) { + return replaceString(text, repl, with, -1); + } + + /** + * Replace a string with another string inside a larger string, for the + * first n values of the search string. + * + * @param text String to do search and replace in + * @param repl String to search for + * @param with String to replace with + * @param n int values to replace + * @return String with n values replacEd + */ + static public String replaceString(String text, String repl, String with, int max) { + if (text == null) { + return null; + } + + StringBuffer buffer = new StringBuffer(text.length()); + int start = 0; + int end = 0; + while ((end = text.indexOf(repl, start)) != -1) { + buffer.append(text.substring(start, end)).append(with); + start = end + repl.length(); + + if (--max == 0) { + break; + } + } + buffer.append(text.substring(start)); + + return buffer.toString(); + } + // + // static public void test1() throws WritingException { + // Writer writer = new java.io.StringWriter(); + // XmlWriter xmlwriter = new XmlWriter(writer); + // xmlwriter.writeEntity("person").writeAttribute("name", + // "fred").writeAttribute("age", + // "12").writeEntity("phone").writeText("4254343").endEntity().writeEntity("bob").endEntity().endEntity(); + // xmlwriter.close(); + // System.err.println(writer.toString()); + // } + // static public void test2() throws WritingException { + // Writer writer = new java.io.StringWriter(); + // XmlWriter xmlwriter = new XmlWriter(writer); + // xmlwriter.writeEntity("person"); + // xmlwriter.writeAttribute("name", "fred"); + // xmlwriter.writeAttribute("age", "12"); + // xmlwriter.writeEntity("phone"); + // xmlwriter.writeText("4254343"); + // xmlwriter.endEntity(); + // xmlwriter.writeEntity("bob"); + // xmlwriter.endEntity(); + // xmlwriter.endEntity(); + // xmlwriter.close(); + // System.err.println(writer.toString()); + // } + +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java new file mode 100644 index 0000000..4af1ad7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/EventLogTags.java @@ -0,0 +1,16 @@ +package com.google.android.leanback.ime; + +import android.util.EventLog; + +public class EventLogTags { + public static final int TIME_LEANBACK_IME_INPUT = 270900; + public static final int TOTAL_LEANBACK_IME_BACKSPACE = 270902; + + public static void writeTimeLeanbackImeInput(long var0, long var2) { + EventLog.writeEvent(270900, new Object[]{var0, var2}); + } + + public static void writeTotalLeanbackImeBackspace(int var0) { + EventLog.writeEvent(270902, var0); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java new file mode 100644 index 0000000..f85ead0 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardContainer.java @@ -0,0 +1,1302 @@ +package com.google.android.leanback.ime; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.animation.Animator.AnimatorListener; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.SpeechRecognizer; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.Transformation; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import com.google.android.leanback.ime.voice.RecognizerView; +import com.google.android.leanback.ime.voice.SpeechLevelSource; +import com.google.leanback.ime.LeanbackImeService; +import com.liskovsoft.keyboardaddons.KeyboardManager; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; +import java.util.Locale; + +public class LeanbackKeyboardContainer { + private static final boolean DEBUG = false; + public static final double DIRECTION_STEP_MULTIPLIER = 1.25D; + private static final String IME_PRIVATE_OPTIONS_ESCAPE_NORTH = "EscapeNorth=1"; + private static final String IME_PRIVATE_OPTIONS_VOICE_DISMISS = "VoiceDismiss=1"; + private static final long MOVEMENT_ANIMATION_DURATION = 150L; + private static final int MSG_START_INPUT_VIEW = 0; + protected static final float PHYSICAL_HEIGHT_CM = 5.0F; + protected static final float PHYSICAL_WIDTH_CM = 12.0F; + private static final String TAG = "LbKbContainer"; + public static final double TOUCH_MOVE_MIN_DISTANCE = 0.1D; + public static final int TOUCH_STATE_CLICK = 3; + public static final int TOUCH_STATE_NO_TOUCH = 0; + public static final int TOUCH_STATE_TOUCH_MOVE = 2; + public static final int TOUCH_STATE_TOUCH_SNAP = 1; + private static final boolean VOICE_SUPPORTED = true; + public static final Interpolator sMovementInterpolator = new DecelerateInterpolator(1.5F); + private Keyboard mAbcKeyboard; + private Keyboard mAbcKeyboardRU; + private Button mActionButtonView; + private final float mAlphaIn; + private final float mAlphaOut; + private boolean mAutoEnterSpaceEnabled; + private boolean mCapCharacters; + private boolean mCapSentences; + private boolean mCapWords; + private final int mClickAnimDur; + private LeanbackImeService mContext; + private LeanbackKeyboardContainer.KeyFocus mCurrKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private LeanbackKeyboardContainer.DismissListener mDismissListener; + private LeanbackKeyboardContainer.KeyFocus mDownKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private CharSequence mEnterKeyText; + private int mEnterKeyTextResId; + private boolean mEscapeNorthEnabled; + private Keyboard mInitialMainKeyboard; + private KeyboardManager mKeyboardManager; + private View mKeyboardsContainer; + private LeanbackKeyboardView mMainKeyboardView; + private int mMiniKbKeyIndex; + private Keyboard mNumKeyboard; + private float mOverestimate; + private PointF mPhysicalSelectPos = new PointF(2.0F, 0.5F); + private PointF mPhysicalTouchPos = new PointF(2.0F, 0.5F); + private LeanbackKeyboardView mPrevView; + private Intent mRecognizerIntent; + private Rect mRect = new Rect(); + private RelativeLayout mRootView; + private View mSelector; + private LeanbackKeyboardContainer.ScaleAnimation mSelectorAnimation; + private ValueAnimator mSelectorAnimator; + private SpeechLevelSource mSpeechLevelSource; + private SpeechRecognizer mSpeechRecognizer; + private LinearLayout mSuggestions; + private View mSuggestionsBg; + private HorizontalScrollView mSuggestionsContainer; + private boolean mSuggestionsEnabled; + private Keyboard mSymKeyboard; + private LeanbackKeyboardContainer.KeyFocus mTempKeyInfo = new LeanbackKeyboardContainer.KeyFocus(); + private PointF mTempPoint = new PointF(); + private boolean mTouchDown = false; + private int mTouchState = 0; + private final int mVoiceAnimDur; + private final LeanbackKeyboardContainer.VoiceIntroAnimator mVoiceAnimator; + private RecognizerView mVoiceButtonView; + private boolean mVoiceEnabled; + private AnimatorListener mVoiceEnterListener = new AnimatorListener() { + public void onAnimationCancel(Animator var1) { + } + + public void onAnimationEnd(Animator var1) { + } + + public void onAnimationRepeat(Animator var1) { + } + + public void onAnimationStart(Animator var1) { + LeanbackKeyboardContainer.this.mSelector.setVisibility(View.INVISIBLE); + LeanbackKeyboardContainer.this.startRecognition(LeanbackKeyboardContainer.this.mContext); + } + }; + private AnimatorListener mVoiceExitListener = new AnimatorListener() { + public void onAnimationCancel(Animator var1) { + } + + public void onAnimationEnd(Animator var1) { + LeanbackKeyboardContainer.this.mSelector.setVisibility(View.VISIBLE); + } + + public void onAnimationRepeat(Animator var1) { + } + + public void onAnimationStart(Animator var1) { + LeanbackKeyboardContainer.this.mVoiceButtonView.showNotListening(); + LeanbackKeyboardContainer.this.mSpeechRecognizer.cancel(); + LeanbackKeyboardContainer.this.mSpeechRecognizer.setRecognitionListener((RecognitionListener) null); + LeanbackKeyboardContainer.this.mVoiceOn = false; + } + }; + private boolean mVoiceKeyDismissesEnabled; + private LeanbackKeyboardContainer.VoiceListener mVoiceListener; + private boolean mVoiceOn; + private Float mX; + private Float mY; + + public LeanbackKeyboardContainer(Context context) { + this.mContext = (LeanbackImeService) context; + final Resources res = this.mContext.getResources(); + this.mVoiceAnimDur = res.getInteger(R.integer.voice_anim_duration); + this.mAlphaIn = res.getFraction(R.fraction.alpha_in, 1, 1); + this.mAlphaOut = res.getFraction(R.fraction.alpha_out, 1, 1); + this.mVoiceAnimator = new LeanbackKeyboardContainer.VoiceIntroAnimator(this.mVoiceEnterListener, this.mVoiceExitListener); + this.initKeyboards(); + this.mRootView = (RelativeLayout) this.mContext.getLayoutInflater().inflate(R.layout.root_leanback, null); + this.mKeyboardsContainer = this.mRootView.findViewById(R.id.keyboard); + this.mSuggestionsBg = this.mRootView.findViewById(R.id.candidate_background); + this.mSuggestionsContainer = (HorizontalScrollView) this.mRootView.findViewById(R.id.suggestions_container); + this.mSuggestions = (LinearLayout) this.mSuggestionsContainer.findViewById(R.id.suggestions); + this.mMainKeyboardView = (LeanbackKeyboardView) this.mRootView.findViewById(R.id.main_keyboard); + this.mVoiceButtonView = (RecognizerView) this.mRootView.findViewById(R.id.voice); + this.mActionButtonView = (Button) this.mRootView.findViewById(R.id.enter); + this.mSelector = this.mRootView.findViewById(R.id.selector); + this.mSelectorAnimation = new LeanbackKeyboardContainer.ScaleAnimation((FrameLayout) this.mSelector); + this.mOverestimate = this.mContext.getResources().getFraction(R.fraction.focused_scale, 1, 1); + final float scale = context.getResources().getFraction(R.fraction.clicked_scale, 1, 1); + this.mClickAnimDur = context.getResources().getInteger(R.integer.clicked_anim_duration); + this.mSelectorAnimator = ValueAnimator.ofFloat(new float[]{1.0F, scale}); + this.mSelectorAnimator.setDuration((long) this.mClickAnimDur); + this.mSelectorAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float value = (Float) animation.getAnimatedValue(); + LeanbackKeyboardContainer.this.mSelector.setScaleX(value); + LeanbackKeyboardContainer.this.mSelector.setScaleY(value); + } + }); + this.mSpeechLevelSource = new SpeechLevelSource(); + this.mVoiceButtonView.setSpeechLevelSource(this.mSpeechLevelSource); + this.mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this.mContext); + this.mVoiceButtonView.setCallback(new RecognizerView.Callback() { + public void onCancelRecordingClicked() { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + + public void onStartRecordingClicked() { + LeanbackKeyboardContainer.this.startVoiceRecording(); + } + + public void onStopRecordingClicked() { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + }); + } + + private void configureFocus(LeanbackKeyboardContainer.KeyFocus focus, Rect rect, int index, int type) { + focus.type = type; + focus.index = index; + focus.rect.set(rect); + } + + private void configureFocus(LeanbackKeyboardContainer.KeyFocus focus, Rect rect, int index, Key key, int type) { + focus.type = type; + if (key != null) { + if (key.codes != null) { + focus.code = key.codes[0]; + } else { + focus.code = 0; + } + + focus.index = index; + focus.label = key.label; + focus.rect.left = key.x + rect.left; + focus.rect.top = key.y + rect.top; + focus.rect.right = focus.rect.left + key.width; + focus.rect.bottom = focus.rect.top + key.height; + } + } + + private void escapeNorth() { + this.mDismissListener.onDismiss(false); + } + + private PointF getAlignmentPosition(final float posXCm, final float posYCm, final PointF result) { + final float width = (float) (this.mRootView.getWidth() - this.mRootView.getPaddingRight() - this.mRootView.getPaddingLeft()); + final float height = (float) (this.mRootView.getHeight() - this.mRootView.getPaddingTop() - this.mRootView.getPaddingBottom()); + final float size = this.mContext.getResources().getDimension(R.dimen.selector_size); + result.x = posXCm / 12.0F * (width - size) + (float) this.mRootView.getPaddingLeft(); + result.y = posYCm / 5.0F * (height - size) + (float) this.mRootView.getPaddingTop(); + return result; + } + + private void getPhysicalPosition(final float x, final float y, final PointF result) { + float width = (float) (this.mSelector.getWidth() / 2); + float height = (float) (this.mSelector.getHeight() / 2); + float posXCm = (float) (this.mRootView.getWidth() - this.mRootView.getPaddingRight() - this.mRootView.getPaddingLeft()); + float posYCm = (float) (this.mRootView.getHeight() - this.mRootView.getPaddingTop() - this.mRootView.getPaddingBottom()); + float size = this.mContext.getResources().getDimension(R.dimen.selector_size); + result.x = (x - width - (float) this.mRootView.getPaddingLeft()) * 12.0F / (posXCm - size); + result.y = (y - height - (float) this.mRootView.getPaddingTop()) * 5.0F / (posYCm - size); + } + + private PointF getTouchSnapPosition() { + PointF var1 = new PointF(); + this.getPhysicalPosition((float) this.mCurrKeyInfo.rect.centerX(), (float) this.mCurrKeyInfo.rect.centerY(), var1); + return var1; + } + + private void initKeyboards() { + this.mAbcKeyboard = new Keyboard(this.mContext, R.xml.qwerty_us); + this.mSymKeyboard = new Keyboard(this.mContext, R.xml.sym_us); + this.updateAddonKeyboard(); + this.mNumKeyboard = new Keyboard(this.mContext, R.xml.number); + } + + private boolean isMatch(Locale var1, Locale[] var2) { + int var4 = var2.length; + + for (int var3 = 0; var3 < var4; ++var3) { + Locale var5 = var2[var3]; + if ((TextUtils.isEmpty(var5.getLanguage()) || TextUtils.equals(var1.getLanguage(), var5.getLanguage())) && (TextUtils.isEmpty + (var5.getCountry()) || TextUtils.equals(var1.getCountry(), var5.getCountry()))) { + return true; + } + } + + return false; + } + + private void moveFocusToIndex(int var1, int var2) { + Key var3 = this.mMainKeyboardView.getKey(var1); + this.configureFocus(this.mTempKeyInfo, this.mRect, var1, var3, var2); + this.setTouchState(0); + this.setKbFocus(this.mTempKeyInfo, true, true); + } + + private void offsetRect(Rect var1, View var2) { + var1.left = 0; + var1.top = 0; + var1.right = var2.getWidth(); + var1.bottom = var2.getHeight(); + this.mRootView.offsetDescendantRectToMyCoords(var2, var1); + } + + private void onToggleCapsLock() { + this.onShiftDoubleClick(this.isCapsLockOn()); + } + + private void setImeOptions(Resources var1, EditorInfo var2) { + if (this.mInitialMainKeyboard == null) { + this.mInitialMainKeyboard = this.mAbcKeyboard; + } + + this.mSuggestionsEnabled = false; + this.mAutoEnterSpaceEnabled = false; + this.mVoiceEnabled = false; + this.mEscapeNorthEnabled = false; + this.mVoiceKeyDismissesEnabled = false; + label67: + switch (LeanbackUtils.getInputTypeClass(var2)) { + case 1: + switch (LeanbackUtils.getInputTypeVariation(var2)) { + case 16: + case 32: + case 160: + case 208: + this.mSuggestionsEnabled = false; + this.mAutoEnterSpaceEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + break label67; + case 96: + case 128: + case 144: + case 224: + this.mSuggestionsEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + default: + break label67; + } + case 2: + case 3: + case 4: + this.mSuggestionsEnabled = false; + this.mVoiceEnabled = false; + this.mInitialMainKeyboard = this.mAbcKeyboard; + } + + if (this.mSuggestionsEnabled) { + if ((var2.inputType & 524288) == 0) { + ; + } + + this.mSuggestionsEnabled = false; + } + + if (this.mAutoEnterSpaceEnabled) { + if (this.mSuggestionsEnabled && this.mAutoEnterSpaceEnabled) { + ; + } + + this.mAutoEnterSpaceEnabled = false; + } + + if ((var2.inputType & 16384) != 0) { + ; + } + + this.mCapSentences = false; + if ((var2.inputType & 8192) == 0 && LeanbackUtils.getInputTypeVariation(var2) == 96) { + ; + } + + this.mCapWords = false; + if ((var2.inputType & 4096) != 0) { + ; + } + + this.mCapCharacters = false; + if (var2.privateImeOptions != null) { + if (var2.privateImeOptions.contains("EscapeNorth=1")) { + this.mEscapeNorthEnabled = false; + } + + if (var2.privateImeOptions.contains("VoiceDismiss=1")) { + this.mVoiceKeyDismissesEnabled = false; + } + } + + this.mEnterKeyText = var2.actionLabel; + if (TextUtils.isEmpty(this.mEnterKeyText)) { + switch (LeanbackUtils.getImeAction(var2)) { + case 2: + this.mEnterKeyTextResId = R.string.label_go_key; + return; + case 3: + this.mEnterKeyTextResId = R.string.label_search_key; + return; + case 4: + this.mEnterKeyTextResId = R.string.label_send_key; + return; + case 5: + this.mEnterKeyTextResId = R.string.label_next_key; + return; + default: + this.mEnterKeyTextResId = R.string.label_done_key; + } + } + + } + + private void setKbFocus(final LeanbackKeyboardContainer.KeyFocus focus, final boolean forceFocusChange, final boolean animate) { + boolean clicked = true; + if (!focus.equals(this.mCurrKeyInfo) || forceFocusChange) { + LeanbackKeyboardView prevView = this.mPrevView; + this.mPrevView = null; + boolean overestimateWidth = false; + boolean overestimateHeight = false; + switch (focus.type) { + case KeyFocus.TYPE_MAIN: + if (focus.code != 32) { + overestimateWidth = true; + } else { + overestimateWidth = false; + } + + LeanbackKeyboardView mainView = this.mMainKeyboardView; + int index = focus.index; + if (this.mTouchState == 3) { + overestimateHeight = true; + } else { + overestimateHeight = false; + } + + mainView.setFocus(index, overestimateHeight, overestimateWidth); + this.mPrevView = this.mMainKeyboardView; + overestimateHeight = true; + break; + case KeyFocus.TYPE_VOICE: + this.mVoiceButtonView.setMicFocused(true); + this.dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_ACTION: + LeanbackUtils.sendAccessibilityEvent(this.mActionButtonView, true); + this.dismissMiniKeyboard(); + break; + case KeyFocus.TYPE_SUGGESTION: + this.dismissMiniKeyboard(); + } + + if (prevView != null && prevView != this.mPrevView) { + if (this.mTouchState != 3) { + clicked = false; + } + + prevView.setFocus(-1, clicked); + } + + this.setSelectorToFocus(focus.rect, overestimateWidth, overestimateHeight, animate); + this.mCurrKeyInfo.set(focus); + } + } + + private void setShiftState(int var1) { + this.mMainKeyboardView.setShiftState(var1); + } + + private void setTouchStateInternal(int var1) { + this.mTouchState = var1; + } + + private void startRecognition(Context var1) { + this.mRecognizerIntent = new Intent("android.speech.action.RECOGNIZE_SPEECH"); + this.mRecognizerIntent.putExtra("android.speech.extra.LANGUAGE_MODEL", "free_form"); + this.mRecognizerIntent.putExtra("android.speech.extra.PARTIAL_RESULTS", true); + this.mSpeechRecognizer.setRecognitionListener(new RecognitionListener() { + float peakRmsLevel = 0.0F; + int rmsCounter = 0; + + public void onBeginningOfSpeech() { + LeanbackKeyboardContainer.this.mVoiceButtonView.showRecording(); + } + + public void onBufferReceived(byte[] var1) { + } + + public void onEndOfSpeech() { + LeanbackKeyboardContainer.this.mVoiceButtonView.showRecognizing(); + LeanbackKeyboardContainer.this.mVoiceOn = false; + } + + public void onError(int var1) { + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + switch (var1) { + case 4: + Log.d("LbKbContainer", "recognizer error server error"); + return; + case 5: + Log.d("LbKbContainer", "recognizer error client error"); + return; + case 6: + Log.d("LbKbContainer", "recognizer error speech timeout"); + return; + case 7: + Log.d("LbKbContainer", "recognizer error no match"); + return; + default: + Log.d("LbKbContainer", "recognizer other error " + var1); + } + } + + public void onEvent(int var1, Bundle var2) { + } + + public void onPartialResults(Bundle var1) { + synchronized (this) { + } + } + + public void onReadyForSpeech(Bundle var1) { + LeanbackKeyboardContainer.this.mVoiceButtonView.showListening(); + } + + public void onResults(Bundle var1) { + ArrayList var2 = var1.getStringArrayList("results_recognition"); + if (var2 != null && LeanbackKeyboardContainer.this.mVoiceListener != null) { + LeanbackKeyboardContainer.this.mVoiceListener.onVoiceResult((String) var2.get(0)); + } + + LeanbackKeyboardContainer.this.cancelVoiceRecording(); + } + + public void onRmsChanged(float param1) { + // $FF: Couldn't be decompiled + throw new IllegalStateException("method not implemented"); + } + }); + this.mSpeechRecognizer.startListening(this.mRecognizerIntent); + } + + public void alignSelector(float var1, float var2, boolean var3) { + var1 -= (float) (this.mSelector.getWidth() / 2); + var2 -= (float) (this.mSelector.getHeight() / 2); + if (!var3) { + this.mSelector.setX(var1); + this.mSelector.setY(var2); + } else { + this.mSelector.animate().x(var1).y(var2).setInterpolator(sMovementInterpolator).setDuration(150L).start(); + } + } + + public boolean areSuggestionsEnabled() { + return this.mSuggestionsEnabled; + } + + public void cancelVoiceRecording() { + this.mVoiceAnimator.startExitAnimation(); + } + + public void clearSuggestions() { + this.mSuggestions.removeAllViews(); + if (this.getCurrFocus().type == 3) { + this.resetFocusCursor(); + } + + } + + public boolean dismissMiniKeyboard() { + return this.mMainKeyboardView.dismissMiniKeyboard(); + } + + public boolean enableAutoEnterSpace() { + return this.mAutoEnterSpaceEnabled; + } + + public boolean getBestFocus(final Float x, final Float y, final LeanbackKeyboardContainer.KeyFocus focus) { + this.offsetRect(this.mRect, this.mActionButtonView); + int actionLeft = this.mRect.left; + this.offsetRect(this.mRect, this.mMainKeyboardView); + int keyboardTop = this.mRect.top; + Float newX = x; + if (x == null) { + newX = this.mX; + } + + Float newY = y; + if (y == null) { + newY = this.mY; + } + + int count = this.mSuggestions.getChildCount(); + if (newY < (float) keyboardTop && count > 0 && this.mSuggestionsEnabled) { + for (actionLeft = 0; actionLeft < count; ++actionLeft) { + View view = this.mSuggestions.getChildAt(actionLeft); + this.offsetRect(this.mRect, view); + if (newX < (float) this.mRect.right || actionLeft + 1 == count) { + view.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(view.findViewById(R.id.text), true); + this.configureFocus(focus, this.mRect, actionLeft, 3); + break; + } + } + + return true; + } else if (newY < (float) keyboardTop && this.mEscapeNorthEnabled) { + this.escapeNorth(); + return false; + } else if (newX > (float) actionLeft) { + this.offsetRect(this.mRect, this.mActionButtonView); + this.configureFocus(focus, this.mRect, 0, 2); + return true; + } else { + this.mX = newX; + this.mY = newY; + this.offsetRect(this.mRect, this.mMainKeyboardView); + final float left = (float) this.mRect.left; + final float top = (float) this.mRect.top; + actionLeft = this.mMainKeyboardView.getNearestIndex(Float.valueOf(newX - left), Float.valueOf(newY - top)); + Key key = this.mMainKeyboardView.getKey(actionLeft); + this.configureFocus(focus, this.mRect, actionLeft, key, 0); + return true; + } + } + + public LeanbackKeyboardContainer.KeyFocus getCurrFocus() { + return this.mCurrKeyInfo; + } + + public int getCurrKeyCode() { + int var1 = 0; + Key var2 = this.getKey(this.mCurrKeyInfo.type, this.mCurrKeyInfo.index); + if (var2 != null) { + var1 = var2.codes[0]; + } + + return var1; + } + + public Button getGoButton() { + return this.mActionButtonView; + } + + public Key getKey(int var1, int var2) { + return var1 == 0 ? this.mMainKeyboardView.getKey(var2) : null; + } + + public boolean getNextFocusInDirection(int direction, LeanbackKeyboardContainer.KeyFocus startFocus, LeanbackKeyboardContainer.KeyFocus nextFocus) { + switch (startFocus.type) { + case KeyFocus.TYPE_MAIN: + Key key = this.getKey(startFocus.type, startFocus.index); + float var5 = (float) startFocus.rect.height() / 2.0F; + float var4 = (float) startFocus.rect.centerX(); + float var6 = (float) startFocus.rect.centerY(); + if (startFocus.code == 32) { + var4 = this.mX; + } + + if ((direction & 1) != 0) { + if ((key.edgeFlags & 1) == 0) { + var4 = (float) startFocus.rect.left - var5; + } + } else if ((direction & 4) != 0) { + if ((key.edgeFlags & 2) != 0) { + this.offsetRect(this.mRect, this.mActionButtonView); + var4 = (float) this.mRect.centerX(); + } else { + var4 = (float) startFocus.rect.right + var5; + } + } + + if ((direction & 8) != 0) { + var5 = (float) ((double) var6 - (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } else { + var5 = var6; + if ((direction & 2) != 0) { + var5 = (float) ((double) var6 + (double) startFocus.rect.height() * DIRECTION_STEP_MULTIPLIER); + } + } + + this.getPhysicalPosition(var4, var5, this.mTempPoint); + return this.getBestFocus(var4, var5, nextFocus); + case KeyFocus.TYPE_VOICE: + default: + break; + case KeyFocus.TYPE_ACTION: + this.offsetRect(this.mRect, this.mMainKeyboardView); + if ((direction & 1) != 0) { + return this.getBestFocus((float) this.mRect.right, null, nextFocus); + } + + if ((direction & 8) != 0) { + this.offsetRect(this.mRect, this.mSuggestions); + return this.getBestFocus((float) startFocus.rect.centerX(), (float) this.mRect.centerY(), nextFocus); + } + break; + case KeyFocus.TYPE_SUGGESTION: + if ((direction & 2) != 0) { + this.offsetRect(this.mRect, this.mMainKeyboardView); + return this.getBestFocus((float) startFocus.rect.centerX(), (float) this.mRect.top, nextFocus); + } + + if ((direction & 8) != 0) { + if (this.mEscapeNorthEnabled) { + this.escapeNorth(); + return true; + } + } else { + boolean var7; + if ((direction & 1) != 0) { + var7 = true; + } else { + var7 = false; + } + + boolean var12; + if ((direction & 4) != 0) { + var12 = true; + } else { + var12 = false; + } + + if (var7 || var12) { + this.offsetRect(this.mRect, this.mRootView); + MarginLayoutParams var11 = (MarginLayoutParams) this.mSuggestionsContainer.getLayoutParams(); + int var8 = this.mRect.left + var11.leftMargin; + int var9 = this.mRect.right - var11.rightMargin; + int var10 = startFocus.index; + byte var13; + if (var7) { + var13 = -1; + } else { + var13 = 1; + } + + direction = var10 + var13; + View var14 = this.mSuggestions.getChildAt(direction); + if (var14 != null) { + this.offsetRect(this.mRect, var14); + if (this.mRect.left < var8 && this.mRect.right > var9) { + this.mRect.left = var8; + this.mRect.right = var9; + } else if (this.mRect.left < var8) { + this.mRect.right = this.mRect.width() + var8; + this.mRect.left = var8; + } else if (this.mRect.right > var9) { + this.mRect.left = var9 - this.mRect.width(); + this.mRect.right = var9; + } + + var14.requestFocus(); + LeanbackUtils.sendAccessibilityEvent(var14.findViewById(R.id.text), true); + this.configureFocus(nextFocus, this.mRect, direction, 3); + return true; + } + } + } + } + + return true; + } + + public CharSequence getSuggestionText(int var1) { + Object var3 = null; + CharSequence var2 = (CharSequence) var3; + if (var1 >= 0) { + var2 = (CharSequence) var3; + if (var1 < this.mSuggestions.getChildCount()) { + Button var4 = (Button) this.mSuggestions.getChildAt(var1).findViewById(R.id.text); + var2 = (CharSequence) var3; + if (var4 != null) { + var2 = var4.getText(); + } + } + } + + return var2; + } + + public int getTouchState() { + return this.mTouchState; + } + + public RelativeLayout getView() { + return this.mRootView; + } + + public boolean isCapsLockOn() { + return this.mMainKeyboardView.getShiftState() == 2; + } + + public boolean isCurrKeyShifted() { + return this.mMainKeyboardView.isShifted(); + } + + public boolean isMiniKeyboardOnScreen() { + return this.mMainKeyboardView.isMiniKeyboardOnScreen(); + } + + public boolean isVoiceEnabled() { + return this.mVoiceEnabled; + } + + public boolean isVoiceVisible() { + return this.mVoiceButtonView.getVisibility() == 0; + } + + public void onInitInputView() { + this.resetFocusCursor(); + this.mSelector.setVisibility(View.VISIBLE); + } + + public boolean onKeyLongPress() { + int var1 = this.mCurrKeyInfo.code; + if (var1 == -1) { + this.onToggleCapsLock(); + this.setTouchState(0); + return true; + } else if (var1 == 32) { + this.switchToNextKeyboard(); + this.setTouchState(0); + return true; + } else { + if (this.mCurrKeyInfo.type == 0) { + this.mMainKeyboardView.onKeyLongPress(); + if (this.mMainKeyboardView.isMiniKeyboardOnScreen()) { + this.mMiniKbKeyIndex = this.mCurrKeyInfo.index; + this.moveFocusToIndex(this.mMainKeyboardView.getBaseMiniKbIndex(), 0); + return true; + } + } + + return false; + } + } + + public void onModeChangeClick() { + this.dismissMiniKeyboard(); + if (this.mMainKeyboardView.getKeyboard().equals(this.mSymKeyboard)) { + this.mMainKeyboardView.setKeyboard(this.mInitialMainKeyboard); + } else { + this.mMainKeyboardView.setKeyboard(this.mSymKeyboard); + } + } + + public void onPeriodEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters && !this.mCapWords && !this.mCapSentences) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters || this.mCapWords || this.mCapSentences) { + this.setShiftState(1); + return; + } + + } + + public void onShiftClick() { + byte var1; + if (this.mMainKeyboardView.isShifted()) { + var1 = 0; + } else { + var1 = 1; + } + + this.setShiftState(var1); + } + + public void onShiftDoubleClick(boolean var1) { + byte var2; + if (var1) { + var2 = 0; + } else { + var2 = 2; + } + + this.setShiftState(var2); + } + + public void onSpaceEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters && !this.mCapWords) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters || this.mCapWords) { + this.setShiftState(1); + return; + } + + } + + public void onStartInput(EditorInfo var1) { + this.setImeOptions(this.mContext.getResources(), var1); + this.mVoiceOn = false; + } + + @TargetApi(17) + public void onStartInputView() { + this.clearSuggestions(); + LayoutParams params = (LayoutParams) this.mKeyboardsContainer.getLayoutParams(); + if (this.mSuggestionsEnabled) { + params.removeRule(10); + this.mSuggestionsContainer.setVisibility(View.VISIBLE); + this.mSuggestionsBg.setVisibility(View.VISIBLE); + } else { + params.addRule(10); + this.mSuggestionsContainer.setVisibility(View.GONE); + this.mSuggestionsBg.setVisibility(View.GONE); + } + + this.mKeyboardsContainer.setLayoutParams(params); + this.mMainKeyboardView.setKeyboard(this.mInitialMainKeyboard); + this.mVoiceButtonView.setMicEnabled(this.mVoiceEnabled); + this.resetVoice(); + this.dismissMiniKeyboard(); + if (!TextUtils.isEmpty(this.mEnterKeyText)) { + this.mActionButtonView.setText(this.mEnterKeyText); + this.mActionButtonView.setContentDescription(this.mEnterKeyText); + } else { + this.mActionButtonView.setText(this.mEnterKeyTextResId); + this.mActionButtonView.setContentDescription(this.mContext.getString(this.mEnterKeyTextResId)); + } + + if (this.mCapCharacters) { + this.setShiftState(2); + } else if (!this.mCapSentences && !this.mCapWords) { + this.setShiftState(0); + } else { + this.setShiftState(1); + } + } + + public void onTextEntry() { + if (this.mMainKeyboardView.isShifted()) { + if (!this.isCapsLockOn() && !this.mCapCharacters) { + this.setShiftState(0); + } + } else if (this.isCapsLockOn() || this.mCapCharacters) { + this.setShiftState(2); + } + + if (this.dismissMiniKeyboard()) { + this.moveFocusToIndex(this.mMiniKbKeyIndex, 0); + } + + } + + public void onVoiceClick() { + if (this.mVoiceButtonView != null) { + this.mVoiceButtonView.onClick(); + } + + } + + public void resetFocusCursor() { + this.offsetRect(this.mRect, this.mMainKeyboardView); + this.mX = (float) ((double) this.mRect.left + (double) this.mRect.width() * 0.45D); + this.mY = (float) ((double) this.mRect.top + (double) this.mRect.height() * 0.375D); + this.getBestFocus(this.mX, this.mY, this.mTempKeyInfo); + this.setKbFocus(this.mTempKeyInfo, true, false); + this.setTouchStateInternal(0); + this.mSelectorAnimator.reverse(); + this.mSelectorAnimator.end(); + } + + public void resetVoice() { + this.mMainKeyboardView.setAlpha(this.mAlphaIn); + this.mActionButtonView.setAlpha(this.mAlphaIn); + this.mVoiceButtonView.setAlpha(this.mAlphaOut); + this.mMainKeyboardView.setVisibility(View.VISIBLE); + this.mActionButtonView.setVisibility(View.VISIBLE); + this.mVoiceButtonView.setVisibility(View.INVISIBLE); + } + + public void setDismissListener(LeanbackKeyboardContainer.DismissListener listener) { + this.mDismissListener = listener; + } + + public void setFocus(LeanbackKeyboardContainer.KeyFocus focus) { + this.setKbFocus(focus, false, true); + } + + public void setSelectorToFocus(Rect rect, boolean overestimateWidth, boolean overestimateHeight, boolean animate) { + if (this.mSelector.getWidth() != 0 && this.mSelector.getHeight() != 0 && rect.width() != 0 && rect.height() != 0) { + final float width = (float) rect.width(); + final float height = (float) rect.height(); + float heightOver = height; + if (overestimateHeight) { + heightOver = height * this.mOverestimate; + } + + float widthOver = width; + if (overestimateWidth) { + widthOver = width * this.mOverestimate; + } + + float deltaY = heightOver; + float deltaX = widthOver; + if ((double) (Math.max(widthOver, heightOver) / Math.min(widthOver, heightOver)) < 1.1D) { + deltaY = Math.max(widthOver, heightOver); + deltaX = deltaY; + } + + final float x = rect.exactCenterX() - deltaX / 2.0F; + final float y = rect.exactCenterY() - deltaY / 2.0F; + this.mSelectorAnimation.cancel(); + if (animate) { + this.mSelectorAnimation.reset(); + this.mSelectorAnimation.setAnimationBounds(x, y, deltaX, deltaY); + this.mSelector.startAnimation(this.mSelectorAnimation); + } else { + this.mSelectorAnimation.setValues(x, y, deltaX, deltaY); + } + } + } + + public void setTouchState(int var1) { + switch (var1) { + case 0: + if (this.mTouchState == 2 || this.mTouchState == 3) { + this.mSelectorAnimator.reverse(); + } + break; + case 1: + if (this.mTouchState == 3) { + this.mSelectorAnimator.reverse(); + } else if (this.mTouchState == 2) { + this.mSelectorAnimator.reverse(); + } + break; + case 2: + if (this.mTouchState == 0 || this.mTouchState == 1) { + this.mSelectorAnimator.start(); + } + break; + case 3: + if (this.mTouchState == 0 || this.mTouchState == 1) { + this.mSelectorAnimator.start(); + } + } + + this.setTouchStateInternal(var1); + this.setKbFocus(this.mCurrKeyInfo, true, true); + } + + public void setVoiceListener(LeanbackKeyboardContainer.VoiceListener var1) { + this.mVoiceListener = var1; + } + + public void startVoiceRecording() { + if (this.mVoiceEnabled) { + if (!this.mVoiceKeyDismissesEnabled) { + this.mVoiceAnimator.startEnterAnimation(); + return; + } + + this.mDismissListener.onDismiss(true); + } + + } + + public void switchToNextKeyboard() { + LeanbackKeyboardView var1 = this.mMainKeyboardView; + Keyboard var2 = this.mKeyboardManager.getNextKeyboard(); + this.mInitialMainKeyboard = var2; + var1.setKeyboard(var2); + } + + public void updateAddonKeyboard() { + KeyboardManager var1 = new KeyboardManager(this.mContext, this.mAbcKeyboard); + this.mKeyboardManager = var1; + this.mInitialMainKeyboard = var1.getNextKeyboard(); + } + + public void updateSuggestions(ArrayList var1) { + int var2 = this.mSuggestions.getChildCount(); + int var3 = var1.size(); + if (var3 < var2) { + this.mSuggestions.removeViews(var3, var2 - var3); + } else if (var3 > var2) { + while (var2 < var3) { + View var4 = this.mContext.getLayoutInflater().inflate(R.layout.candidate, (ViewGroup) null); + this.mSuggestions.addView(var4); + ++var2; + } + } + + for (var2 = 0; var2 < var3; ++var2) { + Button var5 = (Button) this.mSuggestions.getChildAt(var2).findViewById(R.id.text); + var5.setText((CharSequence) var1.get(var2)); + var5.setContentDescription((CharSequence) var1.get(var2)); + } + + if (this.getCurrFocus().type == 3) { + this.resetFocusCursor(); + } + + } + + public interface DismissListener { + void onDismiss(boolean var1); + } + + public static class KeyFocus { + public static final int TYPE_ACTION = 2; + public static final int TYPE_INVALID = -1; + public static final int TYPE_MAIN = 0; + public static final int TYPE_SUGGESTION = 3; + public static final int TYPE_VOICE = 1; + int code; + int index; + CharSequence label; + final Rect rect = new Rect(); + int type = -1; + + public boolean equals(Object var1) { + if (this != var1) { + if (var1 == null || this.getClass() != var1.getClass()) { + return false; + } + + LeanbackKeyboardContainer.KeyFocus var2 = (LeanbackKeyboardContainer.KeyFocus) var1; + if (this.code != var2.code) { + return false; + } + + if (this.index != var2.index) { + return false; + } + + if (this.type != var2.type) { + return false; + } + + label31: + { + if (this.label != null) { + if (this.label.equals(var2.label)) { + break label31; + } + } else if (var2.label == null) { + break label31; + } + + return false; + } + + if (!this.rect.equals(var2.rect)) { + return false; + } + } + + return true; + } + + public int hashCode() { + int var2 = this.rect.hashCode(); + int var3 = this.index; + int var4 = this.type; + int var5 = this.code; + int var1; + if (this.label != null) { + var1 = this.label.hashCode(); + } else { + var1 = 0; + } + + return (((var2 * 31 + var3) * 31 + var4) * 31 + var5) * 31 + var1; + } + + public void set(LeanbackKeyboardContainer.KeyFocus var1) { + this.index = var1.index; + this.type = var1.type; + this.code = var1.code; + this.label = var1.label; + this.rect.set(var1.rect); + } + + public String toString() { + StringBuilder var1 = new StringBuilder(); + var1.append("[type: ").append(this.type).append(", index: ").append(this.index).append(", code: ").append(this.code).append(", label: " + + "").append(this.label).append(", rect: ").append(this.rect).append("]"); + return var1.toString(); + } + } + + private class ScaleAnimation extends Animation { + private float mEndHeight; + private float mEndWidth; + private float mEndX; + private float mEndY; + private final android.view.ViewGroup.LayoutParams mParams; + private float mStartHeight; + private float mStartWidth; + private float mStartX; + private float mStartY; + private final View mView; + + public ScaleAnimation(FrameLayout var2) { + this.mView = var2; + this.mParams = var2.getLayoutParams(); + this.setDuration(150L); + this.setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator); + } + + protected void applyTransformation(float var1, Transformation var2) { + if (var1 == 0.0F) { + this.mStartX = this.mView.getX(); + this.mStartY = this.mView.getY(); + this.mStartWidth = (float) this.mParams.width; + this.mStartHeight = (float) this.mParams.height; + } else { + this.setValues((this.mEndX - this.mStartX) * var1 + this.mStartX, (this.mEndY - this.mStartY) * var1 + this.mStartY, (float) ((int) + ((this.mEndWidth - this.mStartWidth) * var1 + this.mStartWidth)), (float) ((int) ((this.mEndHeight - this.mStartHeight) * + var1 + this.mStartHeight))); + } + } + + public void setAnimationBounds(float var1, float var2, float var3, float var4) { + this.mEndX = var1; + this.mEndY = var2; + this.mEndWidth = var3; + this.mEndHeight = var4; + } + + public void setValues(float var1, float var2, float var3, float var4) { + this.mView.setX(var1); + this.mView.setY(var2); + this.mParams.width = (int) var3; + this.mParams.height = (int) var4; + this.mView.setLayoutParams(this.mParams); + this.mView.requestLayout(); + } + } + + private class VoiceIntroAnimator { + private AnimatorListener mEnterListener; + private AnimatorListener mExitListener; + private ValueAnimator mValueAnimator; + + public VoiceIntroAnimator(AnimatorListener var2, AnimatorListener var3) { + this.mEnterListener = var2; + this.mExitListener = var3; + this.mValueAnimator = ValueAnimator.ofFloat(new float[]{LeanbackKeyboardContainer.this.mAlphaOut, LeanbackKeyboardContainer.this.mAlphaIn}); + this.mValueAnimator.setDuration((long) LeanbackKeyboardContainer.this.mVoiceAnimDur); + this.mValueAnimator.setInterpolator(new AccelerateInterpolator()); + } + + private void start(final boolean var1) { + this.mValueAnimator.cancel(); + this.mValueAnimator.removeAllListeners(); + ValueAnimator var3 = this.mValueAnimator; + AnimatorListener var2; + if (var1) { + var2 = this.mEnterListener; + } else { + var2 = this.mExitListener; + } + + var3.addListener(var2); + this.mValueAnimator.removeAllUpdateListeners(); + this.mValueAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator var1x) { + float var2 = (Float) VoiceIntroAnimator.this.mValueAnimator.getAnimatedValue(); + float var4 = LeanbackKeyboardContainer.this.mAlphaIn + LeanbackKeyboardContainer.this.mAlphaOut - var2; + float var3; + if (var1) { + var3 = var4; + } else { + var3 = var2; + } + + if (var1) { + var4 = var2; + } + + LeanbackKeyboardContainer.this.mMainKeyboardView.setAlpha(var3); + LeanbackKeyboardContainer.this.mActionButtonView.setAlpha(var3); + LeanbackKeyboardContainer.this.mVoiceButtonView.setAlpha(var4); + if (var2 == LeanbackKeyboardContainer.this.mAlphaOut) { + if (!var1) { + LeanbackKeyboardContainer.this.mMainKeyboardView.setVisibility(View.VISIBLE); + LeanbackKeyboardContainer.this.mActionButtonView.setVisibility(View.VISIBLE); + return; + } + + LeanbackKeyboardContainer.this.mVoiceButtonView.setVisibility(View.VISIBLE); + } else if (var2 == LeanbackKeyboardContainer.this.mAlphaIn) { + if (var1) { + LeanbackKeyboardContainer.this.mMainKeyboardView.setVisibility(View.INVISIBLE); + LeanbackKeyboardContainer.this.mActionButtonView.setVisibility(View.INVISIBLE); + return; + } + + LeanbackKeyboardContainer.this.mVoiceButtonView.setVisibility(View.INVISIBLE); + return; + } + + } + }); + this.mValueAnimator.start(); + } + + void startEnterAnimation() { + if (!LeanbackKeyboardContainer.this.isVoiceVisible() && !this.mValueAnimator.isRunning()) { + this.start(true); + } + + } + + void startExitAnimation() { + if (LeanbackKeyboardContainer.this.isVoiceVisible() && !this.mValueAnimator.isRunning()) { + this.start(false); + } + + } + } + + public interface VoiceListener { + void onVoiceResult(String var1); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java new file mode 100644 index 0000000..a150608 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardController.java @@ -0,0 +1,822 @@ +package com.google.android.leanback.ime; + +import android.graphics.PointF; +import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard.Key; +import android.os.Handler; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnHoverListener; +import android.view.View.OnLayoutChangeListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.RelativeLayout; +import com.google.android.pano.util.TouchNavSpaceTracker; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; + +public class LeanbackKeyboardController implements LeanbackKeyboardContainer.VoiceListener, LeanbackKeyboardContainer.DismissListener, OnTouchListener, OnHoverListener, Runnable { + public static final int CLICK_MOVEMENT_BLOCK_DURATION_MS = 500; + private static final boolean DEBUG = false; + private static final int KEY_CHANGE_HISTORY_SIZE = 10; + private static final long KEY_CHANGE_REVERT_TIME_MS = 100L; + private static final String TAG = "LbKbController"; + private boolean clickConsumed; + private long lastClickTime; + private LeanbackKeyboardContainer mContainer; + private InputMethodService mContext; + private LeanbackKeyboardController.DoubleClickDetector mDoubleClickDetector; + private LeanbackKeyboardContainer.KeyFocus mDownFocus; + private Handler mHandler; + private LeanbackKeyboardController.InputListener mInputListener; + ArrayList mKeyChangeHistory; + private LeanbackKeyboardContainer.KeyFocus mKeyDownKeyFocus; + private boolean mKeyDownReceived; + private boolean mLongPressHandled; + private int mMoveCount; + private OnLayoutChangeListener mOnLayoutChangeListener; + public float mResizeSquareDistance; + private TouchNavSpaceTracker mSpaceTracker; + private LeanbackKeyboardContainer.KeyFocus mTempFocus; + private PointF mTempPoint; + private LeanbackKeyboardController.TouchEventListener mTouchEventListener; + private long prevTime; + + public LeanbackKeyboardController(InputMethodService var1, LeanbackKeyboardController.InputListener var2) { + this(var1, var2, new TouchNavSpaceTracker(), new LeanbackKeyboardContainer(var1)); + } + + LeanbackKeyboardController(InputMethodService var1, LeanbackKeyboardController.InputListener var2, TouchNavSpaceTracker var3, LeanbackKeyboardContainer var4) { + this.mDoubleClickDetector = new LeanbackKeyboardController.DoubleClickDetector(); + this.mOnLayoutChangeListener = new OnLayoutChangeListener() { + public void onLayoutChange(View var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9) { + var2 = var4 - var2; + var3 = var5 - var3; + if (var2 > 0 && var3 > 0) { + if (LeanbackKeyboardController.this.mSpaceTracker != null) { + LeanbackKeyboardController.this.mSpaceTracker.setPixelSize((float)var2, (float)var3); + } + + if (var2 != var8 - var6 || var3 != var9 - var7) { + LeanbackKeyboardController.this.initInputView(); + } + } + + } + }; + this.mTouchEventListener = new LeanbackKeyboardController.TouchEventListener(); + this.mDownFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mTempFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mKeyChangeHistory = new ArrayList(11); + this.mTempPoint = new PointF(); + this.mKeyDownReceived = false; + this.mLongPressHandled = false; + this.mContext = var1; + this.mResizeSquareDistance = var1.getResources().getDimension(R.dimen.resize_move_distance); + this.mResizeSquareDistance *= this.mResizeSquareDistance; + this.mInputListener = var2; + this.setSpaceTracker(var3); + this.setKeyboardContainer(var4); + this.mContainer.setVoiceListener(this); + this.mContainer.setDismissListener(this); + } + + private boolean applyLETVFixesDown(int var1) { + switch(var1) { + case 82: + case 85: + case 89: + case 90: + return true; + default: + return false; + } + } + + private boolean applyLETVFixesUp(int var1) { + switch(var1) { + case 82: + this.mContainer.switchToNextKeyboard(); + break; + case 85: + this.fakeKeyIndex(0, 2); + break; + case 89: + this.fakeKeyCode(-5); + break; + case 90: + this.fakeKeyCode(32); + break; + default: + return false; + } + + return true; + } + + private void beginLongClickCountdown() { + this.clickConsumed = false; + Handler var2 = this.mHandler; + Handler var1 = var2; + if (var2 == null) { + var1 = new Handler(); + this.mHandler = var1; + } + + var1.removeCallbacks(this); + var1.postDelayed(this, (long)1000); + } + + private void clearKeyIfNecessary() { + ++this.mMoveCount; + if (this.mMoveCount >= 3) { + this.mMoveCount = 0; + this.mKeyDownKeyFocus = null; + } + + } + + private void commitKey() { + this.commitKey(this.mContainer.getCurrFocus()); + } + + private void commitKey(LeanbackKeyboardContainer.KeyFocus var1) { + if (this.mContainer != null && var1 != null) { + switch(var1.type) { + case 1: + this.mContainer.onVoiceClick(); + return; + case 2: + this.mInputListener.onEntry(5, 0, (CharSequence)null); + return; + case 3: + this.mInputListener.onEntry(2, 0, this.mContainer.getSuggestionText(var1.index)); + return; + default: + Key var2 = this.mContainer.getKey(var1.type, var1.index); + if (var2 != null) { + this.handleCommitKeyboardKey(var2.codes[0], var2.label); + return; + } + } + } + + } + + private void fakeClickDown() { + this.mContainer.setTouchState(3); + } + + private void fakeClickUp() { + LeanbackKeyboardContainer var1 = this.mContainer; + this.commitKey(var1.getCurrFocus()); + var1.setTouchState(0); + } + + private void fakeKeyCode(int var1) { + this.mContainer.getCurrFocus().code = var1; + this.handleCommitKeyboardKey(var1, (CharSequence)null); + } + + private void fakeKeyIndex(int var1, int var2) { + LeanbackKeyboardContainer.KeyFocus var3 = this.mContainer.getCurrFocus(); + var3.index = var1; + var3.type = var2; + this.commitKey(var3); + } + + private void fakeLongClickDown() { + LeanbackKeyboardContainer var1 = this.mContainer; + var1.onKeyLongPress(); + var1.setTouchState(3); + } + + private void fakeLongClickUp() { + this.mContainer.setTouchState(0); + } + + private PointF getBestSnapPosition(PointF var1, long var2) { + if (this.mKeyChangeHistory.size() <= 1) { + return var1; + } else { + int var4 = 0; + + PointF var5; + while(true) { + var5 = var1; + if (var4 >= this.mKeyChangeHistory.size() - 1) { + break; + } + + LeanbackKeyboardController.KeyChange var6 = (LeanbackKeyboardController.KeyChange)this.mKeyChangeHistory.get(var4); + if (var2 - ((LeanbackKeyboardController.KeyChange)this.mKeyChangeHistory.get(var4 + 1)).time < 100L) { + var5 = var6.position; + this.mKeyChangeHistory.clear(); + this.mKeyChangeHistory.add(new LeanbackKeyboardController.KeyChange(var2, var5)); + break; + } + + ++var4; + } + + return var5; + } + } + + private PointF getCurrentKeyPosition() { + if (this.mContainer != null) { + LeanbackKeyboardContainer.KeyFocus var1 = this.mContainer.getCurrFocus(); + return new PointF((float)var1.rect.centerX(), (float)var1.rect.centerY()); + } else { + return null; + } + } + + private PointF getRelativePosition(View var1, MotionEvent var2) { + int[] var5 = new int[2]; + var1.getLocationOnScreen(var5); + float var3 = var2.getRawX(); + float var4 = var2.getRawY(); + return new PointF(var3 - (float)var5[0], var4 - (float)var5[1]); + } + + private int getSimplifiedKey(int var1) { + int var2 = 23; + if (var1 != 23) { + byte var3 = 66; + var2 = var3; + if (var1 != 66) { + var2 = var3; + if (var1 != 160) { + var2 = var1; + if (var1 == 96) { + var2 = var3; + } + } + } + } + + var1 = var2; + if (var2 == 97) { + var1 = 4; + } + + return var1; + } + + private void handleCommitKeyboardKey(int var1, CharSequence var2) { + switch(var1) { + case -8: + this.mContainer.dismissMiniKeyboard(); + return; + case -7: + this.mContainer.startVoiceRecording(); + return; + case -6: + this.mContainer.onShiftDoubleClick(this.mContainer.isCapsLockOn()); + return; + case -5: + this.mInputListener.onEntry(1, 0, (CharSequence)null); + return; + case -4: + this.mInputListener.onEntry(4, 0, (CharSequence)null); + return; + case -3: + this.mInputListener.onEntry(3, 0, (CharSequence)null); + return; + case -2: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "mode change"); + } + + this.mContainer.onModeChangeClick(); + return; + case -1: + if (Log.isLoggable("LbKbController", Log.DEBUG)) { + Log.d("LbKbController", "shift"); + } + + this.mContainer.onShiftClick(); + return; + case 32: + this.mInputListener.onEntry(0, var1, " "); + this.mContainer.onSpaceEntry(); + return; + case 46: + this.mInputListener.onEntry(0, var1, var2); + this.mContainer.onPeriodEntry(); + return; + default: + this.mInputListener.onEntry(0, var1, var2); + this.mContainer.onTextEntry(); + if (this.mContainer.isMiniKeyboardOnScreen()) { + this.mContainer.dismissMiniKeyboard(); + } + + } + } + + private boolean handleKeyDownEvent(int var1, int var2) { + var1 = this.getSimplifiedKey(var1); + boolean var3; + boolean var4; + if (var1 == 4) { + this.mContainer.cancelVoiceRecording(); + var3 = false; + } else if (this.mContainer.isVoiceVisible()) { + if (var1 == 22 || var1 == 23 || var1 == 66) { + this.mContainer.cancelVoiceRecording(); + } + + var3 = true; + } else { + var4 = true; + var3 = var4; + switch(var1) { + case 19: + var3 = this.onDirectionalMove(8); + break; + case 20: + var3 = this.onDirectionalMove(2); + break; + case 21: + var3 = this.onDirectionalMove(1); + break; + case 22: + var3 = this.onDirectionalMove(4); + break; + case 23: + case 66: + if (var2 == 0) { + this.mMoveCount = 0; + this.mKeyDownKeyFocus = new LeanbackKeyboardContainer.KeyFocus(); + this.mKeyDownKeyFocus.set(this.mContainer.getCurrFocus()); + } else if (var2 == 1 && this.handleKeyLongPress(var1)) { + this.mKeyDownKeyFocus = null; + } + + var3 = var4; + if (this.isKeyHandledOnKeyDown(this.mContainer.getCurrKeyCode())) { + this.commitKey(); + var3 = var4; + } + break; + case 99: + this.handleCommitKeyboardKey(-5, (CharSequence)null); + var3 = var4; + break; + case 100: + this.handleCommitKeyboardKey(32, (CharSequence)null); + var3 = var4; + break; + case 102: + this.handleCommitKeyboardKey(-3, (CharSequence)null); + var3 = var4; + break; + case 103: + this.handleCommitKeyboardKey(-4, (CharSequence)null); + var3 = var4; + case 106: + case 107: + break; + default: + var3 = false; + } + } + + var4 = var3; + if (!var3) { + var4 = this.applyLETVFixesDown(var1); + } + + return var4; + } + + private boolean handleKeyLongPress(int var1) { + boolean var2; + if (this.isEnterKey(var1) && this.mContainer.onKeyLongPress()) { + var2 = true; + } else { + var2 = false; + } + + this.mLongPressHandled = var2; + if (this.mContainer.isMiniKeyboardOnScreen()) { + Log.d("LbKbController", "mini keyboard shown after long press"); + } + + return this.mLongPressHandled; + } + + private boolean handleKeyUpEvent(int var1, long var2) { + var1 = this.getSimplifiedKey(var1); + boolean var4; + boolean var5; + if (var1 == 4) { + var4 = false; + } else if (this.mContainer.isVoiceVisible()) { + var4 = true; + } else { + var5 = true; + var4 = var5; + switch(var1) { + case 19: + case 20: + case 21: + case 22: + this.clearKeyIfNecessary(); + var4 = var5; + break; + case 23: + case 66: + if (this.mContainer.getCurrKeyCode() == -1) { + this.mDoubleClickDetector.addEvent(var2); + var4 = var5; + } else { + var4 = var5; + if (!this.isKeyHandledOnKeyDown(this.mContainer.getCurrKeyCode())) { + this.commitKey(this.mKeyDownKeyFocus); + var4 = var5; + } + } + case 99: + case 100: + case 102: + case 103: + break; + case 106: + this.handleCommitKeyboardKey(-2, (CharSequence)null); + var4 = var5; + break; + case 107: + this.handleCommitKeyboardKey(-6, (CharSequence)null); + var4 = var5; + break; + default: + var4 = false; + } + } + + var5 = var4; + if (!var4) { + var5 = this.applyLETVFixesUp(var1); + } + + return var5; + } + + private void initInputView() { + this.mContainer.onInitInputView(); + this.updatePositionToCurrentFocus(); + } + + private boolean isCallAllowed(int var1) { + long var2 = System.currentTimeMillis(); + if (this.prevTime != 0L && var2 - this.prevTime <= (long)(var1 * 3)) { + if (var2 - this.prevTime > (long)var1) { + this.prevTime = 0L; + return true; + } + } else { + this.prevTime = var2; + } + + return false; + } + + private boolean isDoubleClick() { + long var1 = System.currentTimeMillis(); + long var3 = this.lastClickTime; + if (this.lastClickTime != 0L && var1 - var3 <= (long)300) { + return true; + } else { + this.lastClickTime = var1; + return false; + } + } + + private boolean isEnterKey(int var1) { + var1 = this.getSimplifiedKey(var1); + return var1 == 23 || var1 == 66; + } + + private boolean isKeyHandledOnKeyDown(int var1) { + return var1 == -5 || var1 == -3 || var1 == -4; + } + + private void moveSelectorToPoint(float var1, float var2) { + LeanbackKeyboardContainer var3 = this.mContainer; + LeanbackKeyboardContainer.KeyFocus var4 = this.mTempFocus; + var3.getBestFocus(new Float(var1), new Float(var2), var4); + this.mContainer.setFocus(this.mTempFocus); + var3 = this.mContainer; + Rect var5 = this.mTempFocus.rect; + var3.alignSelector((float)var5.centerX(), (float)var5.centerY(), false); + } + + private boolean onDirectionalMove(int var1) { + if (this.mContainer.getNextFocusInDirection(var1, this.mDownFocus, this.mTempFocus)) { + this.mContainer.setFocus(this.mTempFocus); + this.mDownFocus.set(this.mTempFocus); + this.clearKeyIfNecessary(); + } + + return true; + } + + private void performBestSnap(long var1) { + LeanbackKeyboardContainer.KeyFocus var3 = this.mContainer.getCurrFocus(); + this.mTempPoint.x = (float)var3.rect.centerX(); + this.mTempPoint.y = (float)var3.rect.centerY(); + PointF var4 = this.getBestSnapPosition(this.mTempPoint, var1); + this.mContainer.getBestFocus(var4.x, var4.y, this.mTempFocus); + this.mContainer.setFocus(this.mTempFocus); + this.updatePositionToCurrentFocus(); + } + + private void setKeyState(int var1, boolean var2) { + LeanbackKeyboardContainer var3 = this.mContainer; + LeanbackKeyboardContainer.KeyFocus var4 = var3.getCurrFocus(); + var4.index = var1; + var4.type = 0; + byte var5; + if (var2) { + var5 = 3; + } else { + var5 = 0; + } + + var3.setTouchState(var5); + } + + private void updatePositionToCurrentFocus() { + PointF var1 = this.getCurrentKeyPosition(); + if (var1 != null && this.mSpaceTracker != null) { + this.mSpaceTracker.setPixelPosition(var1.x, var1.y); + } + + } + + public boolean areSuggestionsEnabled() { + return this.mContainer != null ? this.mContainer.areSuggestionsEnabled() : false; + } + + public boolean enableAutoEnterSpace() { + return this.mContainer != null ? this.mContainer.enableAutoEnterSpace() : false; + } + + public View getView() { + if (this.mContainer != null) { + RelativeLayout var1 = this.mContainer.getView(); + var1.setClickable(true); + var1.setOnTouchListener(this); + var1.setOnHoverListener(this); + Button var2 = this.mContainer.getGoButton(); + var2.setOnTouchListener(this); + var2.setOnHoverListener(this); + var2.setTag("Go"); + return var1; + } else { + return null; + } + } + + public void onDismiss(boolean var1) { + if (var1) { + this.mInputListener.onEntry(8, 0, (CharSequence)null); + } else { + this.mInputListener.onEntry(7, 0, (CharSequence)null); + } + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + return this.mSpaceTracker != null && this.mContext != null && this.mContext.isInputViewShown() && this.mSpaceTracker.onGenericMotionEvent(var1); + } + + public boolean onHover(View var1, MotionEvent var2) { + boolean var4 = this.isCallAllowed(300); + boolean var3 = var4; + if (var4) { + if (var2.getAction() == 7) { + PointF var5 = this.getRelativePosition(this.mContainer.getView(), var2); + this.moveSelectorToPoint(var5.x, var5.y); + } + + var3 = true; + } + + return var3; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + this.mDownFocus.set(this.mContainer.getCurrFocus()); + if (this.mSpaceTracker != null && this.mSpaceTracker.onKeyDown(var1, var2)) { + return true; + } else { + if (this.isEnterKey(var1)) { + this.mKeyDownReceived = true; + if (var2.getRepeatCount() == 0) { + this.mContainer.setTouchState(3); + } + } + + return this.handleKeyDownEvent(var1, var2.getRepeatCount()); + } + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (this.mSpaceTracker != null && this.mSpaceTracker.onKeyUp(var1, var2)) { + return true; + } else { + if (this.isEnterKey(var1)) { + if (!this.mKeyDownReceived || this.mLongPressHandled) { + this.mLongPressHandled = false; + return true; + } + + this.mKeyDownReceived = false; + if (this.mContainer.getTouchState() == 3) { + this.mContainer.setTouchState(1); + } + } + + return this.handleKeyUpEvent(var1, var2.getEventTime()); + } + } + + public void onStartInput(EditorInfo var1) { + if (this.mContainer != null) { + this.mContainer.onStartInput(var1); + this.initInputView(); + } + + } + + public void onStartInputView() { + this.mKeyDownReceived = false; + if (this.mContainer != null) { + this.mContainer.onStartInputView(); + } + + this.mDoubleClickDetector.reset(); + } + + public boolean onTouch(View var1, MotionEvent var2) { + Object var3 = var1.getTag(); + if (var3 != null && "Go".equals(var3)) { + this.fakeKeyIndex(0, 2); + } else { + switch(var2.getAction()) { + case 0: + this.moveSelectorToPoint(var2.getX(), var2.getY()); + this.fakeClickDown(); + this.beginLongClickCountdown(); + break; + case 1: + if (!this.clickConsumed) { + this.clickConsumed = true; + if (this.isDoubleClick()) { + this.mContainer.onKeyLongPress(); + break; + } + + this.fakeClickUp(); + } + + this.fakeLongClickUp(); + break; + default: + return false; + } + } + + return true; + } + + public void onVoiceResult(String var1) { + this.mInputListener.onEntry(6, 0, var1); + } + + public void run() { + if (!this.clickConsumed) { + this.clickConsumed = true; + this.fakeLongClickDown(); + } + } + + public void setKeyboardContainer(LeanbackKeyboardContainer var1) { + this.mContainer = var1; + var1.getView().addOnLayoutChangeListener(this.mOnLayoutChangeListener); + } + + public void setSpaceTracker(TouchNavSpaceTracker var1) { + this.mSpaceTracker = var1; + var1.setLPFEnabled(true); + var1.setKeyEventListener(this.mTouchEventListener); + } + + public void updateAddonKeyboard() { + this.mContainer.updateAddonKeyboard(); + } + + public void updateSuggestions(ArrayList var1) { + if (this.mContainer != null) { + this.mContainer.updateSuggestions(var1); + } + + } + + private class DoubleClickDetector { + final long DOUBLE_CLICK_TIMEOUT_MS; + boolean mFirstClickShiftLocked; + long mFirstClickTime; + + private DoubleClickDetector() { + this.DOUBLE_CLICK_TIMEOUT_MS = 200L; + this.mFirstClickTime = 0L; + } + + public void addEvent(long var1) { + if (var1 - this.mFirstClickTime > 200L) { + this.mFirstClickTime = var1; + this.mFirstClickShiftLocked = LeanbackKeyboardController.this.mContainer.isCapsLockOn(); + LeanbackKeyboardController.this.commitKey(); + } else { + LeanbackKeyboardController.this.mContainer.onShiftDoubleClick(this.mFirstClickShiftLocked); + this.reset(); + } + } + + public void reset() { + this.mFirstClickTime = 0L; + } + } + + public interface InputListener { + int ENTRY_TYPE_ACTION = 5; + int ENTRY_TYPE_BACKSPACE = 1; + int ENTRY_TYPE_DISMISS = 7; + int ENTRY_TYPE_LEFT = 3; + int ENTRY_TYPE_RIGHT = 4; + int ENTRY_TYPE_STRING = 0; + int ENTRY_TYPE_SUGGESTION = 2; + int ENTRY_TYPE_VOICE = 6; + int ENTRY_TYPE_VOICE_DISMISS = 8; + + void onEntry(int var1, int var2, CharSequence var3); + } + + private static final class KeyChange { + public PointF position; + public long time; + + public KeyChange(long var1, PointF var3) { + this.time = var1; + this.position = var3; + } + } + + private class TouchEventListener implements TouchNavSpaceTracker.KeyEventListener { + private TouchEventListener() { + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + if (LeanbackKeyboardController.this.isEnterKey(var1)) { + LeanbackKeyboardController.this.mKeyDownReceived = true; + if (var2.getRepeatCount() == 0) { + LeanbackKeyboardController.this.mContainer.setTouchState(3); + LeanbackKeyboardController.this.mSpaceTracker.blockMovementUntil(var2.getEventTime() + 500L); + LeanbackKeyboardController.this.performBestSnap(var2.getEventTime()); + } + } + + return LeanbackKeyboardController.this.handleKeyDownEvent(var1, var2.getRepeatCount()); + } + + public boolean onKeyLongPress(int var1, KeyEvent var2) { + return LeanbackKeyboardController.this.handleKeyLongPress(var1); + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (LeanbackKeyboardController.this.isEnterKey(var1)) { + if (!LeanbackKeyboardController.this.mKeyDownReceived || LeanbackKeyboardController.this.mLongPressHandled) { + LeanbackKeyboardController.this.mLongPressHandled = false; + return true; + } + + LeanbackKeyboardController.this.mKeyDownReceived = false; + if (LeanbackKeyboardController.this.mContainer.getTouchState() == 3) { + LeanbackKeyboardController.this.mContainer.setTouchState(1); + LeanbackKeyboardController.this.mSpaceTracker.unblockMovement(); + } + } + + return LeanbackKeyboardController.this.handleKeyUpEvent(var1, var2.getEventTime()); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java new file mode 100644 index 0000000..d87cf46 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackKeyboardView.java @@ -0,0 +1,508 @@ +package com.google.android.leanback.ime; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.Bitmap.Config; +import android.graphics.Paint.Align; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.Iterator; +import java.util.List; + +public class LeanbackKeyboardView extends FrameLayout { + public static final int ASCII_PERIOD = 46; + public static final int ASCII_SPACE = 32; + private static final boolean DEBUG = false; + public static final int KEYCODE_CAPS_LOCK = -6; + public static final int KEYCODE_DELETE = -5; + public static final int KEYCODE_DISMISS_MINI_KEYBOARD = -8; + public static final int KEYCODE_LEFT = -3; + public static final int KEYCODE_RIGHT = -4; + public static final int KEYCODE_SHIFT = -1; + public static final int KEYCODE_SYM_TOGGLE = -2; + public static final int KEYCODE_VOICE = -7; + private static final int NOT_A_KEY = -1; + public static final int SHIFT_LOCKED = 2; + public static final int SHIFT_OFF = 0; + public static final int SHIFT_ON = 1; + private static final String TAG = "LbKbView"; + private int mBaseMiniKbIndex = -1; + private final int mClickAnimDur; + private final float mClickedScale; + private int mColCount; + private View mCurrentFocusView; + private boolean mFocusClicked; + private int mFocusIndex; + private final float mFocusedScale; + private final int mInactiveMiniKbAlpha; + private ImageView[] mKeyImageViews; + private int mKeyTextColor; + private int mKeyTextSize; + private Keyboard mKeyboard; + private LeanbackKeyboardView.KeyHolder[] mKeys; + private boolean mMiniKeyboardOnScreen; + private int mModeChangeTextSize; + private Rect mPadding; + private Paint mPaint; + private int mRowCount; + private int mShiftState; + private final int mUnfocusStartDelay; + + public LeanbackKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + Resources res = context.getResources(); + TypedArray styledAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LeanbackKeyboardView, 0, 0); + this.mRowCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_rowCount, -1); + this.mColCount = styledAttrs.getInteger(R.styleable.LeanbackKeyboardView_columnCount, -1); + this.mKeyTextSize = (int) res.getDimension(R.dimen.key_font_size); + this.mPaint = new Paint(); + this.mPaint.setAntiAlias(true); + this.mPaint.setTextSize((float) this.mKeyTextSize); + this.mPaint.setTextAlign(Align.CENTER); + this.mPaint.setAlpha(255); + this.mPadding = new Rect(0, 0, 0, 0); + this.mModeChangeTextSize = (int) res.getDimension(R.dimen.function_key_mode_change_font_size); + this.mKeyTextColor = res.getColor(R.color.key_text_default); + this.mFocusIndex = -1; + this.mShiftState = 0; + this.mFocusedScale = res.getFraction(R.fraction.focused_scale, 1, 1); + this.mClickedScale = res.getFraction(R.fraction.clicked_scale, 1, 1); + this.mClickAnimDur = res.getInteger(R.integer.clicked_anim_duration); + this.mUnfocusStartDelay = res.getInteger(R.integer.unfocused_anim_delay); + this.mInactiveMiniKbAlpha = res.getInteger(R.integer.inactive_mini_kb_alpha); + } + + private CharSequence adjustCase(LeanbackKeyboardView.KeyHolder keyHolder) { + CharSequence label = keyHolder.key.label; + CharSequence result = label; + if (label != null && label.length() < 3) { + boolean flag; + if (keyHolder.isInMiniKb && keyHolder.isInvertible) { + flag = true; + } else { + flag = false; + } + + if (this.mKeyboard.isShifted() ^ flag) { + result = label.toString().toUpperCase(); + } else { + result = label.toString().toLowerCase(); + } + + keyHolder.key.label = result; + } + + return result; + } + + @TargetApi(16) + private ImageView createKeyImageView(int keyIndex) { + Rect var8 = this.mPadding; + int var2 = this.getPaddingLeft(); + int var3 = this.getPaddingTop(); + LeanbackKeyboardView.KeyHolder var6 = this.mKeys[keyIndex]; + Key var7 = var6.key; + this.adjustCase(var6); + String var5; + if (var7.label == null) { + var5 = null; + } else { + var5 = var7.label.toString(); + } + + if (Log.isLoggable("LbKbView", Log.DEBUG)) { + Log.d("LbKbView", "LABEL: " + var7.label + "->" + var5); + } + + Bitmap var9 = Bitmap.createBitmap(var7.width, var7.height, Config.ARGB_8888); + Canvas var10 = new Canvas(var9); + Paint var11 = this.mPaint; + var11.setColor(this.mKeyTextColor); + var10.drawARGB(0, 0, 0, 0); + if (var7.icon != null) { + if (var7.codes[0] == -1) { + switch (this.mShiftState) { + case 0: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_off); + break; + case 1: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_on); + break; + case 2: + var7.icon = this.getContext().getResources().getDrawable(R.drawable.ic_ime_shift_lock_on); + } + } + + keyIndex = (var7.width - var8.left - var8.right - var7.icon.getIntrinsicWidth()) / 2 + var8.left; + int var4 = (var7.height - var8.top - var8.bottom - var7.icon.getIntrinsicHeight()) / 2 + var8.top; + var10.translate((float) keyIndex, (float) var4); + var7.icon.setBounds(0, 0, var7.icon.getIntrinsicWidth(), var7.icon.getIntrinsicHeight()); + var7.icon.draw(var10); + var10.translate((float) (-keyIndex), (float) (-var4)); + } else if (var5 != null) { + if (var5.length() > 1) { + var11.setTextSize((float) this.mModeChangeTextSize); + var11.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); + } else { + var11.setTextSize((float) this.mKeyTextSize); + var11.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + } + + var10.drawText(var5, (float) ((var7.width - var8.left - var8.right) / 2 + var8.left), (float) ((var7.height - var8.top - var8.bottom) / + 2) + (var11.getTextSize() - var11.descent()) / 2.0F + (float) var8.top, var11); + var11.setShadowLayer(0.0F, 0.0F, 0.0F, 0); + } + + ImageView var12 = new ImageView(this.getContext()); + var12.setImageBitmap(var9); + var12.setContentDescription(var5); + this.addView(var12, new LayoutParams(-2, -2)); + var12.setX((float) (var7.x + var2)); + var12.setY((float) (var7.y + var3)); + if (this.mMiniKeyboardOnScreen && !var6.isInMiniKb) { + keyIndex = this.mInactiveMiniKbAlpha; + } else { + keyIndex = 255; + } + + var12.setImageAlpha(keyIndex); + var12.setVisibility(View.VISIBLE); + return var12; + } + + private void createKeyImageViews(LeanbackKeyboardView.KeyHolder[] keys) { + int var3 = keys.length; + int var2; + if (this.mKeyImageViews != null) { + ImageView[] var5 = this.mKeyImageViews; + int var4 = var5.length; + + for (var2 = 0; var2 < var4; ++var2) { + this.removeView(var5[var2]); + } + + this.mKeyImageViews = null; + } + + for (var2 = 0; var2 < var3; ++var2) { + if (this.mKeyImageViews == null) { + this.mKeyImageViews = new ImageView[var3]; + } else if (this.mKeyImageViews[var2] != null) { + this.removeView(this.mKeyImageViews[var2]); + } + + this.mKeyImageViews[var2] = this.createKeyImageView(var2); + } + + } + + private void removeMessages() { + } + + private void setKeys(List keys) { + this.mKeys = new LeanbackKeyboardView.KeyHolder[keys.size()]; + Iterator var4 = keys.iterator(); + + for (int var2 = 0; var2 < this.mKeys.length && var4.hasNext(); ++var2) { + Key var3 = (Key) var4.next(); + this.mKeys[var2] = new LeanbackKeyboardView.KeyHolder(var3); + } + + } + + public boolean dismissMiniKeyboard() { + boolean var1 = false; + if (this.mMiniKeyboardOnScreen) { + this.mMiniKeyboardOnScreen = false; + this.setKeys(this.mKeyboard.getKeys()); + this.invalidateAllKeys(); + var1 = true; + } + + return var1; + } + + public int getBaseMiniKbIndex() { + return this.mBaseMiniKbIndex; + } + + public int getColCount() { + return this.mColCount; + } + + public Key getFocusedKey() { + return this.mFocusIndex == -1 ? null : this.mKeys[this.mFocusIndex].key; + } + + public Key getKey(int index) { + return this.mKeys != null && this.mKeys.length != 0 && index >= 0 && index <= this.mKeys.length ? this.mKeys[index].key : null; + } + + public Keyboard getKeyboard() { + return this.mKeyboard; + } + + public int getNearestIndex(float x, float y) { + int var7; + if (this.mKeys != null && this.mKeys.length != 0) { + float var3 = (float) this.getPaddingLeft(); + float var4 = (float) this.getPaddingTop(); + float var5 = (float) (this.getMeasuredHeight() - this.getPaddingTop() - this.getPaddingBottom()); + float var6 = (float) (this.getMeasuredWidth() - this.getPaddingLeft() - this.getPaddingRight()); + int var9 = this.getRowCount(); + int var10 = this.getColCount(); + int var8 = (int) ((y - var4) / var5 * (float) var9); + if (var8 < 0) { + var7 = 0; + } else { + var7 = var8; + if (var8 >= var9) { + var7 = var9 - 1; + } + } + + var9 = (int) ((x - var3) / var6 * (float) var10); + if (var9 < 0) { + var8 = 0; + } else { + var8 = var9; + if (var9 >= var10) { + var8 = var10 - 1; + } + } + + var8 += this.mColCount * var7; + var7 = var8; + if (var8 > 46) { + var7 = var8; + if (var8 < 53) { + var7 = 46; + } + } + + var8 = var7; + if (var7 >= 53) { + var8 = var7 - 6; + } + + if (var8 < 0) { + return 0; + } + + var7 = var8; + if (var8 >= this.mKeys.length) { + return this.mKeys.length - 1; + } + } else { + var7 = 0; + } + + return var7; + } + + public int getRowCount() { + return this.mRowCount; + } + + public int getShiftState() { + return this.mShiftState; + } + + public void invalidateAllKeys() { + this.createKeyImageViews(this.mKeys); + } + + public void invalidateKey(int keyIndex) { + if (this.mKeys != null && keyIndex >= 0 && keyIndex < this.mKeys.length) { + if (this.mKeyImageViews[keyIndex] != null) { + this.removeView(this.mKeyImageViews[keyIndex]); + } + + this.mKeyImageViews[keyIndex] = this.createKeyImageView(keyIndex); + } + } + + public boolean isMiniKeyboardOnScreen() { + return this.mMiniKeyboardOnScreen; + } + + public boolean isShifted() { + return this.mShiftState == 1 || this.mShiftState == 2; + } + + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + public void onKeyLongPress() { + int var1 = this.mKeys[this.mFocusIndex].key.popupResId; + if (var1 != 0) { + this.dismissMiniKeyboard(); + this.mMiniKeyboardOnScreen = true; + List var6 = (new Keyboard(this.getContext(), var1)).getKeys(); + int var3 = var6.size(); + var1 = this.mFocusIndex; + int var2 = this.mFocusIndex / this.mColCount; + int var4 = (this.mFocusIndex + var3) / this.mColCount; + if (var2 != var4) { + var1 = this.mColCount * var4 - var3; + } + + this.mBaseMiniKbIndex = var1; + + for (var2 = 0; var2 < var3; ++var2) { + Key var7 = (Key) var6.get(var2); + var7.x = this.mKeys[var1 + var2].key.x; + var7.y = this.mKeys[var1 + var2].key.y; + var7.edgeFlags = this.mKeys[var1 + var2].key.edgeFlags; + this.mKeys[var1 + var2].key = var7; + this.mKeys[var1 + var2].isInMiniKb = true; + LeanbackKeyboardView.KeyHolder var8 = this.mKeys[var1 + var2]; + boolean var5; + if (var2 == 0) { + var5 = true; + } else { + var5 = false; + } + + var8.isInvertible = var5; + } + + this.invalidateAllKeys(); + } + + } + + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (this.mKeyboard == null) { + this.setMeasuredDimension(this.getPaddingLeft() + this.getPaddingRight(), this.getPaddingTop() + this.getPaddingBottom()); + } else { + int var3 = this.mKeyboard.getMinWidth() + this.getPaddingLeft() + this.getPaddingRight(); + heightMeasureSpec = var3; + if (MeasureSpec.getSize(widthMeasureSpec) < var3 + 10) { + heightMeasureSpec = MeasureSpec.getSize(widthMeasureSpec); + } + + this.setMeasuredDimension(heightMeasureSpec, this.mKeyboard.getHeight() + this.getPaddingTop() + this.getPaddingBottom()); + } + } + + public void setFocus(int row, int col, boolean clicked) { + this.setFocus(this.mColCount * row + col, clicked); + } + + public void setFocus(int index, boolean clicked) { + this.setFocus(index, clicked, true); + } + + public void setFocus(int index, boolean clicked, boolean showFocusScale) { + float var4 = 1.0F; + if (this.mKeyImageViews != null && this.mKeyImageViews.length != 0) { + int var5; + label49: + { + if (index >= 0) { + var5 = index; + if (index < this.mKeyImageViews.length) { + break label49; + } + } + + var5 = -1; + } + + if (var5 != this.mFocusIndex || clicked != this.mFocusClicked) { + if (var5 != this.mFocusIndex) { + if (this.mFocusIndex != -1) { + LeanbackUtils.sendAccessibilityEvent(this.mKeyImageViews[this.mFocusIndex], false); + } + + if (var5 != -1) { + LeanbackUtils.sendAccessibilityEvent(this.mKeyImageViews[var5], true); + } + } + + if (this.mCurrentFocusView != null) { + this.mCurrentFocusView.animate().scaleX(1.0F).scaleY(1.0F).setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setStartDelay((long) this.mUnfocusStartDelay); + this.mCurrentFocusView.animate().setDuration((long) this.mClickAnimDur).setInterpolator(LeanbackKeyboardContainer + .sMovementInterpolator).setStartDelay((long) this.mUnfocusStartDelay); + } + + if (var5 != -1) { + if (clicked) { + var4 = this.mClickedScale; + } else if (showFocusScale) { + var4 = this.mFocusedScale; + } + + this.mCurrentFocusView = this.mKeyImageViews[var5]; + this.mCurrentFocusView.animate().scaleX(var4).scaleY(var4).setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator) + .setDuration((long) this.mClickAnimDur).start(); + } + + this.mFocusIndex = var5; + this.mFocusClicked = clicked; + if (-1 != var5 && !this.mKeys[var5].isInMiniKb) { + this.dismissMiniKeyboard(); + return; + } + } + } + + } + + public void setKeyboard(Keyboard keyboard) { + this.removeMessages(); + this.mKeyboard = keyboard; + this.setKeys(this.mKeyboard.getKeys()); + int var2 = this.mShiftState; + this.mShiftState = -1; + this.setShiftState(var2); + this.requestLayout(); + this.invalidateAllKeys(); + } + + public void setShiftState(int state) { + if (this.mShiftState != state) { + switch (state) { + case 0: + this.mKeyboard.setShifted(false); + break; + case 1: + case 2: + this.mKeyboard.setShifted(true); + } + + this.mShiftState = state; + this.invalidateAllKeys(); + } + } + + private class KeyHolder { + public boolean isInMiniKb = false; + public boolean isInvertible = false; + public Key key; + + public KeyHolder(Key var2) { + this.key = var2; + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java new file mode 100644 index 0000000..f2797ca --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackLocales.java @@ -0,0 +1,92 @@ +package com.google.android.leanback.ime; + +import java.util.Locale; + +public class LeanbackLocales { + public static final Locale ALBANIANIAN; + public static final Locale AZERBAIJANI; + public static final Locale[] AZERTY; + public static final Locale BASQUE_SPANISH; + public static final Locale BELGIAN_DUTCH; + public static final Locale BRITISH_ENGLISH = new Locale("en", "GB"); + public static final Locale CANADIAN_FRENCH; + public static final Locale CATALAN; + public static final Locale CROATIAN; + public static final Locale CZECH; + public static final Locale DANISH; + public static final Locale ENGLISH; + public static final Locale ESTONIAN; + public static final Locale FINNISH; + public static final Locale FRENCH; + public static final Locale GALIC_SPANISH; + public static final Locale GERMAN; + public static final Locale HUNGARIAN; + public static final Locale INDIAN_ENGLISH; + public static final Locale NORWEGIAN; + public static final Locale OTHER_SPANISH; + public static final Locale[] QWERTY_AZ; + public static final Locale[] QWERTY_CA; + public static final Locale[] QWERTY_DA; + public static final Locale[] QWERTY_ES_EU; + public static final Locale[] QWERTY_ES_US; + public static final Locale[] QWERTY_ET; + public static final Locale[] QWERTY_FI; + public static final Locale[] QWERTY_GB; + public static final Locale[] QWERTY_IN; + public static final Locale[] QWERTY_NB; + public static final Locale[] QWERTY_SV; + public static final Locale[] QWERTY_US; + public static final Locale[] QWERTZ; + public static final Locale[] QWERTZ_CH; + public static final Locale SERBIAN; + public static final Locale SLOVENIAN; + public static final Locale SPAIN_SPANISH; + public static final Locale SWEDISH; + public static final Locale SWISS_FRENCH; + public static final Locale SWISS_GERMAN; + public static final Locale SWISS_ITALIAN; + + static { + QWERTY_GB = new Locale[]{BRITISH_ENGLISH}; + INDIAN_ENGLISH = new Locale("en", "IN"); + QWERTY_IN = new Locale[]{INDIAN_ENGLISH}; + SPAIN_SPANISH = new Locale("es", "ES"); + GALIC_SPANISH = new Locale("gl", "ES"); + BASQUE_SPANISH = new Locale("eu", "ES"); + QWERTY_ES_EU = new Locale[]{SPAIN_SPANISH, GALIC_SPANISH, BASQUE_SPANISH}; + OTHER_SPANISH = new Locale("es", ""); + QWERTY_ES_US = new Locale[]{OTHER_SPANISH}; + AZERBAIJANI = new Locale("az", ""); + QWERTY_AZ = new Locale[]{AZERBAIJANI}; + CATALAN = new Locale("ca", ""); + QWERTY_CA = new Locale[]{CATALAN}; + DANISH = new Locale("da", ""); + QWERTY_DA = new Locale[]{DANISH}; + ESTONIAN = new Locale("et", ""); + QWERTY_ET = new Locale[]{ESTONIAN}; + FINNISH = new Locale("fi", ""); + QWERTY_FI = new Locale[]{FINNISH}; + NORWEGIAN = new Locale("nb", ""); + QWERTY_NB = new Locale[]{NORWEGIAN}; + SWEDISH = new Locale("sv", ""); + QWERTY_SV = new Locale[]{SWEDISH}; + ENGLISH = Locale.ENGLISH; + CANADIAN_FRENCH = Locale.CANADA_FRENCH; + QWERTY_US = new Locale[]{ENGLISH, CANADIAN_FRENCH}; + SWISS_GERMAN = new Locale("de", "CH"); + SWISS_ITALIAN = new Locale("it", "CH"); + QWERTZ_CH = new Locale[]{SWISS_GERMAN, SWISS_ITALIAN}; + GERMAN = new Locale("de", ""); + CROATIAN = new Locale("hr", ""); + CZECH = new Locale("cs", ""); + SWISS_FRENCH = new Locale("fr", "CH"); + HUNGARIAN = new Locale("hu", ""); + SERBIAN = new Locale("sr", ""); + SLOVENIAN = new Locale("sl", ""); + ALBANIANIAN = new Locale("sq", ""); + QWERTZ = new Locale[]{GERMAN, CROATIAN, CZECH, SWISS_FRENCH, SWISS_ITALIAN, HUNGARIAN, SERBIAN, SLOVENIAN, ALBANIANIAN}; + FRENCH = Locale.FRENCH; + BELGIAN_DUTCH = new Locale("nl", "BE"); + AZERTY = new Locale[]{FRENCH, BELGIAN_DUTCH}; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java new file mode 100644 index 0000000..36efd56 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackSuggestionsFactory.java @@ -0,0 +1,94 @@ +package com.google.android.leanback.ime; + +import android.inputmethodservice.InputMethodService; +import android.text.TextUtils; +import android.util.Log; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import com.liskovsoft.leankeykeyboard.R; + +import java.util.ArrayList; + +public class LeanbackSuggestionsFactory { + private static final boolean DEBUG = Log.isLoggable("LbSuggestionsFactory", Log.DEBUG); + private static final int MODE_AUTO_COMPLETE = 2; + private static final int MODE_DEFAULT = 0; + private static final int MODE_DOMAIN = 1; + private static final String TAG = "LbSuggestionsFactory"; + private InputMethodService mContext; + private int mMode; + private int mNumSuggestions; + private final ArrayList mSuggestions = new ArrayList(); + + public LeanbackSuggestionsFactory(InputMethodService var1, int var2) { + this.mContext = var1; + this.mNumSuggestions = var2; + } + + public void clearSuggestions() { + this.mSuggestions.clear(); + } + + public void createSuggestions() { + this.clearSuggestions(); + if (this.mMode == 1) { + String[] var3 = this.mContext.getResources().getStringArray(R.array.common_domains); + int var2 = var3.length; + + for(int var1 = 0; var1 < var2; ++var1) { + String var4 = var3[var1]; + this.mSuggestions.add(var4); + } + } + + } + + public ArrayList getSuggestions() { + return this.mSuggestions; + } + + public void onDisplayCompletions(CompletionInfo[] infos) { + this.createSuggestions(); + int len; + if (infos == null) { + len = 0; + } else { + len = infos.length; + } + + for(int i = 0; i < len && this.mSuggestions.size() < this.mNumSuggestions && !TextUtils.isEmpty(infos[i].getText()); ++i) { + this.mSuggestions.add(i, infos[i].getText().toString()); + } + + if (Log.isLoggable("LbSuggestionsFactory", Log.DEBUG)) { + for(len = 0; len < this.mSuggestions.size(); ++len) { + Log.d("LbSuggestionsFactory", "completion " + len + ": " + (String)this.mSuggestions.get(len)); + } + } + + } + + public void onStartInput(EditorInfo info) { + this.mMode = 0; + if ((info.inputType & 65536) != 0) { + this.mMode = 2; + } + + switch(LeanbackUtils.getInputTypeClass(info)) { + case 1: + switch(LeanbackUtils.getInputTypeVariation(info)) { + case 32: + case 208: + this.mMode = 1; + return; + default: + return; + } + default: + } + } + + public boolean shouldSuggestionsAmend() { + return this.mMode == 1; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java new file mode 100644 index 0000000..73e6b65 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/LeanbackUtils.java @@ -0,0 +1,38 @@ +package com.google.android.leanback.ime; + +import android.os.Handler; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +public class LeanbackUtils { + private static final int ACCESSIBILITY_DELAY_MS = 250; + private static final Handler sAccessibilityHandler = new Handler(); + + public static int getImeAction(EditorInfo var0) { + return var0.imeOptions & 1073742079; + } + + public static int getInputTypeClass(EditorInfo var0) { + return var0.inputType & 15; + } + + public static int getInputTypeVariation(EditorInfo var0) { + return var0.inputType & 4080; + } + + public static boolean isAlphabet(int var0) { + return Character.isLetter(var0); + } + + public static void sendAccessibilityEvent(final View var0, boolean var1) { + if (var0 != null && var1) { + sAccessibilityHandler.removeCallbacksAndMessages((Object)null); + sAccessibilityHandler.postDelayed(new Runnable() { + public void run() { + var0.sendAccessibilityEvent(16384); + } + }, 250L); + } + + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java new file mode 100644 index 0000000..bfcf7b7 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/BitmapSoundLevelView.java @@ -0,0 +1,195 @@ +package com.google.android.leanback.ime.voice; + +import android.animation.TimeAnimator; +import android.animation.TimeAnimator.TimeListener; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Paint.Style; +import android.util.AttributeSet; +import android.view.View; +import com.liskovsoft.leankeykeyboard.R; + +public class BitmapSoundLevelView extends View { + private static final boolean DEBUG = false; + private static final int MIC_LEVEL_GUIDELINE_OFFSET = 13; + private static final int MIC_PRIMARY_LEVEL_IMAGE_OFFSET = 3; + private static final String TAG = "BitmapSoundLevelsView"; + private TimeAnimator mAnimator; + private final int mCenterTranslationX; + private final int mCenterTranslationY; + private int mCurrentVolume; + private Rect mDestRect; + private final int mDisableBackgroundColor; + private final Paint mEmptyPaint; + private final int mEnableBackgroundColor; + private SpeechLevelSource mLevelSource; + private final int mMinimumLevelSize; + private Paint mPaint; + private int mPeakLevel; + private int mPeakLevelCountDown; + private final Bitmap mPrimaryLevel; + private final Bitmap mTrailLevel; + + public BitmapSoundLevelView(Context context) { + this(context, null); + } + + public BitmapSoundLevelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @TargetApi(16) + public BitmapSoundLevelView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.mEmptyPaint = new Paint(); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BitmapSoundLevelView, defStyleAttr, 0); + this.mEnableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_enabledBackgroundColor, Color.parseColor("#66FFFFFF")); + this.mDisableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_disabledBackgroundColor, -1); + boolean primaryLevelEnabled = false; + boolean peakLevelEnabled = false; + int primaryLevelId = 0; + if (styledAttrs.hasValue(R.styleable.BitmapSoundLevelView_primaryLevels)) { + primaryLevelId = styledAttrs.getResourceId(R.styleable.BitmapSoundLevelView_primaryLevels, R.drawable.vs_reactive_dark); + primaryLevelEnabled = true; + } + + int trailLevelId = 0; + if (styledAttrs.hasValue(R.styleable.BitmapSoundLevelView_trailLevels)) { + trailLevelId = styledAttrs.getResourceId(R.styleable.BitmapSoundLevelView_trailLevels, R.drawable.vs_reactive_light); + peakLevelEnabled = true; + } + + this.mCenterTranslationX = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterX, 0); + this.mCenterTranslationY = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterY, 0); + this.mMinimumLevelSize = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_minLevelRadius, 0); + styledAttrs.recycle(); + if (primaryLevelEnabled) { + this.mPrimaryLevel = BitmapFactory.decodeResource(this.getResources(), primaryLevelId); + } else { + this.mPrimaryLevel = null; + } + + if (peakLevelEnabled) { + this.mTrailLevel = BitmapFactory.decodeResource(this.getResources(), trailLevelId); + } else { + this.mTrailLevel = null; + } + + this.mPaint = new Paint(); + this.mDestRect = new Rect(); + this.mEmptyPaint.setFilterBitmap(true); + this.mLevelSource = new SpeechLevelSource(); + this.mLevelSource.setSpeechLevel(0); + this.mAnimator = new TimeAnimator(); + this.mAnimator.setRepeatCount(-1); + this.mAnimator.setTimeListener(new TimeListener() { + public void onTimeUpdate(TimeAnimator animator, long l, long l1) { + BitmapSoundLevelView.this.invalidate(); + } + }); + } + + @TargetApi(16) + private void startAnimator() { + if (!this.mAnimator.isStarted()) { + this.mAnimator.start(); + } + + } + + @TargetApi(16) + private void stopAnimator() { + this.mAnimator.cancel(); + } + + private void updateAnimatorState() { + if (this.isEnabled()) { + this.startAnimator(); + } else { + this.stopAnimator(); + } + } + + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + this.updateAnimatorState(); + } + + protected void onDetachedFromWindow() { + this.stopAnimator(); + super.onDetachedFromWindow(); + } + + public void onDraw(Canvas canvas) { + if (this.isEnabled()) { + canvas.drawColor(this.mEnableBackgroundColor); + final int level = this.mLevelSource.getSpeechLevel(); + if (level > this.mPeakLevel) { + this.mPeakLevel = level; + this.mPeakLevelCountDown = 25; + } else if (this.mPeakLevelCountDown == 0) { + this.mPeakLevel = Math.max(0, this.mPeakLevel - 2); + } else { + --this.mPeakLevelCountDown; + } + + if (level > this.mCurrentVolume) { + this.mCurrentVolume += (level - this.mCurrentVolume) / 4; + } else { + this.mCurrentVolume = (int) ((float) this.mCurrentVolume * 0.95F); + } + + final int centerX = this.mCenterTranslationX + this.getWidth() / 2; + final int centerY = this.mCenterTranslationY + this.getWidth() / 2; + int size; + if (this.mTrailLevel != null) { + size = (centerX - this.mMinimumLevelSize) * this.mPeakLevel / 100 + this.mMinimumLevelSize; + this.mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(this.mTrailLevel, (Rect) null, this.mDestRect, this.mEmptyPaint); + } + + if (this.mPrimaryLevel != null) { + size = (centerX - this.mMinimumLevelSize) * this.mCurrentVolume / 100 + this.mMinimumLevelSize; + this.mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size); + canvas.drawBitmap(this.mPrimaryLevel, (Rect) null, this.mDestRect, this.mEmptyPaint); + this.mPaint.setColor(this.getResources().getColor(R.color.search_mic_background)); + this.mPaint.setStyle(Style.FILL); + canvas.drawCircle((float) centerX, (float) centerY, (float) (this.mMinimumLevelSize - 3), this.mPaint); + } + + if (this.mTrailLevel != null && this.mPrimaryLevel != null) { + this.mPaint.setColor(this.getResources().getColor(R.color.search_mic_levels_guideline)); + this.mPaint.setStyle(Style.STROKE); + canvas.drawCircle((float) centerX, (float) centerY, (float) (centerX - 13), this.mPaint); + } + + } else { + canvas.drawColor(this.mDisableBackgroundColor); + } + } + + public void onWindowFocusChanged(boolean var1) { + super.onWindowFocusChanged(var1); + if (var1) { + this.updateAnimatorState(); + } else { + this.stopAnimator(); + } + } + + public void setEnabled(boolean var1) { + super.setEnabled(var1); + this.updateAnimatorState(); + } + + public void setLevelSource(SpeechLevelSource var1) { + this.mLevelSource = var1; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java new file mode 100644 index 0000000..5d95f3e --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/RecognizerView.java @@ -0,0 +1,216 @@ +package com.google.android.leanback.ime.voice; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Parcelable.Creator; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.BaseSavedState; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import com.google.android.leanback.ime.LeanbackUtils; +import com.liskovsoft.leankeykeyboard.R; + +public class RecognizerView extends RelativeLayout { + private static final boolean DEBUG = false; + private static final String TAG = "RecognizerView"; + private RecognizerView.Callback mCallback; + private boolean mEnabled; + protected ImageView mMicButton; + private BitmapSoundLevelView mSoundLevels; + private RecognizerView.State mState; + + public RecognizerView(Context var1) { + super(var1); + } + + public RecognizerView(Context var1, AttributeSet var2) { + super(var1, var2); + } + + public RecognizerView(Context var1, AttributeSet var2, int var3) { + super(var1, var2, var3); + } + + private void updateState(RecognizerView.State var1) { + this.mState = var1; + this.refreshUi(); + } + + public View getMicButton() { + return this.mMicButton; + } + + public void onAttachedToWindow() { + super.onAttachedToWindow(); + this.refreshUi(); + } + + public void onClick() { + switch (this.mState) { + case MIC_INITIALIZING: + default: + return; + case LISTENING: + this.mCallback.onCancelRecordingClicked(); + return; + case RECORDING: + this.mCallback.onStopRecordingClicked(); + return; + case RECOGNIZING: + this.mCallback.onCancelRecordingClicked(); + return; + case NOT_LISTENING: + this.mCallback.onStartRecordingClicked(); + } + } + + @SuppressLint("MissingSuperCall") + @Override + public void onFinishInflate() { + LayoutInflater.from(this.getContext()).inflate(R.layout.recognizer_view, this, true); + this.mSoundLevels = (BitmapSoundLevelView) this.findViewById(R.id.microphone); + this.mMicButton = (ImageView) this.findViewById(R.id.recognizer_mic_button); + this.mState = RecognizerView.State.NOT_LISTENING; + } + + @Override + public void onRestoreInstanceState(Parcelable var1) { + if (!(var1 instanceof RecognizerView.SavedState)) { + super.onRestoreInstanceState(var1); + } else { + RecognizerView.SavedState var2 = (RecognizerView.SavedState) var1; + super.onRestoreInstanceState(var2.getSuperState()); + this.mState = var2.mState; + } + } + + @Override + public Parcelable onSaveInstanceState() { + RecognizerView.SavedState var1 = new RecognizerView.SavedState(super.onSaveInstanceState()); + var1.mState = this.mState; + return var1; + } + + protected void refreshUi() { + if (this.mEnabled) { + switch (this.mState) { + case MIC_INITIALIZING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + this.mSoundLevels.setEnabled(false); + return; + case LISTENING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); + this.mSoundLevels.setEnabled(true); + return; + case RECORDING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector); + this.mSoundLevels.setEnabled(true); + return; + case RECOGNIZING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + this.mSoundLevels.setEnabled(false); + return; + case NOT_LISTENING: + this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); + this.mSoundLevels.setEnabled(false); + return; + default: + } + } + } + + public void setCallback(RecognizerView.Callback callback) { + this.mCallback = callback; + } + + public void setMicEnabled(boolean enabled) { + this.mEnabled = enabled; + if (enabled) { + this.mMicButton.setAlpha(1.0F); + this.mMicButton.setImageResource(R.drawable.ic_voice_available); + } else { + this.mMicButton.setAlpha(0.1F); + this.mMicButton.setImageResource(R.drawable.ic_voice_off); + } + } + + public void setMicFocused(boolean focused) { + if (this.mEnabled) { + if (focused) { + this.mMicButton.setImageResource(R.drawable.ic_voice_focus); + } else { + this.mMicButton.setImageResource(R.drawable.ic_voice_available); + } + + LeanbackUtils.sendAccessibilityEvent(this.mMicButton, focused); + } + + } + + public void setSpeechLevelSource(SpeechLevelSource var1) { + this.mSoundLevels.setLevelSource(var1); + } + + public void showInitializingMic() { + this.updateState(RecognizerView.State.MIC_INITIALIZING); + } + + public void showListening() { + this.updateState(RecognizerView.State.LISTENING); + } + + public void showNotListening() { + this.updateState(RecognizerView.State.NOT_LISTENING); + } + + public void showRecognizing() { + this.updateState(RecognizerView.State.RECOGNIZING); + } + + public void showRecording() { + this.updateState(RecognizerView.State.RECORDING); + } + + public interface Callback { + void onCancelRecordingClicked(); + + void onStartRecordingClicked(); + + void onStopRecordingClicked(); + } + + public static class SavedState extends BaseSavedState { + public static final Creator CREATOR = new Creator() { + public RecognizerView.SavedState createFromParcel(Parcel var1) { + return new RecognizerView.SavedState(var1); + } + + public RecognizerView.SavedState[] newArray(int var1) { + return new RecognizerView.SavedState[var1]; + } + }; + RecognizerView.State mState; + + private SavedState(Parcel var1) { + super(var1); + this.mState = RecognizerView.State.valueOf(var1.readString()); + } + + public SavedState(Parcelable var1) { + super(var1); + } + + public void writeToParcel(Parcel var1, int var2) { + super.writeToParcel(var1, var2); + var1.writeString(this.mState.toString()); + } + } + + private static enum State { + LISTENING, MIC_INITIALIZING, NOT_LISTENING, RECOGNIZING, RECORDING; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java new file mode 100644 index 0000000..99bcd09 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/leanback/ime/voice/SpeechLevelSource.java @@ -0,0 +1,25 @@ +package com.google.android.leanback.ime.voice; + +public class SpeechLevelSource { + private volatile int mSpeechLevel; + + public int getSpeechLevel() { + return this.mSpeechLevel; + } + + public boolean isValid() { + return this.mSpeechLevel > 0; + } + + public void reset() { + this.mSpeechLevel = -1; + } + + public void setSpeechLevel(int var1) { + if (var1 >= 0 && var1 <= 100) { + this.mSpeechLevel = var1; + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java new file mode 100644 index 0000000..eba1561 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavMotionTracker.java @@ -0,0 +1,164 @@ +package com.google.android.pano.util; + +import android.annotation.SuppressLint; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.InputDevice.MotionRange; + +public class TouchNavMotionTracker { + private static final float MAXIMUM_FLING_VELOCITY = 1270.0F; + private static final float MINIMUM_FLING_VELOCITY = 200.0F; + private float mCurrX; + private float mCurrY; + private MotionEvent mDownEvent; + private final float mMaxFlingVelocityX; + private final float mMaxFlingVelocityY; + private final float mMinFlingVelocityX; + private final float mMinFlingVelocityY; + private final float mMinScrollX; + private final float mMinScrollY; + private float mPrevX; + private float mPrevY; + private final float mResolutionX; + private final float mResolutionY; + private float mScrollX; + private float mScrollY; + private float mVelX; + private float mVelY; + private VelocityTracker mVelocityTracker; + + public TouchNavMotionTracker(float resolutionX, float resolutionY, float minScrollDist) { + if (resolutionX <= 0.0F) { + resolutionX = 6.3F; + } + + this.mResolutionX = resolutionX; + if (resolutionY <= 0.0F) { + resolutionY = 6.3F; + } + + this.mResolutionY = resolutionY; + this.mMaxFlingVelocityX = this.mResolutionX * 1270.0F; + this.mMaxFlingVelocityY = this.mResolutionY * 1270.0F; + this.mMinFlingVelocityX = this.mResolutionX * 200.0F; + this.mMinFlingVelocityY = this.mResolutionY * 200.0F; + this.mMinScrollX = this.mResolutionX * minScrollDist; + this.mMinScrollY = this.mResolutionY * minScrollDist; + } + + @SuppressLint("NewApi") + public static TouchNavMotionTracker buildTrackerForDevice(final InputDevice device, final float minScrollDist) { + MotionRange range = device.getMotionRange(0); + float resolution; + if (range == null) { + resolution = 0.0F; + } else { + resolution = range.getResolution(); + } + + float resolutionX = resolution; + if (resolution <= 0.0F) { + resolutionX = 6.3F; + } + + MotionRange range2 = device.getMotionRange(1); + if (range2 == null) { + resolution = 0.0F; + } else { + resolution = range2.getResolution(); + } + + float resolutionY = resolution; + if (resolution <= 0.0F) { + resolutionY = 6.3F; + } + + return new TouchNavMotionTracker(resolutionX, resolutionY, minScrollDist); + } + + public void addMovement(MotionEvent var1) { + if (this.mVelocityTracker == null) { + this.mVelocityTracker = VelocityTracker.obtain(); + } + + this.mVelocityTracker.addMovement(var1); + } + + public void clear() { + if (this.mDownEvent != null) { + this.mDownEvent.recycle(); + this.mDownEvent = null; + } + + if (this.mVelocityTracker != null) { + this.mVelocityTracker.recycle(); + this.mVelocityTracker = null; + } + + } + + public boolean computeVelocity() { + this.mVelocityTracker.computeCurrentVelocity(1000); + this.mVelX = Math.min(this.mMaxFlingVelocityX, this.mVelocityTracker.getXVelocity()); + this.mVelY = Math.min(this.mMaxFlingVelocityY, this.mVelocityTracker.getYVelocity()); + return Math.abs(this.mVelX) > this.mMinFlingVelocityX || Math.abs(this.mVelY) > this.mMinFlingVelocityY; + } + + public MotionEvent getDownEvent() { + return this.mDownEvent; + } + + public float getPhysicalX(float var1) { + return var1 / this.mResolutionX; + } + + public float getPhysicalY(float var1) { + return var1 / this.mResolutionY; + } + + public float getScrollX() { + return this.mScrollX; + } + + public float getScrollY() { + return this.mScrollY; + } + + public float getXResolution() { + return this.mResolutionX; + } + + public float getXVel() { + return this.mVelX; + } + + public float getYResolution() { + return this.mResolutionY; + } + + public float getYVel() { + return this.mVelY; + } + + public void setDownEvent(MotionEvent var1) { + if (this.mDownEvent != null && var1 != this.mDownEvent) { + this.mDownEvent.recycle(); + } + + this.mDownEvent = var1; + } + + public boolean setNewValues(float var1, float var2) { + this.mCurrX = var1; + this.mCurrY = var2; + this.mScrollX = this.mCurrX - this.mPrevX; + this.mScrollY = this.mCurrY - this.mPrevY; + return Math.abs(this.mScrollX) > this.mMinScrollX || Math.abs(this.mScrollY) > this.mMinScrollY; + } + + public void updatePrevValues() { + this.mPrevX = this.mCurrX; + this.mPrevY = this.mCurrY; + } +} diff --git a/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java new file mode 100644 index 0000000..b9a7597 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/android/pano/util/TouchNavSpaceTracker.java @@ -0,0 +1,593 @@ +package com.google.android.pano.util; + +import android.animation.TimeInterpolator; +import android.graphics.PointF; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.util.SparseArray; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.animation.AccelerateInterpolator; + +public class TouchNavSpaceTracker { + private static final boolean DEBUG = false; + public static final float DEFAULT_DAMPED_SENSITIVITY = 0.5F; + public static final long DEFAULT_DAMPENING_DURATION_MS = 200L; + public static final long DEFAULT_DAMPING_DURATION_MS = 200L; + public static final float DEFAULT_HORIZONTAL_SIZE_MM = 120.0F; + public static final float DEFAULT_LPF_COEFF = 0.25F; + public static final float DEFAULT_MAX_FLICK_DISTANCE_MM = 40.0F; + public static final long DEFAULT_MAX_FLICK_DURATION_MS = 250L; + public static final float DEFAULT_MIN_FLICK_DISTANCE_MM = 4.0F; + public static final float DEFAULT_SENSITIVITY = 1.0F; + public static final float DEFAULT_VERTICAL_SIZE_MM = 50.0F; + private static final int[] DIRECTIONS = new int[]{1, 3, 2, 6, 4, 12, 8, 9, 1}; + private static final float[] DIRECTION_BOUNDARIES = new float[]{-2.7488935F, -1.9634954F, -1.1780972F, -0.3926991F, 0.3926991F, 1.1780972F, 1.9634954F, 2.7488935F}; + public static final int DIRECTION_DOWN = 2; + public static final int DIRECTION_DOWN_LEFT = 3; + public static final int DIRECTION_DOWN_RIGHT = 6; + public static final int DIRECTION_LEFT = 1; + public static final int DIRECTION_RIGHT = 4; + public static final int DIRECTION_UP = 8; + public static final int DIRECTION_UP_LEFT = 9; + public static final int DIRECTION_UP_RIGHT = 12; + private static final int MSG_LONG_CLICK = 0; + private static final String TAG = "TouchNavSpaceTracker"; + private float mDampedSensitivity; + private float mDampingDuration; + private float mFlickMaxDistance; + private long mFlickMaxDuration; + private float mFlickMaxSquared; + private float mFlickMinDistance; + private float mFlickMinSquared; + private Handler mHandler; + protected TouchNavSpaceTracker.KeyEventListener mKeyEventListener; + private float mLPFCurrX; + private float mLPFCurrY; + private boolean mLPFEnabled; + private long mMovementBlockTime; + private float mPhysicalHeight; + private PointF mPhysicalPosition; + private float mPhysicalWidth; + private float mPixelHeight; + protected TouchNavSpaceTracker.TouchEventListener mPixelListener; + private float mPixelWidth; + private float mPixelsPerMm; + private PointF mPrevPhysPosition; + private float mSensitivity; + private TimeInterpolator mSensitivityInterpolator; + protected final SparseArray mTouchParams; + private float mUnscaledFlickMaxDistance; + private float mUnscaledFlickMinDistance; + private boolean mWasBlocked; + + public TouchNavSpaceTracker() { + this((TouchNavSpaceTracker.KeyEventListener)null, (TouchNavSpaceTracker.TouchEventListener)null); + } + + public TouchNavSpaceTracker(TouchNavSpaceTracker.KeyEventListener var1, TouchNavSpaceTracker.TouchEventListener var2) { + this.mPrevPhysPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + this.mPhysicalPosition = new PointF(Float.MIN_VALUE, Float.MIN_VALUE); + this.mWasBlocked = false; + this.mSensitivityInterpolator = new AccelerateInterpolator(); + this.mDampingDuration = 200.0F; + this.mDampedSensitivity = 0.5F; + this.mSensitivity = 1.0F; + this.mUnscaledFlickMinDistance = 4.0F; + this.mUnscaledFlickMaxDistance = 40.0F; + this.mFlickMinDistance = this.mSensitivity * 4.0F; + this.mFlickMaxDistance = this.mSensitivity * 40.0F; + this.mFlickMinSquared = this.mFlickMinDistance * this.mFlickMinDistance; + this.mFlickMaxSquared = this.mFlickMaxDistance * this.mFlickMaxDistance; + this.mFlickMaxDuration = 250L; + this.mLPFEnabled = false; + this.mHandler = new Handler() { + public void handleMessage(Message var1) { + switch(var1.what) { + case 0: + if (TouchNavSpaceTracker.this.mKeyEventListener != null) { + TouchNavSpaceTracker.this.mKeyEventListener.onKeyLongPress(var1.arg1, (KeyEvent)var1.obj); + return; + } + default: + } + } + }; + this.mKeyEventListener = var1; + this.mPixelListener = var2; + this.mTouchParams = new SparseArray(1); + this.mPhysicalWidth = 120.0F; + this.mPhysicalHeight = 50.0F; + this.mPixelWidth = 0.0F; + this.mPixelHeight = 0.0F; + this.mPixelsPerMm = 0.0F; + } + + private float calculateSensitivity(MotionEvent var1, MotionEvent var2) { + long var4 = var1.getEventTime() - var2.getEventTime(); + float var3; + if (var1.getEventTime() < this.mMovementBlockTime) { + var3 = 0.0F; + this.mWasBlocked = true; + } else if ((float)var4 < this.mDampingDuration) { + var3 = this.mSensitivityInterpolator.getInterpolation((float)var4 / this.mDampingDuration); + var3 = this.mDampedSensitivity + (this.mSensitivity - this.mDampedSensitivity) * var3; + } else { + var3 = this.mSensitivity; + } + + if (var3 != 0.0F && this.mWasBlocked) { + this.mWasBlocked = false; + this.setPhysicalPosition(this.mPhysicalPosition.x, this.mPhysicalPosition.y); + } + + return var3; + } + + private void checkForLongClick(int var1, KeyEvent var2) { + if (var1 == 23) { + Message var3 = this.mHandler.obtainMessage(0); + var3.arg1 = var1; + var3.obj = var2; + if (!this.mHandler.hasMessages(0)) { + this.mHandler.sendMessageDelayed(var3, (long)ViewConfiguration.getLongPressTimeout()); + return; + } + } + + } + + private void clampPosition() { + if (this.mPhysicalPosition.x < 0.0F) { + this.setPhysicalPosition(0.0F, this.mPhysicalPosition.y); + } else if (this.mPhysicalPosition.x > this.mPhysicalWidth) { + this.setPhysicalPosition(this.mPhysicalWidth, this.mPhysicalPosition.y); + } + + if (this.mPhysicalPosition.y < 0.0F) { + this.setPhysicalPosition(this.mPhysicalPosition.x, 0.0F); + } else if (this.mPhysicalPosition.y > this.mPhysicalHeight) { + this.setPhysicalPosition(this.mPhysicalPosition.x, this.mPhysicalHeight); + return; + } + + } + + private int getDpadDirection(float var1, float var2) { + var1 = (float)Math.atan2((double)(-var2), (double)var1); + + int var3; + for(var3 = 0; var3 < DIRECTION_BOUNDARIES.length && var1 >= DIRECTION_BOUNDARIES[var3]; ++var3) { + ; + } + + return DIRECTIONS[var3]; + } + + private float getPhysicalX(float var1) { + return this.mPixelWidth <= 0.0F ? 0.0F : this.mPhysicalWidth * var1 / this.mPixelWidth; + } + + private float getPhysicalY(float var1) { + return this.mPixelHeight <= 0.0F ? 0.0F : this.mPhysicalHeight * var1 / this.mPixelHeight; + } + + private float getPixelX(float var1) { + return this.mPixelWidth * var1 / this.mPhysicalWidth; + } + + private float getPixelY(float var1) { + return this.mPixelHeight * var1 / this.mPhysicalHeight; + } + + private int getPrimaryDpadDirection(float var1, float var2) { + if (Math.abs(var1) > Math.abs(var2)) { + return var1 > 0.0F ? 4 : 1; + } else { + return var2 > 0.0F ? 2 : 8; + } + } + + private float getScaledValue(float var1, float var2) { + return var1 * var2; + } + + private TouchNavMotionTracker getTrackerForDevice(InputDevice var1) { + TouchNavMotionTracker var3 = (TouchNavMotionTracker)this.mTouchParams.get(var1.getId()); + TouchNavMotionTracker var2 = var3; + if (var3 == null) { + var2 = TouchNavMotionTracker.buildTrackerForDevice(var1, 0.1F); + this.mTouchParams.put(var1.getId(), var2); + } + + return var2; + } + + private void setPhysicalSizeInternal(float var1, float var2) { + this.mPhysicalWidth = var1; + this.mPhysicalHeight = var2; + if (this.mPhysicalPosition.x > this.mPhysicalWidth) { + this.mPhysicalPosition.x = this.mPhysicalWidth; + } + + if (this.mPhysicalPosition.y > this.mPhysicalHeight) { + this.mPhysicalPosition.y = this.mPhysicalHeight; + } + + } + + private void updatePhysicalSize() { + if (this.mPixelWidth > 0.0F && this.mPixelHeight > 0.0F && this.mPixelsPerMm > 0.0F) { + this.setPhysicalSizeInternal(this.mPixelWidth / this.mPixelsPerMm, this.mPixelHeight / this.mPixelsPerMm); + } + + } + + public void blockMovementUntil(long var1) { + this.mMovementBlockTime = var1; + } + + public void configureDamping(float var1, long var2) { + this.mDampedSensitivity = var1; + this.mDampingDuration = (float)var2; + } + + public void configureFlicks(float var1, float var2, long var3) { + this.mUnscaledFlickMinDistance = var1; + this.mUnscaledFlickMaxDistance = var2; + this.mFlickMinDistance = this.mSensitivity * var1; + this.mFlickMaxDistance = this.mSensitivity * var2; + this.mFlickMinSquared = this.mFlickMinDistance * this.mFlickMinDistance; + this.mFlickMaxSquared = this.mFlickMaxDistance * this.mFlickMaxDistance; + this.mFlickMaxDuration = var3; + } + + public PointF getCurrentPhysicalPosition() { + return new PointF(this.mPhysicalPosition.x, this.mPhysicalPosition.y); + } + + public PointF getCurrentPixelPosition() { + return new PointF(this.getPixelX(this.mPhysicalPosition.x), this.getPixelY(this.mPhysicalPosition.y)); + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + if (var1 != null && (var1.getSource() & 2097152) == 2097152) { + InputDevice var13 = var1.getDevice(); + if (var13 == null) { + return false; + } + + TouchNavMotionTracker var19 = this.getTrackerForDevice(var13); + int var10 = var1.getActionMasked(); + var19.addMovement(var1); + boolean var6; + if ((var10 & 255) == 6) { + var6 = true; + } else { + var6 = false; + } + + int var7; + if (var6) { + var7 = var1.getActionIndex(); + } else { + var7 = -1; + } + + float var3 = 0.0F; + float var2 = 0.0F; + int var9 = var1.getPointerCount(); + + for(int var8 = 0; var8 < var9; ++var8) { + if (var7 != var8) { + var3 += var1.getX(var8); + var2 += var1.getY(var8); + } + } + + int var17; + if (var6) { + var17 = var9 - 1; + } else { + var17 = var9; + } + + float var4 = var3 / (float)var17; + float var5 = var2 / (float)var17; + TouchNavSpaceTracker.PhysicalMotionEvent var14 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var4), var19.getPhysicalX(var5), var1.getEventTime()); + boolean var18 = false; + boolean var12 = false; + boolean var11; + MotionEvent var15; + TouchNavSpaceTracker.PhysicalMotionEvent var16; + switch(var10 & 255) { + case 0: + if (this.mLPFEnabled) { + this.mLPFCurrX = var4; + this.mLPFCurrY = var5; + } + + var19.setNewValues(var4, var5); + var19.updatePrevValues(); + var19.setDownEvent(MotionEvent.obtain(var1)); + if (this.mPixelListener != null) { + return false | this.mPixelListener.onDown(var14); + } + break; + case 1: + var15 = var19.getDownEvent(); + if (var15 == null) { + Log.w("TouchNavSpaceTracker", "Up event without down event"); + return false | this.mPixelListener.onUp(var14, this.getPixelX(this.mPhysicalPosition.x), this.getPixelY(this.mPhysicalPosition.y)); + } + + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var15.getX()), var19.getPhysicalY(var15.getY()), var15.getEventTime()); + var6 = var18; + if (var19.computeVelocity()) { + var6 = var18; + if (this.mPixelListener != null) { + var2 = this.getPixelX(var19.getPhysicalX(var19.getXVel())); + var3 = this.getPixelY(var19.getPhysicalY(var19.getYVel())); + var18 = false | this.mPixelListener.onFling(var16, var14, var2, var3); + var6 = var18; + if (var14.getTime() - var16.getTime() < this.mFlickMaxDuration) { + var2 = var14.getX() - var16.getX(); + var3 = var14.getY() - var16.getY(); + var4 = var2 * var2 + var3 * var3; + var6 = var18; + if (var4 > this.mFlickMinSquared) { + var6 = var18; + if (var4 < this.mFlickMaxSquared) { + this.mPixelListener.onFlick(var16, var14, this.getDpadDirection(var2, var3), this.getPrimaryDpadDirection(var2, var3)); + var6 = var18; + } + } + } + } + } + + var2 = this.getPixelX(this.mPhysicalPosition.x); + var3 = this.getPixelY(this.mPhysicalPosition.y); + var11 = this.mPixelListener.onUp(var14, var2, var3); + var19.clear(); + return var6 | var11; + case 2: + if (var19.getDownEvent() == null) { + var19.setDownEvent(MotionEvent.obtain(var1)); + if (this.mLPFEnabled) { + this.mLPFCurrX = var4; + this.mLPFCurrY = var5; + } + } + + var3 = var4; + var2 = var5; + if (this.mLPFEnabled) { + this.mLPFCurrX = this.mLPFCurrX * 0.75F + 0.25F * var4; + this.mLPFCurrY = this.mLPFCurrY * 0.75F + 0.25F * var5; + var3 = this.mLPFCurrX; + var2 = this.mLPFCurrY; + } + + if (var19.setNewValues(var3, var2)) { + var2 = var19.getPhysicalX(var19.getScrollX()); + var3 = var19.getPhysicalY(var19.getScrollY()); + var4 = this.calculateSensitivity(var1, var19.getDownEvent()); + this.mPhysicalPosition.x = this.mPrevPhysPosition.x + this.getScaledValue(var2, var4); + this.mPhysicalPosition.y = this.mPrevPhysPosition.y + this.getScaledValue(var3, var4); + this.clampPosition(); + if (!this.mPhysicalPosition.equals(this.mPrevPhysPosition)) { + var11 = var12; + if (this.mPixelListener != null) { + var11 = var12; + if (this.mPixelHeight > 0.0F) { + var11 = var12; + if (this.mPixelWidth > 0.0F) { + var15 = var19.getDownEvent(); + var16 = new TouchNavSpaceTracker.PhysicalMotionEvent(var1.getDeviceId(), var19.getPhysicalX(var15.getX()), var19.getPhysicalY(var15.getY()), var15.getEventTime()); + var2 = this.getPixelX(this.mPhysicalPosition.x); + var3 = this.getPixelY(this.mPhysicalPosition.y); + var11 = false | this.mPixelListener.onMove(var16, var14, var2, var3); + } + } + } + + this.mPrevPhysPosition.set(this.mPhysicalPosition); + } else { + var11 = false | true; + } + + var19.updatePrevValues(); + return var11; + } + + return false | true; + case 3: + var19.clear(); + return false; + default: + return false; + } + } + + return false; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + if (var2 != null && var2.getDevice() != null && (var2.getDevice().getSources() & 2097152) == 2097152) { + if (var2.getRepeatCount() == 0) { + this.checkForLongClick(var1, var2); + } + + if (this.mKeyEventListener != null) { + return this.mKeyEventListener.onKeyDown(var1, var2); + } + } + + return false; + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + if (var2 != null && var2.getDevice() != null && (var2.getDevice().getSources() & 2097152) == 2097152) { + if (var1 == 23) { + this.mHandler.removeMessages(0); + } + + if (this.mKeyEventListener != null) { + return this.mKeyEventListener.onKeyUp(var1, var2); + } + } + + return false; + } + + public void onPause() { + this.mHandler.removeMessages(0); + } + + public void setKeyEventListener(TouchNavSpaceTracker.KeyEventListener var1) { + this.mKeyEventListener = var1; + } + + public void setLPFEnabled(boolean var1) { + this.mLPFEnabled = var1; + } + + public void setPhysicalDensity(float var1) { + this.mPixelsPerMm = var1; + if (var1 > 0.0F) { + this.updatePhysicalSize(); + } + + } + + public void setPhysicalPosition(float var1, float var2) { + this.mPhysicalPosition.x = var1; + this.mPhysicalPosition.y = var2; + this.mPrevPhysPosition.x = var1; + this.mPrevPhysPosition.y = var2; + this.clampPosition(); + } + + public void setPhysicalSize(float var1, float var2) { + if (this.mPixelsPerMm <= 0.0F) { + this.setPhysicalSizeInternal(var1, var2); + } + } + + public void setPixelPosition(float var1, float var2) { + this.setPhysicalPosition(this.getPhysicalX(var1), this.getPhysicalY(var2)); + } + + public void setPixelSize(float var1, float var2) { + this.mPixelHeight = var2; + this.mPixelWidth = var1; + this.updatePhysicalSize(); + } + + public void setSensitivity(float var1) { + this.mSensitivity = var1; + this.configureFlicks(this.mUnscaledFlickMinDistance, this.mUnscaledFlickMaxDistance, this.mFlickMaxDuration); + } + + public void setTouchEventListener(TouchNavSpaceTracker.TouchEventListener var1) { + this.mPixelListener = var1; + } + + public void unblockMovement() { + this.mMovementBlockTime = 0L; + } + + public interface KeyEventListener { + boolean onKeyDown(int var1, KeyEvent var2); + + boolean onKeyLongPress(int var1, KeyEvent var2); + + boolean onKeyUp(int var1, KeyEvent var2); + } + + public static class PhysicalMotionEvent { + private final int mDeviceId; + private final long mTime; + // $FF: renamed from: mX float + private final float field_6; + // $FF: renamed from: mY float + private final float field_7; + + public PhysicalMotionEvent(int var1, float var2, float var3, long var4) { + this.mDeviceId = var1; + this.field_6 = var2; + this.field_7 = var3; + this.mTime = var4; + } + + public final InputDevice getDevice() { + return InputDevice.getDevice(this.getDeviceId()); + } + + public final int getDeviceId() { + return this.mDeviceId; + } + + public final long getTime() { + return this.mTime; + } + + public final float getX() { + return this.field_6; + } + + public final float getY() { + return this.field_7; + } + } + + public static class SimpleTouchEventListener implements TouchNavSpaceTracker.KeyEventListener, TouchNavSpaceTracker.TouchEventListener { + public boolean onDown(TouchNavSpaceTracker.PhysicalMotionEvent var1) { + return false; + } + + public boolean onFlick(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, int var3, int var4) { + return false; + } + + public boolean onFling(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4) { + return false; + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + return false; + } + + public boolean onKeyLongPress(int var1, KeyEvent var2) { + return false; + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + return false; + } + + public boolean onMove(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4) { + return false; + } + + public boolean onUp(TouchNavSpaceTracker.PhysicalMotionEvent var1, float var2, float var3) { + return false; + } + } + + public interface TouchEventListener { + boolean onDown(TouchNavSpaceTracker.PhysicalMotionEvent var1); + + boolean onFlick(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, int var3, int var4); + + boolean onFling(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4); + + boolean onMove(TouchNavSpaceTracker.PhysicalMotionEvent var1, TouchNavSpaceTracker.PhysicalMotionEvent var2, float var3, float var4); + + boolean onUp(TouchNavSpaceTracker.PhysicalMotionEvent var1, float var2, float var3); + } +} diff --git a/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java b/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java new file mode 100644 index 0000000..f924b8a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/google/leanback/ime/LeanbackImeService.java @@ -0,0 +1,305 @@ +package com.google.leanback.ime; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.inputmethodservice.InputMethodService; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import com.google.android.leanback.ime.LeanbackKeyboardController; +import com.google.android.leanback.ime.LeanbackSuggestionsFactory; +import com.google.android.leanback.ime.LeanbackUtils; + +public class LeanbackImeService extends InputMethodService { + private static final boolean DEBUG = false; + public static final String IME_CLOSE = "com.google.android.athome.action.IME_CLOSE"; + public static final String IME_OPEN = "com.google.android.athome.action.IME_OPEN"; + public static final int MAX_SUGGESTIONS = 10; + static final int MODE_FREE_MOVEMENT = 1; + static final int MODE_TRACKPAD_NAVIGATION = 0; + private static final int MSG_SUGGESTIONS_CLEAR = 123; + private static final int SUGGESTIONS_CLEAR_DELAY = 1000; + private static final String TAG = "LbImeService"; + private boolean mEnterSpaceBeforeCommitting; + private final Handler mHandler = new Handler() { + public void handleMessage(Message var1) { + if (var1.what == 123 && LeanbackImeService.this.mShouldClearSuggestions) { + LeanbackImeService.this.mSuggestionsFactory.clearSuggestions(); + LeanbackImeService.this.mKeyboardController.updateSuggestions(LeanbackImeService.this.mSuggestionsFactory.getSuggestions()); + LeanbackImeService.this.mShouldClearSuggestions = false; + } + + } + }; + private LeanbackKeyboardController.InputListener mInputListener = new LeanbackKeyboardController.InputListener() { + public void onEntry(int var1, int var2, CharSequence var3) { + LeanbackImeService.this.handleTextEntry(var1, var2, var3); + } + }; + private View mInputView; + private LeanbackKeyboardController mKeyboardController; + private boolean mShouldClearSuggestions = true; + private LeanbackSuggestionsFactory mSuggestionsFactory; + + @SuppressLint("NewApi") + public LeanbackImeService() { + if (!this.enableHardwareAcceleration()) { + Log.w("LbImeService", "Could not enable hardware acceleration"); + } + + } + + private void clearSuggestionsDelayed() { + if (!this.mSuggestionsFactory.shouldSuggestionsAmend()) { + this.mHandler.removeMessages(123); + this.mShouldClearSuggestions = true; + this.mHandler.sendEmptyMessageDelayed(123, 1000L); + } + + } + + private int getAmpersandLocation(InputConnection var1) { + String var4 = this.getEditorText(var1); + int var3 = var4.indexOf(64); + int var2 = var3; + if (var3 < 0) { + var2 = var4.length(); + } + + return var2; + } + + private int getCharLengthAfterCursor(InputConnection var1) { + int var2 = 0; + CharSequence var3 = var1.getTextAfterCursor(1000, 0); + if (var3 != null) { + var2 = var3.length(); + } + + return var2; + } + + private int getCharLengthBeforeCursor(InputConnection var1) { + int var2 = 0; + CharSequence var3 = var1.getTextBeforeCursor(1000, 0); + if (var3 != null) { + var2 = var3.length(); + } + + return var2; + } + + private String getEditorText(InputConnection var1) { + StringBuilder var2 = new StringBuilder(); + CharSequence var3 = var1.getTextBeforeCursor(1000, 0); + CharSequence var4 = var1.getTextAfterCursor(1000, 0); + if (var3 != null) { + var2.append(var3); + } + + if (var4 != null) { + var2.append(var4); + } + + return var2.toString(); + } + + private void handleTextEntry(int var1, int var2, CharSequence var3) { + InputConnection var5 = this.getCurrentInputConnection(); + boolean var4 = true; + if (var5 != null) { + boolean var6; + switch (var1) { + case 0: + this.clearSuggestionsDelayed(); + if (this.mEnterSpaceBeforeCommitting && this.mKeyboardController.enableAutoEnterSpace()) { + if (LeanbackUtils.isAlphabet(var2)) { + var5.commitText(" ", 1); + } + + this.mEnterSpaceBeforeCommitting = false; + } + + var5.commitText(var3, 1); + var6 = var4; + if (var2 == 46) { + this.mEnterSpaceBeforeCommitting = true; + var6 = var4; + } + break; + case 1: + this.clearSuggestionsDelayed(); + var5.deleteSurroundingText(1, 0); + this.mEnterSpaceBeforeCommitting = false; + var6 = var4; + break; + case 2: + case 6: + this.clearSuggestionsDelayed(); + if (!this.mSuggestionsFactory.shouldSuggestionsAmend()) { + var5.deleteSurroundingText(this.getCharLengthBeforeCursor(var5), this.getCharLengthAfterCursor(var5)); + } else { + var1 = this.getAmpersandLocation(var5); + var5.setSelection(var1, var1); + var5.deleteSurroundingText(0, this.getCharLengthAfterCursor(var5)); + } + + var5.commitText(var3, 1); + this.mEnterSpaceBeforeCommitting = true; + case 5: + this.sendDefaultEditorAction(false); + var6 = false; + break; + case 3: + case 4: + var3 = var5.getTextBeforeCursor(1000, 0); + if (var3 == null) { + var2 = 0; + } else { + var2 = var3.length(); + } + + if (var1 == 3) { + var1 = var2; + if (var2 > 0) { + var1 = var2 - 1; + } + } else { + var3 = var5.getTextAfterCursor(1000, 0); + var1 = var2; + if (var3 != null) { + var1 = var2; + if (var3.length() > 0) { + var1 = var2 + 1; + } + } + } + + var5.setSelection(var1, var1); + var6 = var4; + break; + case 7: + var5.performEditorAction(1); + var6 = false; + break; + case 8: + var5.performEditorAction(2); + var6 = false; + break; + default: + var6 = var4; + } + + if (this.mKeyboardController.areSuggestionsEnabled() && var6) { + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + return; + } + } + + } + + public View onCreateInputView() { + this.mInputView = this.mKeyboardController.getView(); + this.mInputView.requestFocus(); + return this.mInputView; + } + + public void onDisplayCompletions(CompletionInfo[] var1) { + if (this.mKeyboardController.areSuggestionsEnabled()) { + this.mShouldClearSuggestions = false; + this.mHandler.removeMessages(123); + this.mSuggestionsFactory.onDisplayCompletions(var1); + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + } + + } + + @Override + public boolean onEvaluateFullscreenMode() { + return false; + } + + @SuppressLint("MissingSuperCall") + @Override + public boolean onEvaluateInputViewShown() { + return true; + } + + public void onFinishInputView(boolean var1) { + super.onFinishInputView(var1); + this.sendBroadcast(new Intent("com.google.android.athome.action.IME_CLOSE")); + this.mSuggestionsFactory.clearSuggestions(); + } + + public boolean onGenericMotionEvent(MotionEvent var1) { + return this.isInputViewShown() && (var1.getSource() & 2097152) == 2097152 && this.mKeyboardController.onGenericMotionEvent(var1) ? true : + super.onGenericMotionEvent(var1); + } + + public void onHideIme() { + this.requestHideSelf(0); + } + + public void onInitializeInterface() { + this.mKeyboardController = new LeanbackKeyboardController(this, this.mInputListener); + this.mEnterSpaceBeforeCommitting = false; + this.mSuggestionsFactory = new LeanbackSuggestionsFactory(this, 10); + } + + public boolean onKeyDown(int var1, KeyEvent var2) { + return this.isInputViewShown() && this.mKeyboardController.onKeyDown(var1, var2) ? true : super.onKeyDown(var1, var2); + } + + public boolean onKeyUp(int var1, KeyEvent var2) { + return this.isInputViewShown() && this.mKeyboardController.onKeyUp(var1, var2) ? true : super.onKeyUp(var1, var2); + } + + public boolean onShowInputRequested(int var1, boolean var2) { + return true; + } + + public int onStartCommand(Intent var1, int var2, int var3) { + if (var1 != null) { + super.onStartCommand(var1, var2, var3); + if (var1.getBooleanExtra("restart", false)) { + Log.e("LeanbackImeService", "Service->onStartCommand: trying to restart service"); + LeanbackKeyboardController var4 = this.mKeyboardController; + if (var4 != null) { + var4.updateAddonKeyboard(); + } + } + } + + return 1; + } + + public void onStartInput(EditorInfo var1, boolean var2) { + super.onStartInput(var1, var2); + this.mEnterSpaceBeforeCommitting = false; + this.mSuggestionsFactory.onStartInput(var1); + this.mKeyboardController.onStartInput(var1); + } + + public void onStartInputView(EditorInfo var1, boolean var2) { + super.onStartInputView(var1, var2); + this.mKeyboardController.onStartInputView(); + this.sendBroadcast(new Intent("com.google.android.athome.action.IME_OPEN")); + if (this.mKeyboardController.areSuggestionsEnabled()) { + this.mSuggestionsFactory.createSuggestions(); + this.mKeyboardController.updateSuggestions(this.mSuggestionsFactory.getSuggestions()); + InputConnection var4 = this.getCurrentInputConnection(); + if (var4 != null) { + String var3 = this.getEditorText(var4); + var4.deleteSurroundingText(this.getCharLengthBeforeCursor(var4), this.getCharLengthAfterCursor(var4)); + var4.commitText(var3, 1); + } + } + + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java new file mode 100644 index 0000000..b05585b --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/LaunchAppActivity.java @@ -0,0 +1,77 @@ +// +// Decompiled by Procyon v0.5.30 +// + +package com.liskovsoft.inputchooser; + +import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.widget.Toast; +import android.os.Bundle; +import android.content.ComponentName; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ActivityInfo; +import android.content.Intent; +import android.app.Activity; + +public class LaunchAppActivity extends Activity +{ + @SuppressLint("WrongConstant") + private void addIntentFlags(final Intent intent) { + intent.addFlags(67108864); + intent.addFlags(536870912); + } + + @SuppressLint("WrongConstant") + private ActivityInfo getCurrentActivityInfo() { + try { + return this.getPackageManager().getActivityInfo(this.getComponentName(), 129); + } + catch (NameNotFoundException ex) { + ex.printStackTrace(); + this.makeLongToast(ex.getLocalizedMessage(), 10); + return null; + } + } + + private void launchApp() { + final Intent intent = this.makeIntent(this.getCurrentActivityInfo()); + this.addIntentFlags(intent); + this.startIntent(intent); + this.finish(); + } + + private Intent makeIntent(final ActivityInfo activityInfo) { + final Bundle metaData = activityInfo.metaData; + final Intent intent = new Intent(); + if (metaData.getString("intent") != null) { + intent.setAction(metaData.getString("intent")); + return intent; + } + intent.setComponent(new ComponentName(metaData.getString("package"), metaData.getString("class"))); + return intent; + } + + private void makeLongToast(final String s, int i) { + int n; + for (n = i / 2, i = 0; i < n; ++i) { + Toast.makeText(this, s, Toast.LENGTH_LONG).show(); + } + } + + private void startIntent(final Intent intent) { + try { + this.startActivity(intent); + } + catch (ActivityNotFoundException ex) { + ex.printStackTrace(); + this.makeLongToast(ex.getLocalizedMessage(), 10); + } + } + + protected void onCreate(final Bundle bundle) { + super.onCreate(bundle); + this.launchApp(); + } +} diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java new file mode 100644 index 0000000..3b9b29a --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/inputchooser/RestartServiceReceiver.java @@ -0,0 +1,77 @@ +package com.liskovsoft.inputchooser; +import android.app.*; +import android.content.*; +import android.content.res.*; +import android.net.*; +import android.util.*; +import android.view.inputmethod.*; + +import java.util.*; + +public class RestartServiceReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + sendMessageToService(context); + + } + + private void sendMessageToService(Context context) { + Log.e("RestartServiceReceiver", "Sending message to the service"); + Intent intent = new Intent(); + intent.setComponent(new ComponentName(getPackageName(context), "com.google.leanback.ime.LeanbackImeService")); + intent.putExtra("restart", true); + context.startService(intent); + } + + private void onStartCommand(Intent intent) { + if (intent.getBooleanExtra("restart", false)) { + System.out.println("Restarting service"); + } + } + + private void killThisPackageProcess(Context context) { + Log.e("RestartServiceReceiver", "Attempting to kill org.liskovsoft.androidtv.rukeyboard process"); + ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + activityManager.killBackgroundProcesses(getPackageName(context)); + } + + private void restartService(Context context) { + // START YOUR SERVICE HERE + Log.e("RestartServiceReceiver", "Restarting Service"); + //final Class serviceClass = classForName("com.google.leanback.ime.LeanbackImeService"); + //Intent serviceIntent = new Intent(context.getApplicationContext(), serviceClass); + Intent serviceIntent = new Intent(); + serviceIntent.setComponent(new ComponentName(getPackageName(context), "com.google.leanback.ime.LeanbackImeService")); + context.stopService(serviceIntent); + context.startService(serviceIntent); + } + + private Class classForName(String clazz) { + Class serviceClass; + try { + serviceClass = Class.forName(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return serviceClass; + } + + private void switchLocale(Context ctx) { + Log.e("RestartServiceReceiver", "Trying to switch locale back and forward"); + Locale savedLocale = Locale.getDefault(); + trySwitchLocale(ctx, new Locale("ru")); + trySwitchLocale(ctx, savedLocale); + } + + private void trySwitchLocale(Context ctx, Locale locale) { + Locale.setDefault(locale); + Configuration config = ctx.getResources().getConfiguration(); + config.locale = locale; + ctx.getResources().updateConfiguration(config, + ctx.getResources().getDisplayMetrics()); + } + + private String getPackageName(Context ctx) { + return ctx.getPackageName(); + } +} \ No newline at end of file diff --git a/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java b/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java new file mode 100644 index 0000000..2a061b9 --- /dev/null +++ b/leankeykeyboard/src/main/java/com/liskovsoft/keyboardaddons/KeyboardManager.java @@ -0,0 +1,55 @@ +package com.liskovsoft.keyboardaddons; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import com.anysoftkeyboard.keyboards.KeyboardAddOnAndBuilder; +import com.anysoftkeyboard.keyboards.KeyboardFactory; + +import java.util.ArrayList; +import java.util.List; + +public class KeyboardManager { + private final Keyboard mEnglishKeyboard; + private final Context mContext; + private final List mKeyboardBuilders; + private final List mAllKeyboards; + private final KeyboardFactory mKeyboardFactory; + private int mKeyboardIndex = 0; + + public KeyboardManager(Context ctx, int defaultKeyboard1) { + this(ctx, new Keyboard(ctx, defaultKeyboard1)); + } + + public KeyboardManager(Context ctx, Keyboard englishKeyboard) { + mContext = ctx; + mEnglishKeyboard = englishKeyboard; + mKeyboardFactory = new KeyboardFactory(); + + mKeyboardBuilders = mKeyboardFactory.getAllAvailableKeyboards(mContext); + mAllKeyboards = buildAllKeyboards(); + } + + private List buildAllKeyboards() { + List keyboards = new ArrayList<>(); + keyboards.add(mEnglishKeyboard); + if (!mKeyboardBuilders.isEmpty()) { + for (KeyboardAddOnAndBuilder builder : mKeyboardBuilders) { + keyboards.add(builder.createKeyboard()); + } + } + return keyboards; + } + + public Keyboard getNextKeyboard() { + ++mKeyboardIndex; + mKeyboardIndex = mKeyboardIndex < mAllKeyboards.size() ? mKeyboardIndex : 0; + + Keyboard kbd = mAllKeyboards.get(mKeyboardIndex); + if (kbd == null) { + throw new UnsupportedOperationException(String.format("Keyboard %s not initialized", mKeyboardIndex)); + } + + + return kbd; + } +} diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000..a249644 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_accent_close.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000..185e137 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_alphabet.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000..b046e64 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000..fb2b994 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_left_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000..ab34b65 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_right_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_lock_on.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_lock_on.png new file mode 100644 index 0000000..9dcf038 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_lock_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000..916bf9c Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_on.png new file mode 100644 index 0000000..3cf1f29 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_shift_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space.png new file mode 100644 index 0000000..a0e5c54 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space_ru.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space_ru.png new file mode 100644 index 0000000..0c18c28 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space_ru.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000..2d16197 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png new file mode 100644 index 0000000..08517dd Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_available.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png new file mode 100644 index 0000000..400d9d7 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_off.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_off.png new file mode 100644 index 0000000..9d03a14 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_recording.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_recording.png new file mode 100644 index 0000000..a21b987 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_recording.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/key_selector.9.png new file mode 100644 index 0000000..7a3c25e Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/key_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/touch_selector.9.png new file mode 100644 index 0000000..fe2bdb2 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/touch_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_dark.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_dark.png new file mode 100644 index 0000000..7bebe92 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_dark.png differ diff --git a/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000..bed1747 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-hdpi-v4/vs_reactive_light.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000..58a78e7 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_accent_close.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000..049aba6 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_alphabet.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000..bf41235 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000..e843536 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_left_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000..3e09943 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_right_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_lock_on.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_lock_on.png new file mode 100644 index 0000000..42e965d Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_lock_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000..11a0928 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_on.png new file mode 100644 index 0000000..da80cab Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_shift_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png new file mode 100644 index 0000000..a0e5c54 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space_ru.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space_ru.png new file mode 100644 index 0000000..0c18c28 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space_ru.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000..f731ac3 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png new file mode 100644 index 0000000..87011c9 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_available.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png new file mode 100644 index 0000000..60f7a0c Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png new file mode 100644 index 0000000..ad8de62 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_recording.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_recording.png new file mode 100644 index 0000000..f163319 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_recording.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png new file mode 100644 index 0000000..1a17e00 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png new file mode 100644 index 0000000..ed4ba36 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/touch_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png new file mode 100644 index 0000000..f30f7ae Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_dark.png differ diff --git a/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000..fac1240 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-mdpi-v4/vs_reactive_light.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png new file mode 100644 index 0000000..2786486 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000..612aef5 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_accent_close.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000..5fb3597 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_alphabet.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000..9a55177 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000..3a7b4a1 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_left_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000..470b6e0 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_right_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_lock_on.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_lock_on.png new file mode 100644 index 0000000..b16ea50 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_lock_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000..16fa7d6 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_on.png new file mode 100644 index 0000000..007bdd3 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_shift_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png new file mode 100644 index 0000000..a0e5c54 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space_ru.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space_ru.png new file mode 100644 index 0000000..0c18c28 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space_ru.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000..076e2ca Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_symbols.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_launcher.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_launcher.png new file mode 100644 index 0000000..1c557d5 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_launcher.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_available.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_available.png new file mode 100644 index 0000000..bb38817 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_available.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_focus.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_focus.png new file mode 100644 index 0000000..721152c Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_focus.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png new file mode 100644 index 0000000..c0752ea Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_recording.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_recording.png new file mode 100644 index 0000000..34b1167 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_recording.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png new file mode 100644 index 0000000..921f00b Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/key_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/touch_selector.9.png new file mode 100644 index 0000000..013c8a5 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/touch_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_dark.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_dark.png new file mode 100644 index 0000000..e55967c Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_dark.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png new file mode 100644 index 0000000..16d4a76 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xhdpi-v4/vs_reactive_light.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/banner_app.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/banner_app.png new file mode 100644 index 0000000..2786486 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/banner_app.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_accent_close.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_accent_close.png new file mode 100644 index 0000000..8526a43 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_accent_close.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png new file mode 100644 index 0000000..16f71a0 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_alphabet.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png new file mode 100644 index 0000000..2704ab0 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_delete.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png new file mode 100644 index 0000000..395a640 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_left_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png new file mode 100644 index 0000000..a750f2e Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_right_arrow.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_lock_on.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_lock_on.png new file mode 100644 index 0000000..bd6385b Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_lock_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_off.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_off.png new file mode 100644 index 0000000..db5bf4e Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_off.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png new file mode 100644 index 0000000..f128b13 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_shift_on.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png new file mode 100644 index 0000000..a0e5c54 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space_ru.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space_ru.png new file mode 100644 index 0000000..0c18c28 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_space_ru.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png new file mode 100644 index 0000000..e8315a5 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_ime_symbols.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_launcher.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_launcher.png new file mode 100644 index 0000000..1c557d5 Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/ic_launcher.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/key_selector.9.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/key_selector.9.png new file mode 100644 index 0000000..f6613ed Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/key_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png new file mode 100644 index 0000000..d0a06cf Binary files /dev/null and b/leankeykeyboard/src/main/res/drawable-xxhdpi-v4/touch_selector.9.png differ diff --git a/leankeykeyboard/src/main/res/drawable/selector_caps_shift.xml b/leankeykeyboard/src/main/res/drawable/selector_caps_shift.xml new file mode 100644 index 0000000..6215829 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/selector_caps_shift.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml new file mode 100644 index 0000000..dcae5d2 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_off_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml new file mode 100644 index 0000000..dcae5d2 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_on_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml b/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml new file mode 100644 index 0000000..f583d88 --- /dev/null +++ b/leankeykeyboard/src/main/res/drawable/vs_micbtn_rec_selector.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/leankeykeyboard/src/main/res/layout/candidate.xml b/leankeykeyboard/src/main/res/layout/candidate.xml new file mode 100644 index 0000000..c65ffd8 --- /dev/null +++ b/leankeykeyboard/src/main/res/layout/candidate.xml @@ -0,0 +1,5 @@ + + +