initial commit
22
.gitignore
vendored
Normal file
@@ -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
|
||||
3
README.md
Normal file
@@ -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)
|
||||
40
build.gradle
Normal file
@@ -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
|
||||
}
|
||||
13
gradle.properties
Normal file
@@ -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
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -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
|
||||
160
gradlew
vendored
Normal file
@@ -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 "$@"
|
||||
90
gradlew.bat
vendored
Normal file
@@ -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
|
||||
1
leankeykeyboard/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
32
leankeykeyboard/build.gradle
Normal file
@@ -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
|
||||
}
|
||||
25
leankeykeyboard/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
||||
33
leankeykeyboard/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.liskovsoft.leankeykeyboard">
|
||||
<uses-feature android:name="android.software.leanback" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
|
||||
<application android:banner="@drawable/banner_app" android:label="LeanKey Keyboard" android:icon="@drawable/ic_launcher">
|
||||
<activity android:launchMode="singleTop" android:name="com.liskovsoft.inputchooser.LaunchAppActivity">
|
||||
<meta-data android:name="package" android:value="com.android.settings"/>
|
||||
<meta-data android:name="class" android:value="com.android.settings.Settings"/>
|
||||
<meta-data android:name="intent" android:value="android.settings.INPUT_METHOD_SETTINGS"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:label="LeanKey Keyboard" android:name="com.google.leanback.ime.LeanbackImeService" android:permission="android.permission.BIND_INPUT_METHOD">
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||
</service>
|
||||
<receiver android:name="com.liskovsoft.inputchooser.RestartServiceReceiver" android:exported="true" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_ADDED" />
|
||||
<action android:name="android.intent.action.PACKAGE_REMOVED" />
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<data android:scheme="package"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<Context> 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<AddOnImpl> mAddOnWeakReference;
|
||||
private final SparseIntArray mAttributesMapping = new SparseIntArray();
|
||||
private final SparseArrayCompat<int[]> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<E extends AddOn> {
|
||||
|
||||
private static final class AddOnsComparator implements Comparator<AddOn> {
|
||||
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<AddOnsFactory<?>> 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<E> mAddOns = new ArrayList<>();
|
||||
private final HashMap<String, E> 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<E> 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<E> local = getAddOnsFromResId(askContext, askContext, mBuildInAddOnsResId);
|
||||
//for (E addon : local) {
|
||||
// Logger.d(TAG, "Local add-on %s loaded", addon.getId());
|
||||
//}
|
||||
//mAddOns.addAll(local);
|
||||
ArrayList<E> 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<E> newAddOns) {
|
||||
for (E addOn : newAddOns)
|
||||
mAddOnsById.put(addOn.getId(), addOn);
|
||||
}
|
||||
|
||||
private ArrayList<E> getExternalAddOns(Context askContext) {
|
||||
final ArrayList<E> externalAddOns = new ArrayList<>();
|
||||
|
||||
if (!mReadExternalPacksToo)//this will disable external packs (API careful stage)
|
||||
return externalAddOns;
|
||||
|
||||
final List<ResolveInfo> 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<E> 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<E> 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<E> 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<E> parseAddOnsFromXml(Context askContext, Context context, XmlPullParser xml) {
|
||||
final ArrayList<E> 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);
|
||||
}
|
||||
@@ -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<Integer> 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<remoteMappedStyleable.length; i++) {
|
||||
remoteMappedStyleable[i] = styleableIdList.get(i);
|
||||
}
|
||||
|
||||
return remoteMappedStyleable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.anysoftkeyboard.addons.AddOn;
|
||||
import com.anysoftkeyboard.addons.AddOnImpl;
|
||||
|
||||
public class KeyboardAddOnAndBuilder extends AddOnImpl {
|
||||
|
||||
public static final String KEYBOARD_PREF_PREFIX = "keyboard_";
|
||||
|
||||
private final int mResId;
|
||||
private final int mLandscapeResId;
|
||||
//private final int mIconResId;
|
||||
private final String mDefaultDictionary;
|
||||
private final int mQwertyTranslationId;
|
||||
private final String mAdditionalIsLetterExceptions;
|
||||
private final String mSentenceSeparators;
|
||||
private final boolean mKeyboardDefaultEnabled;
|
||||
|
||||
public KeyboardAddOnAndBuilder(Context askContext, Context packageContext, String id, int nameResId,
|
||||
int layoutResId, int landscapeLayoutResId,
|
||||
String defaultDictionary, int iconResId,
|
||||
int physicalTranslationResId,
|
||||
String additionalIsLetterExceptions,
|
||||
String sentenceSeparators,
|
||||
String description,
|
||||
int keyboardIndex,
|
||||
boolean keyboardDefaultEnabled) {
|
||||
super(askContext, packageContext, KEYBOARD_PREF_PREFIX + id, nameResId, description, keyboardIndex);
|
||||
|
||||
mResId = layoutResId;
|
||||
if (landscapeLayoutResId == AddOn.INVALID_RES_ID) {
|
||||
mLandscapeResId = mResId;
|
||||
} else {
|
||||
mLandscapeResId = landscapeLayoutResId;
|
||||
}
|
||||
|
||||
mDefaultDictionary = defaultDictionary;
|
||||
//mIconResId = iconResId;
|
||||
mAdditionalIsLetterExceptions = additionalIsLetterExceptions;
|
||||
mSentenceSeparators = sentenceSeparators;
|
||||
mQwertyTranslationId = physicalTranslationResId;
|
||||
mKeyboardDefaultEnabled = keyboardDefaultEnabled;
|
||||
}
|
||||
|
||||
public boolean getKeyboardDefaultEnabled() {
|
||||
return mKeyboardDefaultEnabled;
|
||||
}
|
||||
|
||||
public String getKeyboardLocale() {
|
||||
return mDefaultDictionary;
|
||||
}
|
||||
|
||||
public String getSentenceSeparators() {
|
||||
return mSentenceSeparators;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public android.inputmethodservice.Keyboard createKeyboard() {
|
||||
Context remoteContext = getPackageContext();
|
||||
if (remoteContext == null) return null;
|
||||
return new android.inputmethodservice.Keyboard(remoteContext, mLandscapeResId);
|
||||
}
|
||||
|
||||
//@Nullable
|
||||
//public android.inputmethodservice.Keyboard createKeyboard(Context askContext, @Keyboard.KeyboardRowModeId int mode) {
|
||||
// Context remoteContext = getPackageContext();
|
||||
// if (remoteContext == null) return null;
|
||||
// return new ExternalAnyKeyboard(this, askContext, remoteContext, mResId, mLandscapeResId, getId(), getName(), mIconResId, mQwertyTranslationId, mDefaultDictionary, mAdditionalIsLetterExceptions, mSentenceSeparators, mode);
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.inputmethodservice.*;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.anysoftkeyboard.addons.AddOn;
|
||||
import com.anysoftkeyboard.addons.AddOnsFactory;
|
||||
import com.anysoftkeyboard.utils.Logger;
|
||||
//import com.menny.android.anysoftkeyboard.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class KeyboardFactory extends AddOnsFactory<KeyboardAddOnAndBuilder> {
|
||||
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<KeyboardAddOnAndBuilder> getAllAvailableKeyboards(Context askContext) {
|
||||
return getAllAddOns(askContext);
|
||||
}
|
||||
|
||||
public List<KeyboardAddOnAndBuilder> getEnabledKeyboards(Context askContext) {
|
||||
final List<KeyboardAddOnAndBuilder> 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<KeyboardAddOnAndBuilder> 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<KeyboardAddOnAndBuilder> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.anysoftkeyboard.utils;
|
||||
|
||||
public class BuildConfig {
|
||||
public final static boolean TESTING_BUILD = true;
|
||||
public final static boolean DEBUG = true;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String> getAllLogLinesList() {
|
||||
ArrayList<String> 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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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().
|
||||
* <p/>
|
||||
* 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 <em>after</em> 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<String, Object> 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 <em>after</em> 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<Object> 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
|
||||
* <em>after</em> 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<Object> 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 <em>after</em> 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 <em>at</em> 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 <string>: " + parser.getName());
|
||||
} else if (eventType == XmlPullParser.TEXT) {
|
||||
value += parser.getText();
|
||||
} else if (eventType == XmlPullParser.START_TAG) {
|
||||
throw new XmlPullParserException("Unexpected start tag in <string>: " + parser.getName());
|
||||
}
|
||||
}
|
||||
throw new XmlPullParserException(
|
||||
"Unexpected end of document in <string>");
|
||||
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) {
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <a href="mailto:bayard@generationjava.com">Henri Yandell</a>
|
||||
* @author <a href="mailto:menny|AT| evendanan{dot} net">Menny Even Danan - just
|
||||
* added some features on Henri's initial version</a>
|
||||
* @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<String> 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<String>();
|
||||
this.attrs = new StringBuffer();
|
||||
if (addXmlPrefix)
|
||||
this.writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\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("</");
|
||||
this.writer.write(name);
|
||||
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());
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<LeanbackKeyboardController.KeyChange> 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<String> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Key> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<RecognizerView.SavedState> CREATOR = new Creator<RecognizerView.SavedState>() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<TouchNavMotionTracker> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<KeyboardAddOnAndBuilder> mKeyboardBuilders;
|
||||
private final List<Keyboard> 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<Keyboard> buildAllKeyboards() {
|
||||
List<Keyboard> 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;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 360 B |
|
After Width: | Height: | Size: 975 B |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_delete.png
Normal file
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 541 B |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_space.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_ime_symbols.png
Normal file
|
After Width: | Height: | Size: 957 B |
|
After Width: | Height: | Size: 6.7 KiB |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_focus.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/ic_voice_off.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
BIN
leankeykeyboard/src/main/res/drawable-hdpi-v4/key_selector.9.png
Normal file
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 545 B |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 342 B |
|
After Width: | Height: | Size: 717 B |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_delete.png
Normal file
|
After Width: | Height: | Size: 707 B |
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 265 B |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 532 B |
|
After Width: | Height: | Size: 400 B |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_space.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_ime_symbols.png
Normal file
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_focus.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/ic_voice_off.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
BIN
leankeykeyboard/src/main/res/drawable-mdpi-v4/key_selector.9.png
Normal file
|
After Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 399 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
BIN
leankeykeyboard/src/main/res/drawable-xhdpi-v4/banner_app.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 536 B |
|
After Width: | Height: | Size: 1.4 KiB |
BIN
leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_delete.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 364 B |
|
After Width: | Height: | Size: 729 B |
|
After Width: | Height: | Size: 901 B |
|
After Width: | Height: | Size: 584 B |
BIN
leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_ime_space.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
BIN
leankeykeyboard/src/main/res/drawable-xhdpi-v4/ic_voice_off.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 670 B |
|
After Width: | Height: | Size: 711 B |
|
After Width: | Height: | Size: 5.6 KiB |