Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b7d7be6c0 | ||
|
|
ee331d05b1 | ||
|
|
e747337fbf | ||
|
|
b37ca64272 | ||
|
|
68a3ccc9eb | ||
|
|
2d2e9c9be0 | ||
|
|
ed2d0735ce | ||
|
|
fd15a6e6c4 | ||
|
|
e47dd7b9ab | ||
|
|
274237f854 | ||
|
|
c86753b9a5 | ||
|
|
ba7f14b6e3 | ||
|
|
74ffcac5d6 | ||
|
|
6dcfdc4233 | ||
|
|
6bce604262 | ||
|
|
65fb0b4e2a | ||
|
|
2867e6b2bb | ||
|
|
769e659eb8 | ||
|
|
5b4d53bd7c | ||
|
|
12aefa93c8 | ||
|
|
e5a4a7cab1 | ||
|
|
ced8bf5fbe | ||
|
|
277dc084ea | ||
|
|
10266d9895 | ||
|
|
7b63dead60 | ||
|
|
fe4b334bc1 | ||
|
|
879d0320bc | ||
|
|
0b4810ba70 | ||
|
|
66f54ee592 | ||
|
|
11996bcb88 | ||
|
|
8e97fce0aa | ||
|
|
3936d9add9 | ||
|
|
6f86d11213 | ||
|
|
3969fe1404 | ||
|
|
9cce45faf9 | ||
|
|
6304ea2eef | ||
|
|
a812642cf7 | ||
|
|
9379bd51ac | ||
|
|
828a90ae62 | ||
|
|
e392c1f4a6 | ||
|
|
f45140dc41 | ||
|
|
e0035e0027 | ||
|
|
86b551cc5a | ||
|
|
665a9e6a4a | ||
|
|
ed303d8465 | ||
|
|
776d112578 | ||
|
|
1d302820c0 | ||
|
|
c214696e6d | ||
|
|
e4a60e26a8 | ||
|
|
ed0ffbad61 | ||
|
|
3a88d30d8a | ||
|
|
291ad35a12 | ||
|
|
3e036da762 | ||
|
|
b2c96a3ea8 | ||
|
|
9ba3721eeb | ||
|
|
4256d160bb | ||
|
|
13953610d9 | ||
|
|
283f40057b | ||
|
|
14cd9e7f4b | ||
|
|
784b7e4948 | ||
|
|
f521199d9d | ||
|
|
74b05a738e | ||
|
|
9ff6581824 | ||
|
|
898c223bc3 | ||
|
|
026ac4aff5 | ||
|
|
24496d6f6e | ||
|
|
d3277288d5 | ||
|
|
446580f7a2 | ||
|
|
a4caa58d78 | ||
|
|
26fe3a04f9 | ||
|
|
23d00390fd | ||
|
|
38fd876dd2 | ||
|
|
0585964c8b | ||
|
|
6d1e314f9c | ||
|
|
6040c66cd8 | ||
|
|
c4ec509f96 | ||
|
|
6cb41d98f1 | ||
|
|
741c75b5f7 | ||
|
|
702485459d | ||
|
|
2dec1c327d | ||
|
|
3b2b7a2d47 | ||
|
|
61c721520b | ||
|
|
aee70bb618 | ||
|
|
65fe03ebd1 | ||
|
|
1f4e245d7f | ||
|
|
3d9cd08dc3 | ||
|
|
96ae349a44 | ||
|
|
ac7019e08d | ||
|
|
ac3caf97df | ||
|
|
85f332fe39 | ||
|
|
9da0159b4e | ||
|
|
44d486be63 | ||
|
|
3096e4da11 | ||
|
|
8995744a8b | ||
|
|
b3e707ff91 | ||
|
|
7a12dd02a9 | ||
|
|
2b4fe028a5 | ||
|
|
dcab96add4 | ||
|
|
eee71b0f50 | ||
|
|
4de4b24c43 | ||
|
|
5953d33a34 | ||
|
|
d56a20c7d3 | ||
|
|
b4a9488547 | ||
|
|
909136ddf1 | ||
|
|
60209c970d | ||
|
|
805c7fe39d | ||
|
|
3b7f54eb44 | ||
|
|
8973ebfd8f | ||
|
|
a8726a87d1 | ||
|
|
7ac2a35a0d | ||
|
|
cbcceb9d84 | ||
|
|
9f4e106d33 | ||
|
|
d6f6b58ba3 | ||
|
|
828bfd781d | ||
|
|
d99142ac81 | ||
|
|
8e58e4321a | ||
|
|
9d83dd3bb7 | ||
|
|
b985141e1c | ||
|
|
e5a51a0067 | ||
|
|
bfb0a63e6d | ||
|
|
7eb5a9fae8 | ||
|
|
08bcb92c8e | ||
|
|
b08484ed84 | ||
|
|
4122db9cfd | ||
|
|
3057f9d18d | ||
|
|
9fd9e3597c | ||
|
|
c84c1a2dca | ||
|
|
ccbf38c9d8 | ||
|
|
2ba9265b33 | ||
|
|
648a32c1f2 | ||
|
|
482d3267ea | ||
|
|
8f57959364 | ||
|
|
2ae70bf095 | ||
|
|
5bcfa33d47 | ||
|
|
6d739bc761 | ||
|
|
bb6b39e3f9 | ||
|
|
c2cca864e3 | ||
|
|
636309177b | ||
|
|
f838f0e5ac | ||
|
|
41c62c5d88 | ||
|
|
e938ec83e6 | ||
|
|
7c5b61954e | ||
|
|
064f289a34 | ||
|
|
da9bcca811 | ||
|
|
03f94087b1 | ||
|
|
815f2e9dc4 | ||
|
|
b746ec583e | ||
|
|
98ea92c413 | ||
|
|
fa40df4ee3 | ||
|
|
67ab671508 | ||
|
|
1dfdd7c273 | ||
|
|
01cd1ac0b6 | ||
|
|
d45750bb92 | ||
|
|
680829d404 | ||
|
|
4659ea4914 | ||
|
|
204d4509eb | ||
|
|
26ac9ed4bc | ||
|
|
1c9208502e | ||
|
|
212fa95832 | ||
|
|
328214a9f5 | ||
|
|
71debca6b9 | ||
|
|
2afc113011 | ||
|
|
cf76051cde | ||
|
|
8ba21d293e | ||
|
|
9c75227113 | ||
|
|
5fd594fd86 | ||
|
|
a45da5bfe9 | ||
|
|
9112b1c3d6 | ||
|
|
a61c388462 | ||
|
|
7de0f879c5 | ||
|
|
ca591eae26 | ||
|
|
0cf93b0595 | ||
|
|
f3fb808c4f | ||
|
|
9106bf5136 | ||
|
|
be3f4c9bd7 | ||
|
|
ac764617f2 | ||
|
|
28ba0fdadb | ||
|
|
e39b81bfde | ||
|
|
dc3464db79 | ||
|
|
67628e1b98 | ||
|
|
d84739fcb6 | ||
|
|
8ec9d8e0d5 | ||
|
|
3441870e12 | ||
|
|
2678c3c474 | ||
|
|
2e8a66d6ec | ||
|
|
b2ce34d9c0 | ||
|
|
a7192d7c66 | ||
|
|
c77ce45df8 | ||
|
|
08542c941d | ||
|
|
93deda49c5 | ||
|
|
58adaede2e | ||
|
|
b5c2aa564e | ||
|
|
d11649c0dd | ||
|
|
d21a0084ee | ||
|
|
f0d06ba01d | ||
|
|
49b9d7d136 | ||
|
|
1fdbcc6de9 | ||
|
|
34719a47e0 | ||
|
|
bfe7ab3cc3 | ||
|
|
6660500492 | ||
|
|
19442560f9 | ||
|
|
cae438b3dc | ||
|
|
9ff138428a | ||
|
|
ae0141851d | ||
|
|
7b47303911 | ||
|
|
a0d8ba27e3 | ||
|
|
5b7e226dac | ||
|
|
5d05071580 | ||
|
|
a150462b9e | ||
|
|
c072d59212 | ||
|
|
8407377a62 | ||
|
|
0fd76b0c47 | ||
|
|
0cc38c8e57 | ||
|
|
87da1e34c6 | ||
|
|
ff7676b0f9 | ||
|
|
73181178c2 | ||
|
|
e72deb7b3b | ||
|
|
0199b5c6b5 | ||
|
|
b7abf58f19 | ||
|
|
a6a59ce7a4 | ||
|
|
0b57134da8 | ||
|
|
5a03d5f69e | ||
|
|
9a784be210 | ||
|
|
8b291875d9 | ||
|
|
923ac85acb | ||
|
|
fb4427ea9d | ||
|
|
3261cd8b1d | ||
|
|
356abfe94f | ||
|
|
e2fbf69d8d | ||
|
|
3a6e81525a | ||
|
|
07ce787151 | ||
|
|
55fe44ab24 | ||
|
|
32088a64fd | ||
|
|
dc79d361d8 | ||
|
|
cf5799edb0 | ||
|
|
3b0e2d1401 | ||
|
|
22929952c6 | ||
|
|
15f8d516ff | ||
|
|
9a476ca8bb | ||
|
|
1185448d19 | ||
|
|
c106b65fa8 | ||
|
|
8508b25c9c | ||
|
|
4b4f079ddd | ||
|
|
fb64978643 | ||
|
|
782c606a63 | ||
|
|
5be99bf8fa | ||
|
|
6ff10eaf75 | ||
|
|
b415505ff4 | ||
|
|
eaf9f2ea22 | ||
|
|
33c65a0678 | ||
|
|
b82d5bdb55 | ||
|
|
4f65c5b308 | ||
|
|
e3bebe381d | ||
|
|
bc81c901c5 | ||
|
|
5e10b0522f | ||
|
|
08266591b8 | ||
|
|
28b65521de | ||
|
|
bfa1667e03 | ||
|
|
972a70f285 | ||
|
|
0b8ac4ff48 | ||
|
|
cc45c099d8 | ||
|
|
ce710fbb4d | ||
|
|
6fff942296 | ||
|
|
85237beecf | ||
|
|
c08fc9feb7 | ||
|
|
5e93ab5953 | ||
|
|
fea001b575 | ||
|
|
8b16f6a350 | ||
|
|
a95a704207 | ||
|
|
1fa65de48a | ||
|
|
7f8525d65e | ||
|
|
2c4f21b305 | ||
|
|
2809a58f88 | ||
|
|
15be577f70 | ||
|
|
fffb32df92 |
2
.gitignore
vendored
@@ -1,7 +1,9 @@
|
||||
/screen.png
|
||||
notes.txt
|
||||
/other
|
||||
/files
|
||||
/misc
|
||||
/releases
|
||||
tmp/
|
||||
*_bak*
|
||||
*_tmp
|
||||
|
||||
595
LICENSE.md
Normal file
@@ -0,0 +1,595 @@
|
||||
GNU General Public License
|
||||
==========================
|
||||
|
||||
_Version 3, 29 June 2007_
|
||||
_Copyright © 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
## Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for software and other
|
||||
kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away
|
||||
your freedom to share and change the works. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change all versions of a
|
||||
program--to make sure it remains free software for all its users. We, the Free
|
||||
Software Foundation, use the GNU General Public License for most of our software; it
|
||||
applies also to any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General
|
||||
Public Licenses are designed to make sure that you have the freedom to distribute
|
||||
copies of free software (and charge for them if you wish), that you receive source
|
||||
code or can get it if you want it, that you can change the software or use pieces of
|
||||
it in new free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you these rights or
|
||||
asking you to surrender the rights. Therefore, you have certain responsibilities if
|
||||
you distribute copies of the software, or if you modify it: responsibilities to
|
||||
respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee,
|
||||
you must pass on to the recipients the same freedoms that you received. You must make
|
||||
sure that they, too, receive or can get the source code. And you must show them these
|
||||
terms so they know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: **(1)** assert
|
||||
copyright on the software, and **(2)** offer you this License giving you legal permission
|
||||
to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains that there is
|
||||
no warranty for this free software. For both users' and authors' sake, the GPL
|
||||
requires that modified versions be marked as changed, so that their problems will not
|
||||
be attributed erroneously to authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run modified versions of
|
||||
the software inside them, although the manufacturer can do so. This is fundamentally
|
||||
incompatible with the aim of protecting users' freedom to change the software. The
|
||||
systematic pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we have designed
|
||||
this version of the GPL to prohibit the practice for those products. If such problems
|
||||
arise substantially in other domains, we stand ready to extend this provision to
|
||||
those domains in future versions of the GPL, as needed to protect the freedom of
|
||||
users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents. States should
|
||||
not allow patents to restrict development and use of software on general-purpose
|
||||
computers, but in those that do, we wish to avoid the special danger that patents
|
||||
applied to a free program could make it effectively proprietary. To prevent this, the
|
||||
GPL assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
## TERMS AND CONDITIONS
|
||||
|
||||
### 0. Definitions
|
||||
|
||||
“This License” refers to version 3 of the GNU General Public License.
|
||||
|
||||
“Copyright” also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
“The Program” refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as “you”. “Licensees” and
|
||||
“recipients” may be individuals or organizations.
|
||||
|
||||
To “modify” a work means to copy from or adapt all or part of the work in
|
||||
a fashion requiring copyright permission, other than the making of an exact copy. The
|
||||
resulting work is called a “modified version” of the earlier work or a
|
||||
work “based on” the earlier work.
|
||||
|
||||
A “covered work” means either the unmodified Program or a work based on
|
||||
the Program.
|
||||
|
||||
To “propagate” a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for infringement under
|
||||
applicable copyright law, except executing it on a computer or modifying a private
|
||||
copy. Propagation includes copying, distribution (with or without modification),
|
||||
making available to the public, and in some countries other activities as well.
|
||||
|
||||
To “convey” a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through a computer
|
||||
network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays “Appropriate Legal Notices” to the
|
||||
extent that it includes a convenient and prominently visible feature that **(1)**
|
||||
displays an appropriate copyright notice, and **(2)** tells the user that there is no
|
||||
warranty for the work (except to the extent that warranties are provided), that
|
||||
licensees may convey the work under this License, and how to view a copy of this
|
||||
License. If the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
### 1. Source Code
|
||||
|
||||
The “source code” for a work means the preferred form of the work for
|
||||
making modifications to it. “Object code” means any non-source form of a
|
||||
work.
|
||||
|
||||
A “Standard Interface” means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of interfaces
|
||||
specified for a particular programming language, one that is widely used among
|
||||
developers working in that language.
|
||||
|
||||
The “System Libraries” of an executable work include anything, other than
|
||||
the work as a whole, that **(a)** is included in the normal form of packaging a Major
|
||||
Component, but which is not part of that Major Component, and **(b)** serves only to
|
||||
enable use of the work with that Major Component, or to implement a Standard
|
||||
Interface for which an implementation is available to the public in source code form.
|
||||
A “Major Component”, in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system (if any) on which
|
||||
the executable work runs, or a compiler used to produce the work, or an object code
|
||||
interpreter used to run it.
|
||||
|
||||
The “Corresponding Source” for a work in object code form means all the
|
||||
source code needed to generate, install, and (for an executable work) run the object
|
||||
code and to modify the work, including scripts to control those activities. However,
|
||||
it does not include the work's System Libraries, or general-purpose tools or
|
||||
generally available free programs which are used unmodified in performing those
|
||||
activities but which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for the work, and
|
||||
the source code for shared libraries and dynamically linked subprograms that the work
|
||||
is specifically designed to require, such as by intimate data communication or
|
||||
control flow between those subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate
|
||||
automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
### 2. Basic Permissions
|
||||
|
||||
All rights granted under this License are granted for the term of copyright on the
|
||||
Program, and are irrevocable provided the stated conditions are met. This License
|
||||
explicitly affirms your unlimited permission to run the unmodified Program. The
|
||||
output from running a covered work is covered by this License only if the output,
|
||||
given its content, constitutes a covered work. This License acknowledges your rights
|
||||
of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without
|
||||
conditions so long as your license otherwise remains in force. You may convey covered
|
||||
works to others for the sole purpose of having them make modifications exclusively
|
||||
for you, or provide you with facilities for running those works, provided that you
|
||||
comply with the terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for you must do so
|
||||
exclusively on your behalf, under your direction and control, on terms that prohibit
|
||||
them from making any copies of your copyrighted material outside their relationship
|
||||
with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions
|
||||
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
|
||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
|
||||
|
||||
No covered work shall be deemed part of an effective technological measure under any
|
||||
applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
|
||||
adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
|
||||
of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of
|
||||
technological measures to the extent such circumvention is effected by exercising
|
||||
rights under this License with respect to the covered work, and you disclaim any
|
||||
intention to limit operation or modification of the work as a means of enforcing,
|
||||
against the work's users, your or third parties' legal rights to forbid circumvention
|
||||
of technological measures.
|
||||
|
||||
### 4. Conveying Verbatim Copies
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any
|
||||
medium, provided that you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice; keep intact all notices stating that this License and
|
||||
any non-permissive terms added in accord with section 7 apply to the code; keep
|
||||
intact all notices of the absence of any warranty; and give all recipients a copy of
|
||||
this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer
|
||||
support or warranty protection for a fee.
|
||||
|
||||
### 5. Conveying Modified Source Versions
|
||||
|
||||
You may convey a work based on the Program, or the modifications to produce it from
|
||||
the Program, in the form of source code under the terms of section 4, provided that
|
||||
you also meet all of these conditions:
|
||||
|
||||
* **a)** The work must carry prominent notices stating that you modified it, and giving a
|
||||
relevant date.
|
||||
* **b)** The work must carry prominent notices stating that it is released under this
|
||||
License and any conditions added under section 7. This requirement modifies the
|
||||
requirement in section 4 to “keep intact all notices”.
|
||||
* **c)** You must license the entire work, as a whole, under this License to anyone who
|
||||
comes into possession of a copy. This License will therefore apply, along with any
|
||||
applicable section 7 additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no permission to license the
|
||||
work in any other way, but it does not invalidate such permission if you have
|
||||
separately received it.
|
||||
* **d)** If the work has interactive user interfaces, each must display Appropriate Legal
|
||||
Notices; however, if the Program has interactive interfaces that do not display
|
||||
Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are
|
||||
not by their nature extensions of the covered work, and which are not combined with
|
||||
it such as to form a larger program, in or on a volume of a storage or distribution
|
||||
medium, is called an “aggregate” if the compilation and its resulting
|
||||
copyright are not used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work in an aggregate
|
||||
does not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
### 6. Conveying Non-Source Forms
|
||||
|
||||
You may convey a covered work in object code form under the terms of sections 4 and
|
||||
5, provided that you also convey the machine-readable Corresponding Source under the
|
||||
terms of this License, in one of these ways:
|
||||
|
||||
* **a)** Convey the object code in, or embodied in, a physical product (including a
|
||||
physical distribution medium), accompanied by the Corresponding Source fixed on a
|
||||
durable physical medium customarily used for software interchange.
|
||||
* **b)** Convey the object code in, or embodied in, a physical product (including a
|
||||
physical distribution medium), accompanied by a written offer, valid for at least
|
||||
three years and valid for as long as you offer spare parts or customer support for
|
||||
that product model, to give anyone who possesses the object code either **(1)** a copy of
|
||||
the Corresponding Source for all the software in the product that is covered by this
|
||||
License, on a durable physical medium customarily used for software interchange, for
|
||||
a price no more than your reasonable cost of physically performing this conveying of
|
||||
source, or **(2)** access to copy the Corresponding Source from a network server at no
|
||||
charge.
|
||||
* **c)** Convey individual copies of the object code with a copy of the written offer to
|
||||
provide the Corresponding Source. This alternative is allowed only occasionally and
|
||||
noncommercially, and only if you received the object code with such an offer, in
|
||||
accord with subsection 6b.
|
||||
* **d)** Convey the object code by offering access from a designated place (gratis or for
|
||||
a charge), and offer equivalent access to the Corresponding Source in the same way
|
||||
through the same place at no further charge. You need not require recipients to copy
|
||||
the Corresponding Source along with the object code. If the place to copy the object
|
||||
code is a network server, the Corresponding Source may be on a different server
|
||||
(operated by you or a third party) that supports equivalent copying facilities,
|
||||
provided you maintain clear directions next to the object code saying where to find
|
||||
the Corresponding Source. Regardless of what server hosts the Corresponding Source,
|
||||
you remain obligated to ensure that it is available for as long as needed to satisfy
|
||||
these requirements.
|
||||
* **e)** Convey the object code using peer-to-peer transmission, provided you inform
|
||||
other peers where the object code and Corresponding Source of the work are being
|
||||
offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the
|
||||
Corresponding Source as a System Library, need not be included in conveying the
|
||||
object code work.
|
||||
|
||||
A “User Product” is either **(1)** a “consumer product”, which
|
||||
means any tangible personal property which is normally used for personal, family, or
|
||||
household purposes, or **(2)** anything designed or sold for incorporation into a
|
||||
dwelling. In determining whether a product is a consumer product, doubtful cases
|
||||
shall be resolved in favor of coverage. For a particular product received by a
|
||||
particular user, “normally used” refers to a typical or common use of
|
||||
that class of product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected to use, the
|
||||
product. A product is a consumer product regardless of whether the product has
|
||||
substantial commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
“Installation Information” for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install and execute
|
||||
modified versions of a covered work in that User Product from a modified version of
|
||||
its Corresponding Source. The information must suffice to ensure that the continued
|
||||
functioning of the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for
|
||||
use in, a User Product, and the conveying occurs as part of a transaction in which
|
||||
the right of possession and use of the User Product is transferred to the recipient
|
||||
in perpetuity or for a fixed term (regardless of how the transaction is
|
||||
characterized), the Corresponding Source conveyed under this section must be
|
||||
accompanied by the Installation Information. But this requirement does not apply if
|
||||
neither you nor any third party retains the ability to install modified object code
|
||||
on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to
|
||||
continue to provide support service, warranty, or updates for a work that has been
|
||||
modified or installed by the recipient, or for the User Product in which it has been
|
||||
modified or installed. Access to a network may be denied when the modification itself
|
||||
materially and adversely affects the operation of the network or violates the rules
|
||||
and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with
|
||||
this section must be in a format that is publicly documented (and with an
|
||||
implementation available to the public in source code form), and must require no
|
||||
special password or key for unpacking, reading or copying.
|
||||
|
||||
### 7. Additional Terms
|
||||
|
||||
“Additional permissions” are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions. Additional
|
||||
permissions that are applicable to the entire Program shall be treated as though they
|
||||
were included in this License, to the extent that they are valid under applicable
|
||||
law. If additional permissions apply only to part of the Program, that part may be
|
||||
used separately under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any
|
||||
additional permissions from that copy, or from any part of it. (Additional
|
||||
permissions may be written to require their own removal in certain cases when you
|
||||
modify the work.) You may place additional permissions on material, added by you to a
|
||||
covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a
|
||||
covered work, you may (if authorized by the copyright holders of that material)
|
||||
supplement the terms of this License with terms:
|
||||
|
||||
* **a)** Disclaiming warranty or limiting liability differently from the terms of
|
||||
sections 15 and 16 of this License; or
|
||||
* **b)** Requiring preservation of specified reasonable legal notices or author
|
||||
attributions in that material or in the Appropriate Legal Notices displayed by works
|
||||
containing it; or
|
||||
* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
|
||||
modified versions of such material be marked in reasonable ways as different from the
|
||||
original version; or
|
||||
* **d)** Limiting the use for publicity purposes of names of licensors or authors of the
|
||||
material; or
|
||||
* **e)** Declining to grant rights under trademark law for use of some trade names,
|
||||
trademarks, or service marks; or
|
||||
* **f)** Requiring indemnification of licensors and authors of that material by anyone
|
||||
who conveys the material (or modified versions of it) with contractual assumptions of
|
||||
liability to the recipient, for any liability that these contractual assumptions
|
||||
directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered “further
|
||||
restrictions” within the meaning of section 10. If the Program as you received
|
||||
it, or any part of it, contains a notice stating that it is governed by this License
|
||||
along with a term that is a further restriction, you may remove that term. If a
|
||||
license document contains a further restriction but permits relicensing or conveying
|
||||
under this License, you may add to a covered work material governed by the terms of
|
||||
that license document, provided that the further restriction does not survive such
|
||||
relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in
|
||||
the relevant source files, a statement of the additional terms that apply to those
|
||||
files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a
|
||||
separately written license, or stated as exceptions; the above requirements apply
|
||||
either way.
|
||||
|
||||
### 8. Termination
|
||||
|
||||
You may not propagate or modify a covered work except as expressly provided under
|
||||
this License. Any attempt otherwise to propagate or modify it is void, and will
|
||||
automatically terminate your rights under this License (including any patent licenses
|
||||
granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a
|
||||
particular copyright holder is reinstated **(a)** provisionally, unless and until the
|
||||
copyright holder explicitly and finally terminates your license, and **(b)** permanently,
|
||||
if the copyright holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently
|
||||
if the copyright holder notifies you of the violation by some reasonable means, this
|
||||
is the first time you have received notice of violation of this License (for any
|
||||
work) from that copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of
|
||||
parties who have received copies or rights from you under this License. If your
|
||||
rights have been terminated and not permanently reinstated, you do not qualify to
|
||||
receive new licenses for the same material under section 10.
|
||||
|
||||
### 9. Acceptance Not Required for Having Copies
|
||||
|
||||
You are not required to accept this License in order to receive or run a copy of the
|
||||
Program. Ancillary propagation of a covered work occurring solely as a consequence of
|
||||
using peer-to-peer transmission to receive a copy likewise does not require
|
||||
acceptance. However, nothing other than this License grants you permission to
|
||||
propagate or modify any covered work. These actions infringe copyright if you do not
|
||||
accept this License. Therefore, by modifying or propagating a covered work, you
|
||||
indicate your acceptance of this License to do so.
|
||||
|
||||
### 10. Automatic Licensing of Downstream Recipients
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives a license
|
||||
from the original licensors, to run, modify and propagate that work, subject to this
|
||||
License. You are not responsible for enforcing compliance by third parties with this
|
||||
License.
|
||||
|
||||
An “entity transaction” is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an organization, or
|
||||
merging organizations. If propagation of a covered work results from an entity
|
||||
transaction, each party to that transaction who receives a copy of the work also
|
||||
receives whatever licenses to the work the party's predecessor in interest had or
|
||||
could give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if the predecessor
|
||||
has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or
|
||||
affirmed under this License. For example, you may not impose a license fee, royalty,
|
||||
or other charge for exercise of rights granted under this License, and you may not
|
||||
initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that any patent claim is infringed by making, using, selling, offering for sale, or
|
||||
importing the Program or any portion of it.
|
||||
|
||||
### 11. Patents
|
||||
|
||||
A “contributor” is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The work thus
|
||||
licensed is called the contributor's “contributor version”.
|
||||
|
||||
A contributor's “essential patent claims” are all patent claims owned or
|
||||
controlled by the contributor, whether already acquired or hereafter acquired, that
|
||||
would be infringed by some manner, permitted by this License, of making, using, or
|
||||
selling its contributor version, but do not include claims that would be infringed
|
||||
only as a consequence of further modification of the contributor version. For
|
||||
purposes of this definition, “control” includes the right to grant patent
|
||||
sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
|
||||
under the contributor's essential patent claims, to make, use, sell, offer for sale,
|
||||
import and otherwise run, modify and propagate the contents of its contributor
|
||||
version.
|
||||
|
||||
In the following three paragraphs, a “patent license” is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent (such as an
|
||||
express permission to practice a patent or covenant not to sue for patent
|
||||
infringement). To “grant” such a patent license to a party means to make
|
||||
such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the
|
||||
Corresponding Source of the work is not available for anyone to copy, free of charge
|
||||
and under the terms of this License, through a publicly available network server or
|
||||
other readily accessible means, then you must either **(1)** cause the Corresponding
|
||||
Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or **(3)** arrange, in a manner consistent with
|
||||
the requirements of this License, to extend the patent license to downstream
|
||||
recipients. “Knowingly relying” means you have actual knowledge that, but
|
||||
for the patent license, your conveying the covered work in a country, or your
|
||||
recipient's use of the covered work in a country, would infringe one or more
|
||||
identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you
|
||||
convey, or propagate by procuring conveyance of, a covered work, and grant a patent
|
||||
license to some of the parties receiving the covered work authorizing them to use,
|
||||
propagate, modify or convey a specific copy of the covered work, then the patent
|
||||
license you grant is automatically extended to all recipients of the covered work and
|
||||
works based on it.
|
||||
|
||||
A patent license is “discriminatory” if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on the
|
||||
non-exercise of one or more of the rights that are specifically granted under this
|
||||
License. You may not convey a covered work if you are a party to an arrangement with
|
||||
a third party that is in the business of distributing software, under which you make
|
||||
payment to the third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties who would receive
|
||||
the covered work from you, a discriminatory patent license **(a)** in connection with
|
||||
copies of the covered work conveyed by you (or copies made from those copies), or **(b)**
|
||||
primarily for and in connection with specific products or compilations that contain
|
||||
the covered work, unless you entered into that arrangement, or that patent license
|
||||
was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied
|
||||
license or other defenses to infringement that may otherwise be available to you
|
||||
under applicable patent law.
|
||||
|
||||
### 12. No Surrender of Others' Freedom
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise)
|
||||
that contradict the conditions of this License, they do not excuse you from the
|
||||
conditions of this License. If you cannot convey a covered work so as to satisfy
|
||||
simultaneously your obligations under this License and any other pertinent
|
||||
obligations, then as a consequence you may not convey it at all. For example, if you
|
||||
agree to terms that obligate you to collect a royalty for further conveying from
|
||||
those to whom you convey the Program, the only way you could satisfy both those terms
|
||||
and this License would be to refrain entirely from conveying the Program.
|
||||
|
||||
### 13. Use with the GNU Affero General Public License
|
||||
|
||||
Notwithstanding any other provision of this License, you have permission to link or
|
||||
combine any covered work with a work licensed under version 3 of the GNU Affero
|
||||
General Public License into a single combined work, and to convey the resulting work.
|
||||
The terms of this License will continue to apply to the part which is the covered
|
||||
work, but the special requirements of the GNU Affero General Public License, section
|
||||
13, concerning interaction through a network will apply to the combination as such.
|
||||
|
||||
### 14. Revised Versions of this License
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU
|
||||
General Public License from time to time. Such new versions will be similar in spirit
|
||||
to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that
|
||||
a certain numbered version of the GNU General Public License “or any later
|
||||
version” applies to it, you have the option of following the terms and
|
||||
conditions either of that numbered version or of any later version published by the
|
||||
Free Software Foundation. If the Program does not specify a version number of the GNU
|
||||
General Public License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU
|
||||
General Public License can be used, that proxy's public statement of acceptance of a
|
||||
version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no
|
||||
additional obligations are imposed on any author or copyright holder as a result of
|
||||
your choosing to follow a later version.
|
||||
|
||||
### 15. Disclaimer of Warranty
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
|
||||
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
### 16. Limitation of Liability
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
|
||||
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
|
||||
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
|
||||
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
|
||||
OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
|
||||
WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
### 17. Interpretation of Sections 15 and 16
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be
|
||||
given local legal effect according to their terms, reviewing courts shall apply local
|
||||
law that most closely approximates an absolute waiver of all civil liability in
|
||||
connection with the Program, unless a warranty or assumption of liability accompanies
|
||||
a copy of the Program in return for a fee.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
## How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to
|
||||
the public, the best way to achieve this is to make it free software which everyone
|
||||
can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them
|
||||
to the start of each source file to most effectively state the exclusion of warranty;
|
||||
and each file should have at least the “copyright” line and a pointer to
|
||||
where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type 'show c' for details.
|
||||
|
||||
The hypothetical commands `show w` and `show c` should show the appropriate parts of
|
||||
the General Public License. Of course, your program's commands might be different;
|
||||
for a GUI interface, you would use an “about box”.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school, if any, to
|
||||
sign a “copyright disclaimer” for the program, if necessary. For more
|
||||
information on this, and how to apply and follow the GNU GPL, see
|
||||
<<http://www.gnu.org/licenses/>>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may consider it
|
||||
more useful to permit linking proprietary applications with the library. If this is
|
||||
what you want to do, use the GNU Lesser General Public License instead of this
|
||||
License. But first, please read
|
||||
<<http://www.gnu.org/philosophy/why-not-lgpl.html>>.
|
||||
40
README.md
@@ -1,14 +1,12 @@
|
||||
 LeanKeyKeyboard
|
||||
 LeanKeyboard
|
||||
=========
|
||||
|
||||
[](https://www.mozilla.org/MPL/2.0/)
|
||||
|
||||
__LeanKeyKeyboard: Keyboard for Android-based set-top boxes and TVs:__
|
||||
__LeanKeyboard: Keyboard for Android-based set-top boxes and TVs:__
|
||||
|
||||
* <a href="https://t.me/LeanKeyKeyboard">Telegram group</a>
|
||||
* <a href="https://play.google.com/store/apps/details?id=org.liskovsoft.androidtv.rukeyboard" target="_blank">Google Play page</a>
|
||||
|
||||
__NOTE: Google Play release is outdated. So I recommend to obtain new releases [here](https://github.com/yuliskov/LeanKeyKeyboard/releases).__
|
||||
* <a href="https://t.me/LeanKeyboard">Telegram group</a>
|
||||
|
||||
### Features:
|
||||
* Designed for TV screens.
|
||||
@@ -24,28 +22,36 @@ __Tip: Do long press on the language button to choose between available language
|
||||
### Screenshots:
|
||||
* __[Open screenshots](#screens)__
|
||||
|
||||
### Install LeanKeyKeyboard:
|
||||
### Install LeanKeyboard:
|
||||
__Easy installation in less than 10 minutes with only FireTV__
|
||||
* <a href="https://github.com/yuliskov/LeanKeyKeyboard/wiki/How-to-Install-LeanKeyKeyboard-on-FireTV">Install LeanKeyKeyboard (only FireTV needed)</a>
|
||||
* <a href="https://github.com/yuliskov/LeanKeyboard/wiki/How-to-Install-LeanKeyKeyboard-on-FireTV">Install LeanKeyKeyboard (only FireTV needed)</a>
|
||||
|
||||
__Standard installation via ADB__
|
||||
* If you don't know how to sideload/install apps via ADB, read a tutorial (e.g. <a href="http://kodi.wiki/view/HOW-TO:Install_Kodi_on_Fire_TV" target="_blank">this one</a>)
|
||||
* <a href="https://github.com/yuliskov/LeanKeyKeyboard/releases" target="_blank">Download latest LeanKeyKeyboard APK</a> and sideload/install with adb:
|
||||
* *adb install -r LeanKeyKeyboard.apk*
|
||||
* <a href="https://github.com/yuliskov/LeanKeyboard/releases" target="_blank">Download latest LeanKeyKeyboard APK</a> and sideload/install with adb:
|
||||
* *adb install -r LeanKeyboard.apk*
|
||||
* Enjoy :)
|
||||
|
||||
### Donation:
|
||||
If you want to support my developments you are welcome to buy me a cup of coffee :)
|
||||
* [__Donation Alerts (RU)__](https://www.donationalerts.ru/r/firsthash)
|
||||
* [__QIWI (VISA)__](https://qiwi.com/n/GUESS025)
|
||||
* [__PrivatBank (UA)__](https://privatbank.ua/ru/sendmoney?payment=9e46a6ef78)
|
||||
* __BTC__: 1JAT5VVWarVBkpVbNDn8UA8HXNdrukuBSx
|
||||
<!-- * [QIWI (RU, Visa)](https://qiwi.com/n/GUESS025) -->
|
||||
<!-- * [DonatePay (RU, **PayPal**, Visa)](https://new.donatepay.ru/@459197) -->
|
||||
* [**Patreon**](https://www.patreon.com/yuliskov)
|
||||
* **PayPal**: firsthash at gmail.com
|
||||
* **BTC**: 1JAT5VVWarVBkpVbNDn8UA8HXNdrukuBSx
|
||||
* **LTC**: ltc1qgc24eq9jl9cq78qnd5jpqhemkajg9vudwyd8pw
|
||||
* **ETH**: 0xe455E21a085ae195a097cd4F456051A9916A5064
|
||||
* **ETC**: 0x209eCd33Fa61fA92167595eB3Aea92EE1905c815
|
||||
* **XMR**: 48QsMjqfkeW54vkgKyRnjodtYxdmLk6HXfTWPSZoaFPEDpoHDwFUciGCe1QC9VAeGrgGw4PKNAksX9RW7myFqYJQDN5cHGT
|
||||
* **BNB**: bnb1amjr7fauftxxyhe4f95280vklctj243k9u55fq
|
||||
* **DOGE**: DBnqJwJs2GJBxrCDsi5bXwSmjnz8uGdUpB
|
||||
* **eUSDT**: 0xe455e21a085ae195a097cd4f456051a9916a5064
|
||||
|
||||
### Reviews / Articles:
|
||||
* [__XDA Discussion__](https://forum.xda-developers.com/fire-tv/general/guide-change-screen-keyboard-to-leankey-t3527675)
|
||||
|
||||
### Changelog:
|
||||
* [Check releases page for changelog ..](https://github.com/yuliskov/LeanKeyKeyboard/releases)
|
||||
* [Check releases page for changelog ..](https://github.com/yuliskov/LeanKeyboard/releases)
|
||||
|
||||
### Contributors:
|
||||
* __[aglt](https://github.com/aglt)__ (Icelandic lang)
|
||||
@@ -55,6 +61,6 @@ If you want to support my developments you are welcome to buy me a cup of coffee
|
||||
* __[yuliskov](https://github.com/yuliskov)__ (design & coding)
|
||||
|
||||
### Screens:
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
56
build.gradle
@@ -2,10 +2,17 @@
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
// jcenter()
|
||||
// maven {
|
||||
// url 'https://maven.google.com/'
|
||||
// name 'Google'
|
||||
// }
|
||||
// google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
||||
classpath 'com.android.tools.build:gradle:8.5.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@@ -14,27 +21,58 @@ buildscript {
|
||||
// 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 = 28
|
||||
buildToolsVersion = "28.0.3"
|
||||
// Google Play SDK version requirements:
|
||||
// https://support.google.com/googleplay/android-developer/answer/11926878
|
||||
compileSdkVersion = 35
|
||||
buildToolsVersion = "35.0.0"
|
||||
minSdkVersion = 14
|
||||
targetSdkVersion = 26
|
||||
appCompatVersion = 'com.android.support:appcompat-v7:28.+'
|
||||
targetSdkVersion = 35
|
||||
espressoCoreVersion = 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
junitVersion = 'junit:junit:4.12'
|
||||
supportVersion = 'com.android.support:support-v4:28.+'
|
||||
robolectricVersion = 'org.robolectric:robolectric:3.5.1'
|
||||
crashlyticsVersion = 'com.crashlytics.sdk.android:crashlytics:2.8.0@aar'
|
||||
// androidx migration:
|
||||
// https://developer.android.com/jetpack/androidx/migrate
|
||||
// https://developer.android.com/jetpack/androidx/migrate/artifact-mappings
|
||||
appCompatXVersion = 'androidx.appcompat:appcompat:1.1.0'
|
||||
constraintXVersion = 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
supportXVersion = 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
leanbackCompatXVersion = 'androidx.leanback:leanback:1.0.0'
|
||||
designXVersion = 'com.google.android.material:material:1.0.0'
|
||||
voiceOverlayVersion = 'com.algolia.instantsearch:voice:1.1.0' // https://github.com/algolia/voice-overlay-android
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
//jcenter()
|
||||
// com.android.support libs
|
||||
maven { url 'https://maven.google.com' }
|
||||
//maven { url 'https://maven.google.com' }
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
// Fix 'Namespace not specified'
|
||||
// https://stackoverflow.com/questions/76300671/android-getting-error-namespace-not-specified
|
||||
// subprojects {
|
||||
// afterEvaluate { project ->
|
||||
// if (project.hasProperty('android')) {
|
||||
// project.android {
|
||||
// if (namespace == null) {
|
||||
// namespace project.group
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,13 +1,2 @@
|
||||
# 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=-Xmx512m
|
||||
# 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
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
12
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +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
|
||||
#Thu Jan 16 19:59:54 EET 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
BIN
img/screen4.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
img/screen5.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
2
leankeykeyboard/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/build
|
||||
/playstore
|
||||
/origin
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
// Latest gradle fix: https://stackoverflow.com/questions/76300671/android-getting-error-namespace-not-specified
|
||||
namespace 'com.liskovsoft.leankeykeyboard'
|
||||
|
||||
// Latest gradle fix: https://stackoverflow.com/questions/22604627/gradle-buildconfigfield-buildconfig-cannot-resolve-symbol
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
// FIX: Default interface methods are only supported starting with Android N (--min-api 24)
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion
|
||||
buildToolsVersion project.properties.buildToolsVersion
|
||||
|
||||
@@ -8,15 +22,16 @@ android {
|
||||
applicationId "org.liskovsoft.leankeykeyboard.pro"
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
versionCode 80
|
||||
versionName "4.3.30"
|
||||
versionCode 196
|
||||
versionName "6.1.26"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
// https://medium.com/@angelhiadefiesta/how-to-obfuscate-in-android-with-proguard-acab47701577
|
||||
minifyEnabled true // enable obfuscation
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@@ -25,13 +40,15 @@ android {
|
||||
// https://stackoverflow.com/questions/18332474/how-to-set-versionname-in-apk-filename-using-gradle
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def project = "LeanKeyKeyboardPro"
|
||||
def buildType = variant.variantData.variantConfiguration.buildType.name.take(1)
|
||||
def project = "LeanKeyboard"
|
||||
// Latest gradle fix: https://stackoverflow.com/questions/62075122/no-such-property-variantconfiguration-for-class
|
||||
def buildType = variant.buildType.name.take(1)
|
||||
def version = variant.versionName
|
||||
def flavor = variant.productFlavors[-1].name
|
||||
|
||||
def newApkName = sprintf("%s_v%s_%s.apk", [project, version, buildType])
|
||||
def newApkName = sprintf("%s_v%s_%s_%s.apk", [project, version, flavor, buildType])
|
||||
|
||||
output.outputFile = new File(output.outputFile.parent, newApkName)
|
||||
output.outputFileName = new File(newApkName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +57,30 @@ android {
|
||||
disable 'MissingTranslation'
|
||||
disable 'NewApi'
|
||||
}
|
||||
|
||||
// gradle 4.6 migration: disable dimensions mechanism
|
||||
// more: https://proandroiddev.com/advanced-android-flavors-part-4-a-new-version-fc2ad80c01bb
|
||||
flavorDimensions "default"
|
||||
|
||||
productFlavors {
|
||||
playstore {
|
||||
applicationId "org.liskovsoft.androidtv.rukeyboard"
|
||||
}
|
||||
origin {
|
||||
applicationId "com.liskovsoft.leankeyboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
androidTestCompile(project.properties.espressoCoreVersion, {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
androidTestImplementation(project.properties.espressoCoreVersion, {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
compile project.properties.appCompatVersion
|
||||
testCompile project.properties.junitVersion
|
||||
testImplementation project.properties.junitVersion
|
||||
implementation project.properties.appCompatXVersion
|
||||
implementation project.properties.leanbackCompatXVersion
|
||||
implementation project.properties.constraintXVersion
|
||||
implementation project.properties.designXVersion
|
||||
implementation project.properties.voiceOverlayVersion
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.liskovsoft.leankeykeyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -1,39 +1,126 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" 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"/>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.liskovsoft.leankeykeyboard">
|
||||
|
||||
<!-- Use older sdk -->
|
||||
<uses-sdk tools:overrideLibrary="
|
||||
android.support.v17.leanback,
|
||||
androidx.leanback,
|
||||
com.algolia.instantsearch.voice"/>
|
||||
|
||||
<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="@string/ime_name" android:icon="@drawable/ic_launcher" tools:targetApi="21">
|
||||
<activity android:launchMode="singleTop" android:name="com.liskovsoft.other.GenericLaunchActivity">
|
||||
<meta-data android:name="package" android:value="com.android.settings"/>
|
||||
<meta-data android:name="class" android:value="com.android.settings.Settings$LanguageAndInputSettingsActivity"/>
|
||||
<!-- "Languages & input" on old api -->
|
||||
<meta-data android:name="package_alt" android:value="com.android.settings"/>
|
||||
<meta-data android:name="class_alt" android:value="com.android.settings.Settings$InputMethodAndLanguageSettingsActivity"/>
|
||||
<!-- Last try (if above not found) -->
|
||||
<meta-data android:name="intent" android:value="android.settings.INPUT_METHOD_SETTINGS"/>
|
||||
<uses-permission android:name="com.amazon.tv.ime.permission.KEYBOARD_STATE"/>
|
||||
<!-- Google Play Store fix -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:banner="@mipmap/ic_banner_main"
|
||||
android:icon="@mipmap/ic_launcher_main"
|
||||
android:label="@string/ime_name"
|
||||
android:theme="@style/Theme.Leanback">
|
||||
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity2"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:label="@string/ime_service_name"
|
||||
android:name="com.google.leanback.ime.LeanbackImeService"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.activity.settings.KbLayoutActivity"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.activity.PermissionsActivity"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.addons.voice.RecognizerIntentActivity"
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:launchMode="singleTop"/>
|
||||
|
||||
<activity
|
||||
android:name="com.liskovsoft.leankeyboard.activity.settings.KbActivationActivity"
|
||||
android:launchMode="singleTop">
|
||||
<meta-data
|
||||
android:name="package"
|
||||
android:value="com.android.settings"/>
|
||||
<meta-data
|
||||
android:name="class"
|
||||
android:value="com.android.settings.Settings$LanguageAndInputSettingsActivity"/>
|
||||
<!-- "Languages & input" on old api -->
|
||||
<meta-data
|
||||
android:name="package_alt"
|
||||
android:value="com.android.settings"/>
|
||||
<meta-data
|
||||
android:name="class_alt"
|
||||
android:value="com.android.settings.Settings$InputMethodAndLanguageSettingsActivity"/>
|
||||
<!-- Last try (if above not found) -->
|
||||
<meta-data
|
||||
android:name="intent"
|
||||
android:value="android.settings.INPUT_METHOD_SETTINGS"/>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="com.liskovsoft.leankeyboard.ime.LeanbackImeService"
|
||||
android:label="@string/ime_service_name"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.view.im"
|
||||
android:resource="@xml/method"/>
|
||||
</service>
|
||||
<receiver android:name="com.liskovsoft.other.RestartServiceReceiver" android:exported="true" android:enabled="true">
|
||||
|
||||
<receiver
|
||||
android:name="com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="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" />
|
||||
<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>
|
||||
|
||||
<receiver
|
||||
android:name="com.liskovsoft.leankeyboard.receiver.TextUpdateReceiver"
|
||||
android:permission="com.amazon.tv.ime.permission.KEYBOARD_STATE"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.amazon.tv.ime.action.CLIENT_TEXT"/>
|
||||
<action android:name="com.amazon.tv.ime.action.IME_TEXT"/>
|
||||
<action android:name="com.amazon.tv.ime.action.KEYBOARD_STATE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
</manifest>
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.google.android.leanback.ime;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
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 info) {
|
||||
return info.imeOptions & (EditorInfo.IME_FLAG_NO_ENTER_ACTION | EditorInfo.IME_MASK_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class of the input
|
||||
* @param info attrs
|
||||
* @return constant e.g. {@link InputType#TYPE_CLASS_TEXT InputType.TYPE_CLASS_TEXT}
|
||||
*/
|
||||
public static int getInputTypeClass(EditorInfo info) {
|
||||
return info.inputType & InputType.TYPE_MASK_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variation of the input
|
||||
* @param info attrs
|
||||
* @return constant e.g. {@link InputType#TYPE_DATETIME_VARIATION_DATE InputType.TYPE_DATETIME_VARIATION_DATE}
|
||||
*/
|
||||
public static int getInputTypeVariation(EditorInfo info) {
|
||||
return info.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
}
|
||||
|
||||
public static boolean isAlphabet(int letter) {
|
||||
return Character.isLetter(letter);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static void sendAccessibilityEvent(final View view, boolean focusGained) {
|
||||
if (view != null && focusGained) {
|
||||
sAccessibilityHandler.removeCallbacksAndMessages(null);
|
||||
sAccessibilityHandler.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
|
||||
}
|
||||
}, ACCESSIBILITY_DELAY_MS);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.liskovsoft.keyboardaddons;
|
||||
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public interface KeyboardBuilder {
|
||||
@Nullable
|
||||
Keyboard createKeyboard();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.liskovsoft.keyboardaddons.reskbdfactory;
|
||||
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardBuilder;
|
||||
|
||||
class ResKeyboardBuilder implements KeyboardBuilder {
|
||||
@Nullable
|
||||
@Override
|
||||
public Keyboard createKeyboard() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.liskovsoft.keyboardaddons.reskbdfactory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardBuilder;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardFactory;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ResKeyboardFactory implements KeyboardFactory {
|
||||
private final Context mContext;
|
||||
|
||||
public ResKeyboardFactory(Context ctx) {
|
||||
mContext = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends KeyboardBuilder> getAllAvailableKeyboards(Context context) {
|
||||
List<KeyboardBuilder> result = new ArrayList<>();
|
||||
List<KeyboardInfo> infos = ResKeyboardInfo.getAllKeyboardInfos(context);
|
||||
|
||||
for (final KeyboardInfo info : infos) {
|
||||
if (!info.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.add(createKeyboard(info.getLangCode()));
|
||||
}
|
||||
|
||||
// at least one kbd should be enabled
|
||||
if (result.isEmpty()) {
|
||||
KeyboardInfo firstKbd = infos.get(0);
|
||||
result.add(createKeyboard(firstKbd.getLangCode()));
|
||||
firstKbd.setEnabled(true);
|
||||
ResKeyboardInfo.updateAllKeyboardInfos(mContext, infos);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private KeyboardBuilder createKeyboard(final String langCode) {
|
||||
return new KeyboardBuilder() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Keyboard createKeyboard() {
|
||||
return new Keyboard(mContext, mContext.getResources().getIdentifier("qwerty_" + langCode, "xml", mContext.getPackageName()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needUpdate() {
|
||||
return ResKeyboardInfo.needUpdate();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.liskovsoft.keyboardaddons.reskbdfactory;
|
||||
|
||||
import com.liskovsoft.keyboardaddons.KeyboardInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class ResKeyboardManager {
|
||||
public List<KeyboardInfo> getAllKeyboardInfos() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.liskovsoft.leankeyboard.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import com.liskovsoft.leankeyboard.helpers.PermissionHelpers;
|
||||
import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver;
|
||||
|
||||
public class PermissionsActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// restart kbd service
|
||||
Intent intent = new Intent(this, RestartServiceReceiver.class);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
private void checkPermissions() {
|
||||
if (!PermissionHelpers.hasMicPermissions(this)) {
|
||||
PermissionHelpers.verifyMicPermissions(this);
|
||||
} else if (!PermissionHelpers.hasStoragePermissions(this)) {
|
||||
PermissionHelpers.verifyStoragePermissions(this);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.liskovsoft.leankeyboard.activity.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.leanback.app.GuidedStepSupportFragment;
|
||||
import com.liskovsoft.leankeyboard.fragments.settings.KbLayoutFragment;
|
||||
import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver;
|
||||
|
||||
public class KbLayoutActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
GuidedStepSupportFragment.addAsRoot(this, new KbLayoutFragment(), android.R.id.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// restart kbd service
|
||||
Intent intent = new Intent(this, RestartServiceReceiver.class);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.liskovsoft.leankeyboard.activity.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.leanback.app.GuidedStepSupportFragment;
|
||||
import com.liskovsoft.leankeyboard.fragments.settings.KbSettingsFragment;
|
||||
import com.liskovsoft.leankeyboard.receiver.RestartServiceReceiver;
|
||||
|
||||
public class KbSettingsActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
GuidedStepSupportFragment.addAsRoot(this, new KbSettingsFragment(), android.R.id.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// restart kbd service
|
||||
Intent intent = new Intent(this, RestartServiceReceiver.class);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.liskovsoft.leankeyboard.activity.settings;
|
||||
|
||||
public class KbSettingsActivity2 extends KbSettingsActivity {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards;
|
||||
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface KeyboardBuilder {
|
||||
Keyboard createAbcKeyboard();
|
||||
Keyboard createSymKeyboard();
|
||||
Keyboard createNumKeyboard();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.liskovsoft.keyboardaddons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.liskovsoft.keyboardaddons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards;
|
||||
|
||||
public interface KeyboardInfo {
|
||||
boolean isEnabled();
|
||||
String getLangCode();
|
||||
void setLangCode(String langCode);
|
||||
String getLangName();
|
||||
void setLangName(String langName);
|
||||
void setLangCode(String langCode);
|
||||
boolean isEnabled();
|
||||
void setEnabled(boolean enabled);
|
||||
boolean isAzerty();
|
||||
void setIsAzerty(boolean enabled);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.liskovsoft.keyboardaddons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import com.liskovsoft.keyboardaddons.reskbdfactory.ResKeyboardFactory;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.ResKeyboardFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -11,29 +11,38 @@ public class KeyboardManager {
|
||||
private final Context mContext;
|
||||
private final KeyboardStateManager mStateManager;
|
||||
private List<? extends KeyboardBuilder> mKeyboardBuilders;
|
||||
private List<Keyboard> mAllKeyboards;
|
||||
private KeyboardFactory mKeyboardFactory;
|
||||
|
||||
private List<KeyboardData> mAllKeyboards;
|
||||
private final KeyboardFactory mKeyboardFactory;
|
||||
private int mKeyboardIndex = 0;
|
||||
|
||||
public static class KeyboardData {
|
||||
public Keyboard abcKeyboard;
|
||||
public Keyboard symKeyboard;
|
||||
public Keyboard numKeyboard;
|
||||
}
|
||||
|
||||
public KeyboardManager(Context ctx) {
|
||||
mContext = ctx;
|
||||
mStateManager = new KeyboardStateManager(mContext, this);
|
||||
mKeyboardFactory = new ResKeyboardFactory(mContext);
|
||||
mStateManager.restore();
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mKeyboardFactory = new ResKeyboardFactory(mContext);
|
||||
public void load() {
|
||||
mKeyboardBuilders = mKeyboardFactory.getAllAvailableKeyboards(mContext);
|
||||
mAllKeyboards = buildAllKeyboards();
|
||||
}
|
||||
|
||||
private List<Keyboard> buildAllKeyboards() {
|
||||
List<Keyboard> keyboards = new ArrayList<>();
|
||||
private List<KeyboardData> buildAllKeyboards() {
|
||||
List<KeyboardData> keyboards = new ArrayList<>();
|
||||
if (!mKeyboardBuilders.isEmpty()) {
|
||||
for (KeyboardBuilder builder : mKeyboardBuilders) {
|
||||
keyboards.add(builder.createKeyboard());
|
||||
KeyboardData data = new KeyboardData();
|
||||
data.abcKeyboard = builder.createAbcKeyboard();
|
||||
data.symKeyboard = builder.createSymKeyboard();
|
||||
data.numKeyboard = builder.createNumKeyboard();
|
||||
|
||||
keyboards.add(data);
|
||||
}
|
||||
}
|
||||
return keyboards;
|
||||
@@ -48,32 +57,47 @@ public class KeyboardManager {
|
||||
|
||||
/**
|
||||
* Get next keyboard from internal source (looped)
|
||||
* @return keyboard
|
||||
*/
|
||||
public Keyboard getNextKeyboard() {
|
||||
if (mKeyboardFactory.needUpdate()) {
|
||||
init();
|
||||
public KeyboardData next() {
|
||||
if (mKeyboardFactory.needUpdate() || mAllKeyboards == null) {
|
||||
load();
|
||||
}
|
||||
|
||||
++mKeyboardIndex;
|
||||
|
||||
mKeyboardIndex = mKeyboardIndex < mAllKeyboards.size() ? mKeyboardIndex : 0;
|
||||
|
||||
Keyboard kbd = mAllKeyboards.get(mKeyboardIndex);
|
||||
KeyboardData kbd = mAllKeyboards.get(mKeyboardIndex);
|
||||
|
||||
if (kbd == null) {
|
||||
throw new IllegalStateException(String.format("Keyboard %s not initialized", mKeyboardIndex));
|
||||
}
|
||||
|
||||
onNextKeyboard();
|
||||
|
||||
++mKeyboardIndex;
|
||||
|
||||
return kbd;
|
||||
}
|
||||
|
||||
public int getKeyboardIndex() {
|
||||
public int getIndex() {
|
||||
return mKeyboardIndex;
|
||||
}
|
||||
|
||||
public void setKeyboardIndex(int idx) {
|
||||
public void setIndex(int idx) {
|
||||
mKeyboardIndex = idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current keyboard
|
||||
*/
|
||||
public KeyboardData get() {
|
||||
if (mAllKeyboards == null) {
|
||||
load();
|
||||
}
|
||||
|
||||
if (mAllKeyboards.size() <= mKeyboardIndex) {
|
||||
mKeyboardIndex = 0;
|
||||
}
|
||||
|
||||
return mAllKeyboards.get(mKeyboardIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.liskovsoft.keyboardaddons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import com.liskovsoft.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
|
||||
public class KeyboardStateManager {
|
||||
private final Context mContext;
|
||||
@@ -16,10 +16,10 @@ public class KeyboardStateManager {
|
||||
|
||||
public void restore() {
|
||||
int idx = mPrefs.getKeyboardIndex();
|
||||
mManager.setKeyboardIndex(idx);
|
||||
mManager.setIndex(idx);
|
||||
}
|
||||
|
||||
public void onNextKeyboard() {
|
||||
mPrefs.setKeyboardIndex(mManager.getKeyboardIndex());
|
||||
mPrefs.setKeyboardIndex(mManager.getIndex());
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.addons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface AddOn {
|
||||
interface AddOnResourceMapping {
|
||||
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.addons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.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.liskovsoft.keyboardaddons.apkkbdfactory.utils.Logger;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.SparseArrayCompat;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.Logger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.addons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -27,7 +27,7 @@ import android.util.AttributeSet;
|
||||
import android.util.Xml;
|
||||
|
||||
//import com.liskovsoft.keyboardaddons.apklangfactory.AnySoftKeyboard;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.utils.Logger;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.Logger;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.addons;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.utils.Logger;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -14,14 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.keyboards;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.liskovsoft.keyboardaddons.KeyboardBuilder;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.addons.AddOn;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.addons.AddOnImpl;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardBuilder;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons.AddOn;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons.AddOnImpl;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class ApkKeyboardAddOnAndBuilder extends AddOnImpl implements KeyboardBuilder {
|
||||
|
||||
@@ -75,9 +77,19 @@ public class ApkKeyboardAddOnAndBuilder extends AddOnImpl implements KeyboardBui
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public android.inputmethodservice.Keyboard createKeyboard() {
|
||||
public android.inputmethodservice.Keyboard createAbcKeyboard() {
|
||||
Context remoteContext = getPackageContext();
|
||||
if (remoteContext == null) return null;
|
||||
return new android.inputmethodservice.Keyboard(remoteContext, mLandscapeResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyboard createSymKeyboard() {
|
||||
return new Keyboard(getPackageContext(), R.xml.sym_en_us);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyboard createNumKeyboard() {
|
||||
return new Keyboard(getPackageContext(), R.xml.number);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.keyboards;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.keyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -23,11 +23,11 @@ import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.liskovsoft.keyboardaddons.KeyboardBuilder;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardFactory;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.addons.AddOn;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.addons.AddOnsFactory;
|
||||
import com.liskovsoft.keyboardaddons.apkkbdfactory.utils.Logger;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardBuilder;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardFactory;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons.AddOn;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.addons.AddOnsFactory;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -140,6 +140,6 @@ public class ApkLangKeyboardFactory extends AddOnsFactory<ApkKeyboardAddOnAndBui
|
||||
if (keyboardBuilders.size() == 0)
|
||||
return new Keyboard(context, 0x7f04000c); // ru keyboard resource id
|
||||
// remember, only one external keyboard supported
|
||||
return keyboardBuilders.get(0).createKeyboard();
|
||||
return keyboardBuilders.get(0).createAbcKeyboard();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log;
|
||||
|
||||
public class BuildConfig {
|
||||
public final static boolean TESTING_BUILD = true;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log;
|
||||
|
||||
public interface LogProvider {
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.log;
|
||||
|
||||
/**
|
||||
* Doesn't do anything. For release.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.xml;
|
||||
|
||||
import android.util.Xml;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.keyboardaddons.apkkbdfactory.utils;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.extkeyboards.utils.xml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -67,8 +67,6 @@ public class XmlWriter {
|
||||
|
||||
/**
|
||||
* Begin to output an entity.
|
||||
*
|
||||
* @param String name of entity.
|
||||
*/
|
||||
public XmlWriter writeEntity(String name) throws IOException {
|
||||
closeOpeningTag(true);
|
||||
@@ -202,7 +200,7 @@ public class XmlWriter {
|
||||
* @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
|
||||
* @param max int values to replace
|
||||
* @return String with n values replacEd
|
||||
*/
|
||||
static public String replaceString(String text, String repl, String with, int max) {
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CheckedSource {
|
||||
List<CheckedItem> getItems();
|
||||
|
||||
interface CheckedItem {
|
||||
long getId();
|
||||
String getTitle();
|
||||
void onClick(boolean checked);
|
||||
boolean getChecked();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class KeyboardInfoAdapter implements CheckedSource {
|
||||
private final Context mContext;
|
||||
private final List<KeyboardInfo> mInfos;
|
||||
|
||||
public KeyboardInfoAdapter(Context context) {
|
||||
mContext = context;
|
||||
mInfos = ResKeyboardInfo.getAllKeyboardInfos(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CheckedItem> getItems() {
|
||||
List<CheckedItem> result = new ArrayList<>();
|
||||
|
||||
int counter = 99;
|
||||
|
||||
for (KeyboardInfo info : mInfos) {
|
||||
int finalCounter = counter++;
|
||||
|
||||
CheckedItem item = new CheckedItem() {
|
||||
private final KeyboardInfo mInfo = info;
|
||||
private final int mCounter = finalCounter;
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return mCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return mInfo.getLangName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(boolean checked) {
|
||||
if (mInfo.isEnabled() == checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInfo.setEnabled(checked);
|
||||
ResKeyboardInfo.updateAllKeyboardInfos(mContext, mInfos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getChecked() {
|
||||
return mInfo.isEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
result.add(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.Keyboard.Key;
|
||||
import android.text.Layout;
|
||||
import android.util.Log;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardBuilder;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardFactory;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeyboard.utils.TextDrawable;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResKeyboardFactory implements KeyboardFactory {
|
||||
private static final String TAG = ResKeyboardFactory.class.getSimpleName();
|
||||
private final Context mContext;
|
||||
private Map<String, Drawable> mCachedSpace;
|
||||
|
||||
public ResKeyboardFactory(Context ctx) {
|
||||
mContext = ctx;
|
||||
mCachedSpace = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends KeyboardBuilder> getAllAvailableKeyboards(Context context) {
|
||||
List<KeyboardBuilder> result = new ArrayList<>();
|
||||
List<KeyboardInfo> infos = ResKeyboardInfo.getAllKeyboardInfos(context);
|
||||
|
||||
for (final KeyboardInfo info : infos) {
|
||||
if (info.isEnabled()) {
|
||||
result.add(createKeyboard(info));
|
||||
}
|
||||
}
|
||||
|
||||
// at least one kbd should be enabled
|
||||
if (result.isEmpty()) {
|
||||
KeyboardInfo defaultKbd = findDefaultKeyboard(infos);
|
||||
result.add(createKeyboard(defaultKbd));
|
||||
defaultKbd.setEnabled(true);
|
||||
//ResKeyboardInfo.updateAllKeyboardInfos(mContext, infos);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private KeyboardInfo findDefaultKeyboard(List<KeyboardInfo> infos) {
|
||||
KeyboardInfo defaultKeyboard = infos.get(0);
|
||||
|
||||
if (LeanKeyPreferences.instance(mContext).getAutodetectLayout()) {
|
||||
Locale defaultLocale = Locale.getDefault();
|
||||
String lang = defaultLocale.getLanguage();
|
||||
|
||||
for (final KeyboardInfo info : infos) {
|
||||
if (info.getLangCode().startsWith(lang)) {
|
||||
defaultKeyboard = info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultKeyboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: create keyboard from xml data
|
||||
*/
|
||||
private KeyboardBuilder createKeyboard(final KeyboardInfo info) {
|
||||
return new KeyboardBuilder() {
|
||||
private final String langCode = info.getLangCode();
|
||||
|
||||
@Override
|
||||
public Keyboard createAbcKeyboard() {
|
||||
String prefix = info.isAzerty() ? "azerty_" : "qwerty_";
|
||||
int kbResId = mContext.getResources().getIdentifier(prefix + langCode, "xml", mContext.getPackageName());
|
||||
Keyboard keyboard = new Keyboard(mContext, kbResId);
|
||||
Log.d(TAG, "Creating keyboard... " + info.getLangName());
|
||||
return localizeKeys(keyboard, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyboard createSymKeyboard() {
|
||||
Keyboard keyboard = new Keyboard(mContext, R.xml.sym_en_us);
|
||||
return localizeKeys(keyboard, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyboard createNumKeyboard() {
|
||||
return new Keyboard(mContext, R.xml.number);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needUpdate() {
|
||||
return ResKeyboardInfo.needUpdate();
|
||||
}
|
||||
|
||||
private Keyboard localizeKeys(Keyboard keyboard, KeyboardInfo info) {
|
||||
List<Key> keys = keyboard.getKeys();
|
||||
|
||||
for (Key key : keys) {
|
||||
if (key.codes[0] == LeanbackKeyboardView.ASCII_SPACE) {
|
||||
localizeSpace(key, info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
private void localizeSpace(Key key, KeyboardInfo info) {
|
||||
if (mCachedSpace.containsKey(info.getLangCode())) {
|
||||
key.icon = mCachedSpace.get(info.getLangCode());
|
||||
return;
|
||||
}
|
||||
|
||||
TextDrawable drawable = new TextDrawable(mContext, key.icon);
|
||||
drawable.setText(info.getLangName());
|
||||
drawable.setTextAlign(Layout.Alignment.ALIGN_CENTER);
|
||||
//Customize text size and color
|
||||
drawable.setTextColor(Color.WHITE);
|
||||
drawable.setTextSizeFactor(0.3f);
|
||||
drawable.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
|
||||
key.icon = drawable;
|
||||
|
||||
mCachedSpace.put(info.getLangCode(), drawable);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.liskovsoft.keyboardaddons.reskbdfactory;
|
||||
package com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardInfo;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.KeyboardInfo;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,6 +15,7 @@ public class ResKeyboardInfo implements KeyboardInfo {
|
||||
private boolean mEnabled;
|
||||
private String mLangCode;
|
||||
private String mLangName;
|
||||
private boolean mIsAzerty;
|
||||
|
||||
public static List<KeyboardInfo> getAllKeyboardInfos(Context ctx) {
|
||||
List<KeyboardInfo> result = new ArrayList<>();
|
||||
@@ -22,9 +24,11 @@ public class ResKeyboardInfo implements KeyboardInfo {
|
||||
String[] pairs = langPair.split("\\|");
|
||||
final String langName = pairs[0];
|
||||
final String langCode = pairs[1];
|
||||
final boolean isAzerty = pairs.length >= 3 && "azerty".equals(pairs[2]);
|
||||
KeyboardInfo info = new ResKeyboardInfo();
|
||||
info.setLangName(langName);
|
||||
info.setLangCode(langCode);
|
||||
info.setIsAzerty(isAzerty);
|
||||
// sync with prefs
|
||||
syncWithPrefs(ctx, info);
|
||||
result.add(info);
|
||||
@@ -43,7 +47,7 @@ public class ResKeyboardInfo implements KeyboardInfo {
|
||||
|
||||
private static void syncWithPrefs(Context ctx, KeyboardInfo info) {
|
||||
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
final boolean kbdEnabled = sharedPreferences.getBoolean(info.getLangCode(), false);
|
||||
final boolean kbdEnabled = sharedPreferences.getBoolean(info.toString(), false);
|
||||
info.setEnabled(kbdEnabled);
|
||||
}
|
||||
|
||||
@@ -51,7 +55,7 @@ public class ResKeyboardInfo implements KeyboardInfo {
|
||||
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
|
||||
final SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putBoolean(info.getLangCode(), info.isEnabled());
|
||||
editor.putBoolean(info.toString(), info.isEnabled());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@@ -88,4 +92,20 @@ public class ResKeyboardInfo implements KeyboardInfo {
|
||||
public void setEnabled(boolean enabled) {
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAzerty() {
|
||||
return mIsAzerty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsAzerty(boolean isAzerty) {
|
||||
mIsAzerty = isAzerty;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{Name: %s, Code: %s, IsAzerty: %b}", mLangName, mLangCode, mIsAzerty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.liskovsoft.leankeyboard.addons.resize;
|
||||
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KeyboardWrapper extends Keyboard {
|
||||
private Keyboard mKeyboard;
|
||||
private int mHeight = -1;
|
||||
private float mHeightFactor = 1.0f;
|
||||
private float mWidthFactor = 1.0f;
|
||||
|
||||
public KeyboardWrapper(Context context, int xmlLayoutResId) {
|
||||
super(context, xmlLayoutResId);
|
||||
}
|
||||
|
||||
public KeyboardWrapper(Context context, int xmlLayoutResId, int modeId, int width, int height) {
|
||||
super(context, xmlLayoutResId, modeId, width, height);
|
||||
}
|
||||
|
||||
public KeyboardWrapper(Context context, int xmlLayoutResId, int modeId) {
|
||||
super(context, xmlLayoutResId, modeId);
|
||||
}
|
||||
|
||||
public KeyboardWrapper(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) {
|
||||
super(context, layoutTemplateResId, characters, columns, horizontalPadding);
|
||||
}
|
||||
|
||||
public static KeyboardWrapper from(Keyboard keyboard, Context context) {
|
||||
KeyboardWrapper wrapper = new KeyboardWrapper(context, R.xml.empty_kbd);
|
||||
wrapper.mKeyboard = keyboard;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Key> getKeys() {
|
||||
return mKeyboard.getKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Key> getModifierKeys() {
|
||||
return mKeyboard.getModifierKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return (int)(mKeyboard.getHeight() * mHeightFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinWidth() {
|
||||
return (int)(mKeyboard.getMinWidth() * mWidthFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setShifted(boolean shiftState) {
|
||||
return mKeyboard.setShifted(shiftState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShifted() {
|
||||
return mKeyboard.isShifted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShiftKeyIndex() {
|
||||
return mKeyboard.getShiftKeyIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getNearestKeys(int x, int y) {
|
||||
return mKeyboard.getNearestKeys(x, y);
|
||||
}
|
||||
|
||||
public void setHeightFactor(float factor) {
|
||||
mHeightFactor = factor;
|
||||
}
|
||||
|
||||
public void setWidthFactor(float factor) {
|
||||
mWidthFactor = factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper fix: {@link LeanbackKeyboardContainer#onModeChangeClick}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof Keyboard) {
|
||||
return mKeyboard.equals(obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.liskovsoft.leankeyboard.addons.resize;
|
||||
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.Keyboard.Key;
|
||||
import android.util.AttributeSet;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ResizeableLeanbackKeyboardView extends LeanbackKeyboardView {
|
||||
private final LeanKeyPreferences mPrefs;
|
||||
private final int mKeyTextSizeOrigin;
|
||||
private final int mModeChangeTextSizeOrigin;
|
||||
private final float mSizeFactor = 1.3f;
|
||||
private int mKeyOriginWidth;
|
||||
|
||||
public ResizeableLeanbackKeyboardView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mPrefs = LeanKeyPreferences.instance(getContext());
|
||||
mKeyTextSizeOrigin = mKeyTextSize;
|
||||
mModeChangeTextSizeOrigin = mModeChangeTextSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyboard(Keyboard keyboard) {
|
||||
if (mPrefs.getEnlargeKeyboard()) {
|
||||
mKeyTextSize = (int) (mKeyTextSizeOrigin * mSizeFactor);
|
||||
mModeChangeTextSize = (int) (mModeChangeTextSizeOrigin * mSizeFactor);
|
||||
|
||||
keyboard = updateKeyboard(keyboard);
|
||||
} else {
|
||||
mKeyTextSize = mKeyTextSizeOrigin;
|
||||
mModeChangeTextSize = mModeChangeTextSizeOrigin;
|
||||
}
|
||||
|
||||
mPaint.setTextSize(mKeyTextSize);
|
||||
|
||||
super.setKeyboard(keyboard);
|
||||
}
|
||||
|
||||
private Keyboard updateKeyboard(Keyboard keyboard) {
|
||||
List<Key> keys = keyboard.getKeys();
|
||||
|
||||
if (isNotSizedYet(keys.get(0))) {
|
||||
for (Key key : keys) {
|
||||
key.width *= mSizeFactor;
|
||||
key.height *= mSizeFactor;
|
||||
key.gap *= mSizeFactor;
|
||||
key.x *= mSizeFactor;
|
||||
key.y *= mSizeFactor;
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardWrapper wrapper = KeyboardWrapper.from(keyboard, getContext());
|
||||
wrapper.setHeightFactor(mSizeFactor);
|
||||
wrapper.setWidthFactor(mSizeFactor);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private boolean isNotSizedYet(Key key) {
|
||||
boolean result = false;
|
||||
|
||||
if (mKeyOriginWidth == 0) {
|
||||
mKeyOriginWidth = key.width;
|
||||
}
|
||||
|
||||
if (mKeyOriginWidth == key.width) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.liskovsoft.leankeyboard.addons.theme;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardView;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class ThemeManager {
|
||||
private static final String TAG = ThemeManager.class.getSimpleName();
|
||||
private final Context mContext;
|
||||
private final RelativeLayout mRootView;
|
||||
private final LeanKeyPreferences mPrefs;
|
||||
|
||||
public ThemeManager(Context context, RelativeLayout rootView) {
|
||||
mContext = context;
|
||||
mRootView = rootView;
|
||||
mPrefs = LeanKeyPreferences.instance(mContext);
|
||||
}
|
||||
|
||||
public void updateKeyboardTheme() {
|
||||
String currentThemeId = mPrefs.getCurrentTheme();
|
||||
|
||||
if (LeanKeyPreferences.THEME_DEFAULT.equals(currentThemeId)) {
|
||||
applyKeyboardColors(
|
||||
R.color.keyboard_background,
|
||||
R.color.candidate_background,
|
||||
R.color.enter_key_font_color,
|
||||
R.color.key_text_default
|
||||
);
|
||||
applyShiftDrawable(-1);
|
||||
} else {
|
||||
applyForTheme((String themeId) -> {
|
||||
Resources resources = mContext.getResources();
|
||||
int keyboardBackgroundResId = resources.getIdentifier("keyboard_background_" + themeId.toLowerCase(), "color", mContext.getPackageName());
|
||||
int candidateBackgroundResId = resources.getIdentifier("candidate_background_" + themeId.toLowerCase(), "color", mContext.getPackageName());
|
||||
int enterFontColorResId = resources.getIdentifier("enter_key_font_color_" + themeId.toLowerCase(), "color", mContext.getPackageName());
|
||||
int keyTextColorResId = resources.getIdentifier("key_text_default_" + themeId.toLowerCase(), "color", mContext.getPackageName());
|
||||
|
||||
applyKeyboardColors(
|
||||
keyboardBackgroundResId,
|
||||
candidateBackgroundResId,
|
||||
enterFontColorResId,
|
||||
keyTextColorResId
|
||||
);
|
||||
|
||||
int shiftLockOnResId = resources.getIdentifier("ic_ime_shift_lock_on_" + themeId.toLowerCase(), "drawable", mContext.getPackageName());
|
||||
|
||||
applyShiftDrawable(shiftLockOnResId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSuggestionsTheme() {
|
||||
String currentTheme = mPrefs.getCurrentTheme();
|
||||
|
||||
if (LeanKeyPreferences.THEME_DEFAULT.equals(currentTheme)) {
|
||||
applySuggestionsColors(
|
||||
R.color.candidate_font_color
|
||||
);
|
||||
} else {
|
||||
applyForTheme((String themeId) -> {
|
||||
Resources resources = mContext.getResources();
|
||||
int candidateFontColorResId = resources.getIdentifier("candidate_font_color_" + themeId.toLowerCase(), "color", mContext.getPackageName());
|
||||
applySuggestionsColors(candidateFontColorResId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void applyKeyboardColors(
|
||||
int keyboardBackground,
|
||||
int candidateBackground,
|
||||
int enterFontColor,
|
||||
int keyTextColor) {
|
||||
|
||||
RelativeLayout rootLayout = mRootView.findViewById(R.id.root_ime);
|
||||
|
||||
if (rootLayout != null) {
|
||||
rootLayout.setBackgroundColor(ContextCompat.getColor(mContext, keyboardBackground));
|
||||
}
|
||||
|
||||
View candidateLayout = mRootView.findViewById(R.id.candidate_background);
|
||||
|
||||
if (candidateLayout != null) {
|
||||
candidateLayout.setBackgroundColor(ContextCompat.getColor(mContext, candidateBackground));
|
||||
}
|
||||
|
||||
Button enterButton = mRootView.findViewById(R.id.enter);
|
||||
|
||||
if (enterButton != null) {
|
||||
enterButton.setTextColor(ContextCompat.getColor(mContext, enterFontColor));
|
||||
}
|
||||
|
||||
LeanbackKeyboardView keyboardView = mRootView.findViewById(R.id.main_keyboard);
|
||||
|
||||
if (keyboardView != null) {
|
||||
keyboardView.setKeyTextColor(ContextCompat.getColor(mContext, keyTextColor));
|
||||
}
|
||||
}
|
||||
|
||||
private void applySuggestionsColors(int candidateFontColor) {
|
||||
LinearLayout suggestions = mRootView.findViewById(R.id.suggestions);
|
||||
|
||||
if (suggestions != null) {
|
||||
int childCount = suggestions.getChildCount();
|
||||
|
||||
Log.d(TAG, "Number of suggestions: " + childCount);
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = suggestions.getChildAt(i);
|
||||
|
||||
Button candidateButton = child.findViewById(R.id.text);
|
||||
|
||||
if (candidateButton != null) {
|
||||
candidateButton.setTextColor(ContextCompat.getColor(mContext, candidateFontColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyShiftDrawable(int resId) {
|
||||
LeanbackKeyboardView keyboardView = mRootView.findViewById(R.id.main_keyboard);
|
||||
|
||||
if (keyboardView != null && resId > 0) {
|
||||
Drawable drawable = ContextCompat.getDrawable(mContext, resId);
|
||||
|
||||
keyboardView.setCapsLockDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyForTheme(ThemeCallback callback) {
|
||||
String currentThemeId = mPrefs.getCurrentTheme();
|
||||
Resources resources = mContext.getResources();
|
||||
String[] themes = resources.getStringArray(R.array.keyboard_themes);
|
||||
|
||||
for (String theme : themes) {
|
||||
String[] split = theme.split("\\|");
|
||||
String themeName = split[0];
|
||||
String themeId = split[1];
|
||||
|
||||
if (currentThemeId.equals(themeId)) {
|
||||
callback.onThemeFound(themeId);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface ThemeCallback {
|
||||
void onThemeFound(String themeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
interface ActivityListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
public interface RecognizerCallback {
|
||||
void openSearchPage(String searchText);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class RecognizerIntentActivity extends AppCompatActivity {
|
||||
public static RecognizerCallback sCallback;
|
||||
private VoiceSearchBridge mBridge;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mBridge = new VoiceSearchBridge(this, searchText -> sCallback.openSearchPage(searchText));
|
||||
|
||||
mBridge.displaySpeechRecognizers();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
mBridge.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class RecognizerIntentWrapper {
|
||||
private final Context mContext;
|
||||
private RecognizerCallback mCallback;
|
||||
|
||||
public RecognizerIntentWrapper(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void setListener(RecognizerCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void startListening() {
|
||||
if (mCallback != null) {
|
||||
RecognizerIntentActivity.sCallback = mCallback;
|
||||
Intent intent = new Intent(mContext, RecognizerIntentActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
interface SearchCallback {
|
||||
void openSearchPage(String searchText);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.speech.RecognizerIntent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class SystemVoiceDialog implements VoiceDialog, ActivityListener {
|
||||
private static final int SPEECH_REQUEST_CODE = 11;
|
||||
private final Activity mActivity;
|
||||
private final SearchCallback mCallback;
|
||||
|
||||
public SystemVoiceDialog(Activity activity, SearchCallback callback) {
|
||||
mActivity = activity;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public boolean displaySpeechRecognizer() {
|
||||
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
||||
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, SPEECH_REQUEST_CODE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// got speech-to-text result, switch to the search page
|
||||
if (requestCode == SPEECH_REQUEST_CODE && resultCode == -1) {
|
||||
List<String> results = data.getStringArrayListExtra(
|
||||
RecognizerIntent.EXTRA_RESULTS);
|
||||
if (results != null && results.size() > 0) {
|
||||
mCallback.openSearchPage(results.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
interface VoiceDialog {
|
||||
boolean displaySpeechRecognizer();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.algolia.instantsearch.voice.VoiceSpeechRecognizer;
|
||||
import com.algolia.instantsearch.voice.ui.Voice;
|
||||
import com.algolia.instantsearch.voice.ui.VoiceInputDialogFragment;
|
||||
import com.algolia.instantsearch.voice.ui.VoicePermissionDialogFragment;
|
||||
|
||||
class VoiceOverlayDialog implements VoiceDialog, VoiceSpeechRecognizer.ResultsListener {
|
||||
private final AppCompatActivity mActivity;
|
||||
private final SearchCallback mCallback;
|
||||
|
||||
private enum Tag {
|
||||
Permission,
|
||||
Voice
|
||||
}
|
||||
|
||||
public VoiceOverlayDialog(AppCompatActivity activity, SearchCallback callback) {
|
||||
mActivity = activity;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean displaySpeechRecognizer() {
|
||||
if (!Voice.isRecordAudioPermissionGranted(mActivity)) {
|
||||
new VoicePermissionDialogFragment()
|
||||
.show(mActivity.getSupportFragmentManager(), Tag.Permission.name());
|
||||
} else {
|
||||
showVoiceDialog();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showVoiceDialog() {
|
||||
VoicePermissionDialogFragment dialog = getPermissionDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
VoiceInputDialogFragment voiceDialog = getVoiceDialog();
|
||||
|
||||
if (voiceDialog == null) {
|
||||
voiceDialog = new VoiceInputDialogFragment();
|
||||
} else {
|
||||
voiceDialog.dismiss();
|
||||
}
|
||||
|
||||
voiceDialog.show(mActivity.getSupportFragmentManager(), Tag.Voice.name());
|
||||
}
|
||||
|
||||
private VoicePermissionDialogFragment getPermissionDialog() {
|
||||
return (VoicePermissionDialogFragment) mActivity.getSupportFragmentManager().findFragmentByTag(Tag.Permission.name());
|
||||
}
|
||||
|
||||
private VoiceInputDialogFragment getVoiceDialog() {
|
||||
return (VoiceInputDialogFragment) mActivity.getSupportFragmentManager().findFragmentByTag(Tag.Voice.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(String[] strings) {
|
||||
if (strings.length > 0) {
|
||||
mCallback.openSearchPage(strings[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.liskovsoft.leankeyboard.addons.voice;
|
||||
|
||||
import android.content.Intent;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class VoiceSearchBridge implements SearchCallback {
|
||||
private final ArrayList<VoiceDialog> mDialogs;
|
||||
private final AppCompatActivity mActivity;
|
||||
private final RecognizerCallback mCallback;
|
||||
|
||||
public VoiceSearchBridge(AppCompatActivity activity, RecognizerCallback callback) {
|
||||
mActivity = activity;
|
||||
mCallback = callback;
|
||||
mDialogs = new ArrayList<>();
|
||||
mDialogs.add(new SystemVoiceDialog(activity, this));
|
||||
mDialogs.add(new VoiceOverlayDialog(activity, this));
|
||||
}
|
||||
|
||||
public void displaySpeechRecognizers() {
|
||||
for (VoiceDialog dialog : mDialogs) {
|
||||
if (dialog.displaySpeechRecognizer()) { // fist successful attempt is used
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
for (VoiceDialog dialog : mDialogs) {
|
||||
if (dialog instanceof ActivityListener) {
|
||||
((ActivityListener) dialog).onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openSearchPage(String searchText) {
|
||||
if (mCallback != null) {
|
||||
mCallback.openSearchPage(searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.liskovsoft.leankeyboard.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.leanback.app.GuidedStepSupportFragment;
|
||||
import androidx.leanback.widget.GuidedAction;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BaseSettingsFragment extends GuidedStepSupportFragment {
|
||||
private Map<Long, CheckedAction> mCheckedActions = new LinkedHashMap<>();
|
||||
private Map<Long, NextAction> mNextActions = new LinkedHashMap<>();
|
||||
private long mId;
|
||||
|
||||
protected interface OnChecked {
|
||||
void onChecked(boolean checked);
|
||||
}
|
||||
|
||||
protected interface GetChecked {
|
||||
boolean getChecked();
|
||||
}
|
||||
|
||||
protected interface OnClick {
|
||||
void onClick();
|
||||
}
|
||||
|
||||
// Radio action
|
||||
|
||||
protected void addRadioAction(int titleResId, int descResId, GetChecked getChecked, OnChecked onChecked) {
|
||||
addRadioAction(getString(titleResId), getString(descResId), getChecked, onChecked);
|
||||
}
|
||||
|
||||
protected void addRadioAction(int titleRedId, GetChecked getChecked, OnChecked onChecked) {
|
||||
addRadioAction(getString(titleRedId), getChecked, onChecked);
|
||||
}
|
||||
|
||||
protected void addRadioAction(String title, GetChecked getChecked, OnChecked onChecked) {
|
||||
mCheckedActions.put(mId++, new RadioAction(title, getChecked, onChecked));
|
||||
}
|
||||
|
||||
protected void addRadioAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) {
|
||||
mCheckedActions.put(mId++, new RadioAction(title, desc, getChecked, onChecked));
|
||||
}
|
||||
|
||||
// Checked action
|
||||
|
||||
protected void addCheckedAction(int titleResId, int descResId, GetChecked getChecked, OnChecked onChecked) {
|
||||
addCheckedAction(getString(titleResId), getString(descResId), getChecked, onChecked);
|
||||
}
|
||||
|
||||
protected void addCheckedAction(int titleRedId, GetChecked getChecked, OnChecked onChecked) {
|
||||
addCheckedAction(getString(titleRedId), getChecked, onChecked);
|
||||
}
|
||||
|
||||
protected void addCheckedAction(String title, GetChecked getChecked, OnChecked onChecked) {
|
||||
mCheckedActions.put(mId++, new CheckedAction(title, getChecked, onChecked));
|
||||
}
|
||||
|
||||
protected void addCheckedAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) {
|
||||
mCheckedActions.put(mId++, new CheckedAction(title, desc, getChecked, onChecked));
|
||||
}
|
||||
|
||||
// Nested action
|
||||
|
||||
protected void addNextAction(int resId, OnClick onClick) {
|
||||
mNextActions.put(mId++, new NextAction(resId, onClick));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
|
||||
for (long id : mCheckedActions.keySet()) {
|
||||
addCheckedItem(id, mCheckedActions.get(id), actions);
|
||||
}
|
||||
|
||||
for (long id : mNextActions.keySet()) {
|
||||
addNextItem(id, mNextActions.get(id), actions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuidedActionClicked(GuidedAction action) {
|
||||
CheckedAction checkedAction = mCheckedActions.get(action.getId());
|
||||
|
||||
if (checkedAction != null) {
|
||||
checkedAction.onChecked(action.isChecked());
|
||||
}
|
||||
|
||||
NextAction nextAction = mNextActions.get(action.getId());
|
||||
|
||||
if (nextAction != null) {
|
||||
nextAction.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
private void addNextItem(long id, NextAction nextAction, List<GuidedAction> actions) {
|
||||
GuidedAction action = new GuidedAction.Builder(getActivity())
|
||||
.id(id)
|
||||
.hasNext(true)
|
||||
.title(nextAction.getResId()).build();
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
private void addCheckedItem(long id, CheckedAction checkedAction, List<GuidedAction> actions) {
|
||||
GuidedAction action = new GuidedAction.Builder(getActivity())
|
||||
.checked(checkedAction.isChecked())
|
||||
.checkSetId(checkedAction.getItemTypeId())
|
||||
.id(id)
|
||||
.title(checkedAction.getTitle())
|
||||
.build();
|
||||
|
||||
if (checkedAction.getDesc() != null) {
|
||||
action.setDescription(checkedAction.getDesc());
|
||||
}
|
||||
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
private static class RadioAction extends CheckedAction {
|
||||
public RadioAction(String title, GetChecked getChecked, OnChecked onChecked) {
|
||||
super(title, getChecked, onChecked);
|
||||
}
|
||||
|
||||
public RadioAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) {
|
||||
super(title, desc, getChecked, onChecked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemTypeId() {
|
||||
return GuidedAction.DEFAULT_CHECK_SET_ID;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CheckedAction {
|
||||
private final String mDesc;
|
||||
private final GetChecked mGetChecked;
|
||||
private final OnChecked mOnChecked;
|
||||
private final String mTitle;
|
||||
|
||||
public CheckedAction(String title, GetChecked getChecked, OnChecked onChecked) {
|
||||
this(title, null, getChecked, onChecked);
|
||||
}
|
||||
|
||||
public CheckedAction(String title, String desc, GetChecked getChecked, OnChecked onChecked) {
|
||||
mTitle = title;
|
||||
mDesc = desc;
|
||||
mGetChecked = getChecked;
|
||||
mOnChecked = onChecked;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return mDesc;
|
||||
}
|
||||
|
||||
public boolean isChecked() {
|
||||
return mGetChecked.getChecked();
|
||||
}
|
||||
|
||||
public void onChecked(boolean checked) {
|
||||
mOnChecked.onChecked(checked);
|
||||
}
|
||||
|
||||
public int getItemTypeId() {
|
||||
return GuidedAction.CHECKBOX_CHECK_SET_ID;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NextAction {
|
||||
private final int mResId;
|
||||
private final OnClick mOnClick;
|
||||
|
||||
public NextAction(int resId, OnClick onClick) {
|
||||
mResId = resId;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
public int getResId() {
|
||||
return mResId;
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
mOnClick.onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.liskovsoft.leankeyboard.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.leanback.widget.GuidanceStylist.Guidance;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.KeyboardInfoAdapter;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.CheckedSource;
|
||||
import com.liskovsoft.leankeyboard.addons.keyboards.intkeyboards.CheckedSource.CheckedItem;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class KbLayoutFragment extends BaseSettingsFragment {
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
initCheckedItems();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Guidance onCreateGuidance(Bundle savedInstanceState) {
|
||||
String title = getActivity().getResources().getString(R.string.kb_layout);
|
||||
String desc = getActivity().getResources().getString(R.string.kb_layout_desc);
|
||||
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher);
|
||||
|
||||
return new Guidance(
|
||||
title,
|
||||
desc,
|
||||
"",
|
||||
icon
|
||||
);
|
||||
}
|
||||
|
||||
private void initCheckedItems() {
|
||||
CheckedSource source = new KeyboardInfoAdapter(getActivity());
|
||||
for (CheckedItem item : source.getItems()) {
|
||||
addCheckedAction(item.getTitle(), item::getChecked, item::onClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.liskovsoft.leankeyboard.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.leanback.app.GuidedStepSupportFragment;
|
||||
import androidx.leanback.widget.GuidanceStylist.Guidance;
|
||||
import com.liskovsoft.leankeyboard.activity.settings.KbActivationActivity;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class KbSettingsFragment extends BaseSettingsFragment {
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
addNextAction(R.string.activate_keyboard, () -> {
|
||||
Intent intent = new Intent(getActivity(), KbActivationActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
addNextAction(R.string.change_layout, () -> startGuidedFragment(new KbLayoutFragment()));
|
||||
|
||||
addNextAction(R.string.change_theme, () -> startGuidedFragment(new KbThemeFragment()));
|
||||
|
||||
addNextAction(R.string.misc, () -> startGuidedFragment(new MiscFragment()));
|
||||
|
||||
addNextAction(R.string.about_desc, () -> startGuidedFragment(new AboutFragment()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Guidance onCreateGuidance(Bundle savedInstanceState) {
|
||||
String title = getActivity().getResources().getString(R.string.ime_name);
|
||||
String desc = getActivity().getResources().getString(R.string.kb_settings_desc);
|
||||
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher);
|
||||
|
||||
return new Guidance(
|
||||
title,
|
||||
desc,
|
||||
"",
|
||||
icon
|
||||
);
|
||||
}
|
||||
|
||||
private void startGuidedFragment(GuidedStepSupportFragment fragment) {
|
||||
if (getFragmentManager() != null) {
|
||||
GuidedStepSupportFragment.add(getFragmentManager(), fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.liskovsoft.leankeyboard.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.leanback.widget.GuidanceStylist.Guidance;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class KbThemeFragment extends BaseSettingsFragment {
|
||||
private Context mContext;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mContext = context;
|
||||
|
||||
initRadioItems();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Guidance onCreateGuidance(Bundle savedInstanceState) {
|
||||
String title = getActivity().getResources().getString(R.string.kb_theme);
|
||||
String desc = getActivity().getResources().getString(R.string.kb_theme_desc);
|
||||
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher);
|
||||
|
||||
return new Guidance(
|
||||
title,
|
||||
desc,
|
||||
"",
|
||||
icon
|
||||
);
|
||||
}
|
||||
|
||||
private void initRadioItems() {
|
||||
String[] themes = mContext.getResources().getStringArray(R.array.keyboard_themes);
|
||||
|
||||
LeanKeyPreferences prefs = LeanKeyPreferences.instance(mContext);
|
||||
String currentTheme = prefs.getCurrentTheme();
|
||||
|
||||
for (String theme : themes) {
|
||||
String[] split = theme.split("\\|");
|
||||
String themeName = split[0];
|
||||
String themeId = split[1];
|
||||
addRadioAction(themeName, () -> currentTheme.equals(themeId), (checked) -> prefs.setCurrentTheme(themeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.liskovsoft.leankeyboard.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.leanback.widget.GuidanceStylist.Guidance;
|
||||
import com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity2;
|
||||
import com.liskovsoft.leankeyboard.helpers.Helpers;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class MiscFragment extends BaseSettingsFragment {
|
||||
private LeanKeyPreferences mPrefs;
|
||||
private Context mContext;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
mContext = context;
|
||||
mPrefs = LeanKeyPreferences.instance(getActivity());
|
||||
addCheckedAction(R.string.keep_on_screen, R.string.keep_on_screen_desc, mPrefs::getForceShowKeyboard, mPrefs::setForceShowKeyboard);
|
||||
addCheckedAction(R.string.increase_kbd_size, R.string.increase_kbd_size_desc, mPrefs::getEnlargeKeyboard, mPrefs::setEnlargeKeyboard);
|
||||
addCheckedAction(R.string.enable_suggestions, R.string.enable_suggestions_desc, mPrefs::getSuggestionsEnabled, mPrefs::setSuggestionsEnabled);
|
||||
addCheckedAction(R.string.show_launcher_icon, R.string.show_launcher_icon_desc, this::getLauncherIconShown, this::setLauncherIconShown);
|
||||
addCheckedAction(R.string.enable_cyclic_navigation, R.string.enable_cyclic_navigation_desc, mPrefs::getCyclicNavigationEnabled, mPrefs::setCyclicNavigationEnabled);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Guidance onCreateGuidance(Bundle savedInstanceState) {
|
||||
String title = getActivity().getResources().getString(R.string.misc);
|
||||
String desc = getActivity().getResources().getString(R.string.misc_desc);
|
||||
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_launcher);
|
||||
|
||||
return new Guidance(
|
||||
title,
|
||||
desc,
|
||||
"",
|
||||
icon
|
||||
);
|
||||
}
|
||||
|
||||
private void setLauncherIconShown(boolean shown) {
|
||||
Helpers.setLauncherIconShown(mContext, KbSettingsActivity2.class, shown);
|
||||
}
|
||||
|
||||
private boolean getLauncherIconShown() {
|
||||
return Helpers.getLauncherIconShown(mContext, KbSettingsActivity2.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.liskovsoft.leankeyboard.helpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.util.Log;
|
||||
import androidx.core.content.pm.PackageInfoCompat;
|
||||
|
||||
public class AppInfoHelpers {
|
||||
private static final String TAG = AppInfoHelpers.class.getSimpleName();
|
||||
|
||||
public static String getAppVersion(Context context) {
|
||||
return formatAppVersion(getAppVersionName(context), getActivityLabel(context));
|
||||
}
|
||||
|
||||
public static String getAppVersionRobust(Context context, String launchActivityName) {
|
||||
return formatAppVersion(getAppVersionName(context), getActivityLabelRobust(context, launchActivityName));
|
||||
}
|
||||
|
||||
private static String formatAppVersion(String version, String label) {
|
||||
return String.format("%s (%s)", version, label);
|
||||
}
|
||||
|
||||
public static String getActivityLabelRobust(Context context, String launchActivityName) {
|
||||
return getActivityLabel(context, launchActivityName);
|
||||
}
|
||||
|
||||
public static int getAppVersionCode(Context context) {
|
||||
PackageInfo packageInfo = createPackageInfo(context);
|
||||
if (packageInfo != null) {
|
||||
return (int) PackageInfoCompat.getLongVersionCode(packageInfo);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static String getAppVersionName(Context context) {
|
||||
PackageInfo packageInfo = createPackageInfo(context);
|
||||
if (packageInfo != null) {
|
||||
return packageInfo.versionName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PackageInfo createPackageInfo(Context context) {
|
||||
try {
|
||||
return context
|
||||
.getPackageManager()
|
||||
.getPackageInfo(context.getPackageName(), 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.d(TAG, e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getActivityLabel(Context context) {
|
||||
return getActivityLabel(context, (String) null);
|
||||
}
|
||||
|
||||
public static String getActivityLabel(Context context, String cls) {
|
||||
if (cls != null) {
|
||||
return getActivityLabel(context, new ComponentName(context, cls));
|
||||
} else if (context instanceof Activity) {
|
||||
Activity activity = (Activity) context;
|
||||
return getActivityLabel(context, activity.getComponentName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getActivityLabel(Context context, ComponentName name) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
ActivityInfo info = null;
|
||||
|
||||
try {
|
||||
info = pm.getActivityInfo(name, 0);
|
||||
return context.getResources().getString(info.labelRes);
|
||||
} catch (NameNotFoundException | NotFoundException e) {
|
||||
if (info != null) {
|
||||
return Helpers.getSimpleClassName(info.name); // label not found, return simple class name
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ActivityInfo getActivityInfo(Context ctx, ComponentName componentName) {
|
||||
ActivityInfo ai = null;
|
||||
try {
|
||||
ai = ctx.getPackageManager().getActivityInfo(componentName, PackageManager.GET_META_DATA);
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
public static ProviderInfo getProviderInfo(Context ctx, ComponentName componentName) {
|
||||
ProviderInfo ai = null;
|
||||
try {
|
||||
ai = ctx.getPackageManager().getProviderInfo(componentName, PackageManager.GET_META_DATA);
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
public static ActivityInfo[] getActivityList(Context context) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
ActivityInfo[] list = null;
|
||||
|
||||
try {
|
||||
PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
|
||||
list = info.activities;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean isActivityExists(Context context, String actName) {
|
||||
ActivityInfo[] list = getActivityList(context);
|
||||
|
||||
if (list != null) {
|
||||
for (ActivityInfo activityInfo : list) {
|
||||
if (activityInfo.name.contains(actName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getApplicationName(Context context) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ApplicationInfo applicationInfo = context.getApplicationInfo();
|
||||
int stringId = applicationInfo.labelRes;
|
||||
return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package com.liskovsoft.leankeyboard.helpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import com.liskovsoft.leankeyboard.activity.settings.KbSettingsActivity;
|
||||
import com.liskovsoft.leankeyboard.utils.LocaleUtility;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Helpers {
|
||||
/**
|
||||
* Simple wildcard matching routine. Implemented without regex. So you may expect huge performance boost.
|
||||
* @param host
|
||||
* @param mask
|
||||
* @return
|
||||
*/
|
||||
public static boolean matchSubstr(String host, String mask) {
|
||||
String[] sections = mask.split("\\*");
|
||||
String text = host;
|
||||
for (String section : sections) {
|
||||
int index = text.indexOf(section);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
text = text.substring(index + section.length());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean matchSubstrNoCase(String host, String mask) {
|
||||
return matchSubstr(host.toLowerCase(), mask.toLowerCase());
|
||||
}
|
||||
|
||||
public static InputStream getAsset(Context ctx, String fileName) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = ctx.getAssets().open(fileName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
public static String encodeURI(byte[] data) {
|
||||
try {
|
||||
// make behaviour of java uri-encode the same as javascript's one
|
||||
return URLEncoder.encode(new String(data, "UTF-8"), "UTF-8").replace("+", "%20");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean floatEquals(float num1, float num2) {
|
||||
float epsilon = 0.1f;
|
||||
return Math.abs(num1 - num2) < epsilon;
|
||||
}
|
||||
|
||||
public static String getDeviceName() {
|
||||
return String.format("%s (%s)", Build.MODEL, Build.PRODUCT);
|
||||
}
|
||||
|
||||
public static boolean deviceMatch(String[] devicesToProcess) {
|
||||
String thisDeviceName = Helpers.getDeviceName();
|
||||
for (String deviceName : devicesToProcess) {
|
||||
boolean match = matchSubstrNoCase(thisDeviceName, deviceName);
|
||||
if (match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String toString(Throwable ex) {
|
||||
if (ex instanceof IllegalStateException &&
|
||||
ex.getCause() != null) {
|
||||
ex = ex.getCause();
|
||||
}
|
||||
return String.format("%s: %s", ex.getClass().getCanonicalName(), ex.getMessage());
|
||||
}
|
||||
|
||||
public static String toString(InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
|
||||
String result = s.hasNext() ? s.next() : "";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static InputStream toStream(String content) {
|
||||
return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static void postOnUiThread(Runnable runnable) {
|
||||
new Handler(Looper.getMainLooper()).post(runnable);
|
||||
}
|
||||
|
||||
public static String unixToLocalDate(Context ctx, String timestamp) {
|
||||
Locale current = LocaleUtility.getSystemLocale(ctx);
|
||||
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, current);
|
||||
Date date;
|
||||
if (timestamp == null) {
|
||||
date = new Date();
|
||||
} else {
|
||||
date = new Date((long) Integer.parseInt(timestamp) * 1000);
|
||||
}
|
||||
return dateFormat.format(date);
|
||||
}
|
||||
|
||||
public static String runMultiMatcher(String input, String... patterns) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern regex;
|
||||
Matcher matcher;
|
||||
String result = null;
|
||||
for (String pattern : patterns) {
|
||||
regex = Pattern.compile(pattern);
|
||||
matcher = regex.matcher(input);
|
||||
|
||||
if (matcher.find()) {
|
||||
result = matcher.group(matcher.groupCount()); // get last group
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isCallable(Context ctx, Intent intent) {
|
||||
List<ResolveInfo> list = ctx.getPackageManager().queryIntentActivities(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
return list.size() > 0;
|
||||
}
|
||||
|
||||
public static String getSimpleClassName(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return name.substring(name.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
private static 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 static 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);
|
||||
}
|
||||
|
||||
public static Class<?> classForName(String clazz) {
|
||||
Class<?> serviceClass;
|
||||
try {
|
||||
serviceClass = Class.forName(clazz);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return serviceClass;
|
||||
}
|
||||
|
||||
public static String getPackageName(Context ctx) {
|
||||
return ctx.getPackageName();
|
||||
}
|
||||
|
||||
public static void startActivity(Context context, Class<?> activityClass) {
|
||||
try {
|
||||
Intent intent = new Intent(context, activityClass);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
MessageHelpers.showLongMessage(context, "Can't start: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean startIntent(final Context context, final Intent intent) {
|
||||
if (intent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isGenymotion() {
|
||||
String deviceName = getDeviceName();
|
||||
|
||||
return deviceName.contains("(vbox86p)");
|
||||
}
|
||||
|
||||
public static void setLauncherIconShown(Context context, Class<?> activityClass, boolean shown) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ComponentName component = new ComponentName(context, activityClass);
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
shown ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean getLauncherIconShown(Context context, Class<?> activityClass) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ComponentName component = new ComponentName(context, activityClass);
|
||||
return pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.liskovsoft.leankeyboard.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MessageHelpers {
|
||||
private static long sExitMsgTimeMS = 0;
|
||||
private static final int LONG_MSG_TIMEOUT = 5000;
|
||||
|
||||
public static void showMessage(final Context ctx, final String TAG, final Throwable ex) {
|
||||
showMessage(ctx, TAG, Helpers.toString(ex));
|
||||
}
|
||||
|
||||
public static void showMessage(final Context ctx, final String TAG, final String msg) {
|
||||
showMessage(ctx, String.format("%s: %s", TAG, msg));
|
||||
}
|
||||
|
||||
public static void showMessageThrottled(final Context ctx, final String msg) {
|
||||
// throttle msg calls
|
||||
if (System.currentTimeMillis() - sExitMsgTimeMS < LONG_MSG_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
sExitMsgTimeMS = System.currentTimeMillis();
|
||||
showMessage(ctx, msg);
|
||||
}
|
||||
|
||||
public static void showMessage(final Context ctx, final String msg) {
|
||||
if (ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable toast = () -> {
|
||||
try {
|
||||
Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show();
|
||||
} catch (Exception ex) { // NPE fix
|
||||
ex.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
toast.run();
|
||||
} else {
|
||||
new Handler(Looper.getMainLooper()).post(toast);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows long toast message.<br/>
|
||||
* Uses resource id as message.
|
||||
* @param ctx context
|
||||
* @param resId resource id
|
||||
*/
|
||||
public static void showLongMessage(Context ctx, int resId) {
|
||||
showLongMessage(ctx, ctx.getResources().getString(resId));
|
||||
}
|
||||
|
||||
public static void showLongMessage(Context ctx, String msg) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
showMessage(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showLongMessage(Context ctx, String TAG, String msg) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
showMessage(ctx, TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows toast message.<br/>
|
||||
* Uses resource id as message.
|
||||
* @param ctx context
|
||||
* @param resId resource id
|
||||
*/
|
||||
public static void showMessage(Context ctx, int resId) {
|
||||
showMessage(ctx, ctx.getResources().getString(resId));
|
||||
}
|
||||
|
||||
public static void showLongMessageEndPause(Context context, int resId) {
|
||||
showLongMessage(context, resId);
|
||||
|
||||
try {
|
||||
Thread.sleep(5_000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.liskovsoft.leankeyboard.helpers;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build.VERSION;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
@TargetApi(16)
|
||||
public class PermissionHelpers {
|
||||
// Storage Permissions
|
||||
public static final int REQUEST_EXTERNAL_STORAGE = 112;
|
||||
private static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
// Mic Permissions
|
||||
public static final int REQUEST_MIC = 113;
|
||||
private static String[] PERMISSIONS_MIC = {
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the app has permission to write to device storage<br/>
|
||||
* If the app does not has permission then the user will be prompted to grant permissions<br/>
|
||||
* Required for the {@link Context#getExternalCacheDir()}<br/>
|
||||
* NOTE: runs async<br/>
|
||||
*
|
||||
* @param activity to apply permissions to
|
||||
*/
|
||||
public static void verifyStoragePermissions(Context activity) {
|
||||
requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static void verifyMicPermissions(Context activity) {
|
||||
requestPermissions(activity, PERMISSIONS_MIC, REQUEST_MIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only check. There is no prompt.
|
||||
* @param activity to apply permissions to
|
||||
* @return whether permission already granted
|
||||
*/
|
||||
public static boolean hasStoragePermissions(Context activity) {
|
||||
// Check if we have write permission
|
||||
return hasPermissions(activity, PERMISSIONS_STORAGE);
|
||||
}
|
||||
|
||||
public static boolean hasMicPermissions(Context activity) {
|
||||
// Check if we have mic permission
|
||||
return hasPermissions(activity, PERMISSIONS_MIC);
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
/**
|
||||
* Shows permissions dialog<br/>
|
||||
* NOTE: runs async
|
||||
*/
|
||||
private static void requestPermissions(Context activity, String[] permissions, int requestId) {
|
||||
if (!hasPermissions(activity, permissions) && !Helpers.isGenymotion()) {
|
||||
if (activity instanceof Activity) {
|
||||
// We don't have permission so prompt the user
|
||||
ActivityCompat.requestPermissions(
|
||||
(Activity) activity,
|
||||
permissions,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only check. There is no prompt.
|
||||
* @param activity to apply permissions to
|
||||
* @return whether permission already granted
|
||||
*/
|
||||
private static boolean hasPermissions(Context activity, String... permissions) {
|
||||
if (VERSION.SDK_INT >= 23) {
|
||||
for (String permission : permissions) {
|
||||
int result = ActivityCompat.checkSelfPermission(activity, permission);
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.util.EventLog;
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import com.liskovsoft.leankeykeyboard.BuildConfig;
|
||||
|
||||
public class KeyMapperImeService extends InputMethodService {
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_DOWN_UP";
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_DOWN";
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_UP";
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_ACTION_TEXT = BuildConfig.APPLICATION_ID + ".inputmethod.ACTION_INPUT_TEXT";
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_EXTRA_TEXT = BuildConfig.APPLICATION_ID + ".inputmethod.EXTRA_TEXT";
|
||||
private static final String KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT = BuildConfig.APPLICATION_ID + ".inputmethod.EXTRA_KEY_EVENT";
|
||||
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
InputConnection currentInputConnection = getCurrentInputConnection();
|
||||
|
||||
if (currentInputConnection == null || action == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyEvent downEvent;
|
||||
KeyEvent upEvent;
|
||||
|
||||
switch (action) {
|
||||
case KEY_MAPPER_INPUT_METHOD_ACTION_TEXT:
|
||||
String text = intent.getStringExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_TEXT);
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentInputConnection.commitText(text, 1);
|
||||
break;
|
||||
case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP:
|
||||
downEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT);
|
||||
if (downEvent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentInputConnection.sendKeyEvent(downEvent);
|
||||
|
||||
upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP);
|
||||
currentInputConnection.sendKeyEvent(upEvent);
|
||||
break;
|
||||
case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN:
|
||||
downEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT);
|
||||
if (downEvent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
downEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_DOWN);
|
||||
|
||||
currentInputConnection.sendKeyEvent(downEvent);
|
||||
break;
|
||||
case KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP:
|
||||
upEvent = intent.getParcelableExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT);
|
||||
if (upEvent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
upEvent = KeyEvent.changeAction(upEvent, KeyEvent.ACTION_UP);
|
||||
|
||||
currentInputConnection.sendKeyEvent(upEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("UnspecifiedRegisterReceiverFlag")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN);
|
||||
intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_DOWN_UP);
|
||||
intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_INPUT_UP);
|
||||
intentFilter.addAction(KEY_MAPPER_INPUT_METHOD_ACTION_TEXT);
|
||||
|
||||
if (VERSION.SDK_INT < 33) {
|
||||
registerReceiver(mBroadcastReceiver, intentFilter);
|
||||
} else {
|
||||
registerReceiver(mBroadcastReceiver, intentFilter, RECEIVER_EXPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
unregisterReceiver(mBroadcastReceiver);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.google.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
@@ -14,14 +16,12 @@ 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.LeanbackKeyboardController.InputListener;
|
||||
import com.google.android.leanback.ime.LeanbackKeyboardView;
|
||||
import com.google.android.leanback.ime.LeanbackSuggestionsFactory;
|
||||
import com.google.android.leanback.ime.LeanbackUtils;
|
||||
import com.liskovsoft.utils.LangUpdater;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardController.InputListener;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
|
||||
public class LeanbackImeService extends InputMethodService {
|
||||
public class LeanbackImeService extends KeyMapperImeService {
|
||||
private static final String TAG = LeanbackImeService.class.getSimpleName();
|
||||
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";
|
||||
@@ -30,42 +30,63 @@ public class LeanbackImeService extends InputMethodService {
|
||||
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 msg) {
|
||||
if (msg.what == MSG_SUGGESTIONS_CLEAR && 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() {
|
||||
@Override
|
||||
public void onEntry(int type, int keyCode, CharSequence text) {
|
||||
LeanbackImeService.this.handleTextEntry(type, keyCode, text);
|
||||
}
|
||||
};
|
||||
private View mInputView;
|
||||
private LeanbackKeyboardController mKeyboardController;
|
||||
private boolean mShouldClearSuggestions = true;
|
||||
private LeanbackSuggestionsFactory mSuggestionsFactory;
|
||||
public static final String COMMAND_RESTART = "restart";
|
||||
private boolean mForceShowKbd;
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
private final Handler mHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == MSG_SUGGESTIONS_CLEAR && mShouldClearSuggestions) {
|
||||
mSuggestionsFactory.clearSuggestions();
|
||||
mKeyboardController.updateSuggestions(mSuggestionsFactory.getSuggestions());
|
||||
mShouldClearSuggestions = false;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private InputListener mInputListener = this::handleTextEntry;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressWarnings("deprecation")
|
||||
public LeanbackImeService() {
|
||||
if (!enableHardwareAcceleration()) {
|
||||
if (VERSION.SDK_INT < 21 && !enableHardwareAcceleration()) {
|
||||
Log.w("LbImeService", "Could not enable hardware acceleration");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
//setupDensity();
|
||||
super.onCreate();
|
||||
|
||||
LangUpdater langUpdater = new LangUpdater(this);
|
||||
langUpdater.update();
|
||||
|
||||
Log.d(TAG, "onCreate");
|
||||
|
||||
initSettings();
|
||||
}
|
||||
|
||||
private void setupDensity() {
|
||||
if (LeanKeyPreferences.instance(this).getEnlargeKeyboard()) {
|
||||
DisplayMetrics metrics = LeanbackUtils.createMetricsFrom(this, 1.3f);
|
||||
|
||||
if (metrics != null) {
|
||||
getResources().getDisplayMetrics().setTo(metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initSettings() {
|
||||
LeanKeyPreferences prefs = LeanKeyPreferences.instance(this);
|
||||
mForceShowKbd = prefs.getForceShowKeyboard();
|
||||
|
||||
if (mKeyboardController != null) {
|
||||
mKeyboardController.setSuggestionsEnabled(prefs.getSuggestionsEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSuggestionsDelayed() {
|
||||
@@ -77,51 +98,6 @@ public class LeanbackImeService extends InputMethodService {
|
||||
|
||||
}
|
||||
|
||||
private int getAmpersandLocation(InputConnection connection) {
|
||||
String text = getEditorText(connection);
|
||||
int pos = text.indexOf(64);
|
||||
if (pos < 0) { // not found
|
||||
pos = text.length();
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
private int getCharLengthAfterCursor(InputConnection connection) {
|
||||
int len = 0;
|
||||
CharSequence after = connection.getTextAfterCursor(1000, 0);
|
||||
if (after != null) {
|
||||
len = after.length();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private int getCharLengthBeforeCursor(InputConnection connection) {
|
||||
int len = 0;
|
||||
CharSequence before = connection.getTextBeforeCursor(1000, 0);
|
||||
if (before != null) {
|
||||
len = before.length();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private String getEditorText(InputConnection connection) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
CharSequence before = connection.getTextBeforeCursor(1000, 0);
|
||||
CharSequence after = connection.getTextAfterCursor(1000, 0);
|
||||
if (before != null) {
|
||||
result.append(before);
|
||||
}
|
||||
|
||||
if (after != null) {
|
||||
result.append(after);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void handleTextEntry(final int type, final int keyCode, final CharSequence text) {
|
||||
final InputConnection connection = getCurrentInputConnection();
|
||||
if (connection != null) {
|
||||
@@ -153,50 +129,89 @@ public class LeanbackImeService extends InputMethodService {
|
||||
case InputListener.ENTRY_TYPE_VOICE:
|
||||
clearSuggestionsDelayed();
|
||||
if (!mSuggestionsFactory.shouldSuggestionsAmend()) {
|
||||
connection.deleteSurroundingText(this.getCharLengthBeforeCursor(connection), this.getCharLengthAfterCursor(connection));
|
||||
connection.deleteSurroundingText(LeanbackUtils.getCharLengthBeforeCursor(connection), LeanbackUtils.getCharLengthAfterCursor(connection));
|
||||
} else {
|
||||
int location = this.getAmpersandLocation(connection);
|
||||
int location = LeanbackUtils.getAmpersandLocation(connection);
|
||||
connection.setSelection(location, location);
|
||||
connection.deleteSurroundingText(0, this.getCharLengthAfterCursor(connection));
|
||||
connection.deleteSurroundingText(0, LeanbackUtils.getCharLengthAfterCursor(connection));
|
||||
}
|
||||
|
||||
connection.commitText(text, 1);
|
||||
mEnterSpaceBeforeCommitting = true;
|
||||
case InputListener.ENTRY_TYPE_ACTION: // NOTE: user presses Go, Send, Search etc
|
||||
case InputListener.ENTRY_TYPE_ACTION: // User presses Go, Send, Search etc
|
||||
boolean result = sendDefaultEditorAction(true);
|
||||
|
||||
if (result) {
|
||||
hideWindow(); // NOTE: SmartYouTubeTV hide kbd on search page fix
|
||||
hideWindow(); // SmartYouTubeTV: hide kbd on search page fix
|
||||
} else {
|
||||
sendEnterKey(connection);
|
||||
LeanbackUtils.sendEnterKey(connection);
|
||||
}
|
||||
|
||||
updateSuggestions = false;
|
||||
break;
|
||||
case InputListener.ENTRY_TYPE_LEFT:
|
||||
case InputListener.ENTRY_TYPE_RIGHT:
|
||||
BidiFormatter formatter = BidiFormatter.getInstance();
|
||||
|
||||
CharSequence textBeforeCursor = connection.getTextBeforeCursor(1000, 0);
|
||||
int len;
|
||||
if (textBeforeCursor == null) {
|
||||
len = 0;
|
||||
} else {
|
||||
len = textBeforeCursor.length();
|
||||
int lenBefore = 0;
|
||||
boolean isRtlBefore = false;
|
||||
//int rtlLenBefore = 0;
|
||||
if (textBeforeCursor != null) {
|
||||
lenBefore = textBeforeCursor.length();
|
||||
isRtlBefore = formatter.isRtl(textBeforeCursor);
|
||||
//rtlLenBefore = LeanbackUtils.getRtlLenBeforeCursor(textBeforeCursor);
|
||||
}
|
||||
|
||||
int index;
|
||||
if (type == InputListener.ENTRY_TYPE_LEFT) {
|
||||
index = len;
|
||||
if (len > 0) {
|
||||
index = len - 1;
|
||||
}
|
||||
} else {
|
||||
textBeforeCursor = connection.getTextAfterCursor(1000, 0);
|
||||
index = len;
|
||||
if (textBeforeCursor != null && textBeforeCursor.length() > 0) {
|
||||
index = len + 1;
|
||||
}
|
||||
CharSequence textAfterCursor = connection.getTextAfterCursor(1000, 0);
|
||||
int lenAfter = 0;
|
||||
//int rtlLenAfter = 0;
|
||||
boolean isRtlAfter = false;
|
||||
if (textAfterCursor != null) {
|
||||
lenAfter = textAfterCursor.length();
|
||||
isRtlAfter = formatter.isRtl(textAfterCursor);
|
||||
//rtlLenAfter = LeanbackUtils.getRtlLenAfterCursor(textAfterCursor);
|
||||
}
|
||||
|
||||
int index = lenBefore;
|
||||
if (type == InputListener.ENTRY_TYPE_LEFT) {
|
||||
if (lenBefore > 0) {
|
||||
if (!isRtlBefore) {
|
||||
index = lenBefore - 1;
|
||||
} else {
|
||||
if (lenAfter == 0) {
|
||||
index = 1;
|
||||
} else if (lenAfter == 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index = lenBefore + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Log.d(TAG, String.format("direction key: before: lenBefore=%s, lenAfter=%s, rtlLenBefore=%s, rtlLenAfter=%s", lenBefore, lenAfter, rtlLenBefore, rtlLenAfter));
|
||||
Log.d(TAG, String.format("direction key: before: lenBefore=%s, lenAfter=%s, isRtlBefore=%s", lenBefore, lenAfter, isRtlBefore));
|
||||
} else {
|
||||
if (lenAfter > 0) {
|
||||
if (!isRtlAfter) {
|
||||
index = lenBefore + 1;
|
||||
} else {
|
||||
if (lenBefore == 0) {
|
||||
index = lenAfter - 1;
|
||||
} else if (lenBefore == 1) {
|
||||
index = lenAfter + 1;
|
||||
} else {
|
||||
index = lenBefore - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Log.d(TAG, String.format("direction key: after: lenBefore=%s, lenAfter=%s, rtlLenBefore=%s, rtlLenAfter=%s", lenBefore, lenAfter, rtlLenBefore, rtlLenAfter));
|
||||
Log.d(TAG, String.format("direction key: after: lenBefore=%s, lenAfter=%s, isRtlAfter=%s", lenBefore, lenAfter, isRtlAfter));
|
||||
}
|
||||
|
||||
Log.d(TAG, "direction key: index: " + index);
|
||||
|
||||
connection.setSelection(index, index);
|
||||
updateSuggestions = true;
|
||||
break;
|
||||
@@ -218,14 +233,11 @@ public class LeanbackImeService extends InputMethodService {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEnterKey(InputConnection connection) {
|
||||
connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateInputView() {
|
||||
mInputView = mKeyboardController.getView();
|
||||
mInputView.requestFocus();
|
||||
|
||||
return mInputView;
|
||||
}
|
||||
|
||||
@@ -242,24 +254,35 @@ public class LeanbackImeService extends InputMethodService {
|
||||
|
||||
@Override
|
||||
public boolean onEvaluateFullscreenMode() {
|
||||
return false;
|
||||
return false; // don't change it (true shows edit dialog above kbd)
|
||||
}
|
||||
|
||||
/**
|
||||
* At this point, decision whether to show kbd taking place
|
||||
* At this point, decision whether to show kbd taking place<br/>
|
||||
* <a href="https://stackoverflow.com/questions/7449283/is-it-possible-to-have-both-physical-keyboard-and-soft-keyboard-active-at-the-sa">More info</a>
|
||||
* @return whether to show kbd
|
||||
*/
|
||||
@SuppressLint("MissingSuperCall")
|
||||
@Override
|
||||
public boolean onEvaluateInputViewShown() {
|
||||
return mKeyboardController.showInputView();
|
||||
Log.d(TAG, "onEvaluateInputViewShown");
|
||||
return mForceShowKbd || super.onEvaluateInputViewShown();
|
||||
}
|
||||
|
||||
// FireTV fix
|
||||
@Override
|
||||
public boolean onShowInputRequested(int flags, boolean configChange) {
|
||||
Log.d(TAG, "onShowInputRequested");
|
||||
return mForceShowKbd || super.onShowInputRequested(flags, configChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInputView(boolean finishingInput) {
|
||||
super.onFinishInputView(finishingInput);
|
||||
this.sendBroadcast(new Intent(IME_CLOSE));
|
||||
this.mSuggestionsFactory.clearSuggestions();
|
||||
sendBroadcast(new Intent(IME_CLOSE));
|
||||
mSuggestionsFactory.clearSuggestions();
|
||||
|
||||
// NOTE: Trying to fix kbd without UI bug (telegram)
|
||||
reInitKeyboard();
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@@ -270,21 +293,21 @@ public class LeanbackImeService extends InputMethodService {
|
||||
mKeyboardController.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
public void onHideIme() {
|
||||
public void hideIme() {
|
||||
requestHideSelf(InputMethodService.BACK_DISPOSITION_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeInterface() {
|
||||
mKeyboardController = new LeanbackKeyboardController(this, mInputListener);
|
||||
mKeyboardController.setHideWhenPhysicalKeyboardUsed(!mForceShowKbd);
|
||||
mEnterSpaceBeforeCommitting = false;
|
||||
mSuggestionsFactory = new LeanbackSuggestionsFactory(this, MAX_SUGGESTIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// NOTE: hide keyboard on ESC key
|
||||
// https://github.com/yuliskov/SmartYouTubeTV/issues/142
|
||||
// Hide keyboard on ESC key: https://github.com/yuliskov/SmartYouTubeTV/issues/142
|
||||
event = mapEscToBack(event);
|
||||
keyCode = mapEscToBack(keyCode);
|
||||
|
||||
@@ -293,8 +316,7 @@ public class LeanbackImeService extends InputMethodService {
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// NOTE: hide keyboard on ESC key
|
||||
// https://github.com/yuliskov/SmartYouTubeTV/issues/142
|
||||
// Hide keyboard on ESC key: https://github.com/yuliskov/SmartYouTubeTV/issues/142
|
||||
event = mapEscToBack(event);
|
||||
keyCode = mapEscToBack(keyCode);
|
||||
|
||||
@@ -316,26 +338,21 @@ public class LeanbackImeService extends InputMethodService {
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
// FireTV fix
|
||||
//@Override
|
||||
//public boolean onShowInputRequested(int flags, boolean configChange) {
|
||||
// return true;
|
||||
//}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
if (intent != null) {
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
if (intent.getBooleanExtra("restart", false)) {
|
||||
Log.e("LeanbackImeService", "Service->onStartCommand: trying to restart service");
|
||||
LeanbackKeyboardController controller = mKeyboardController;
|
||||
if (controller != null) {
|
||||
controller.updateAddonKeyboard();
|
||||
}
|
||||
Log.d(TAG, "onStartCommand: " + intent.toUri(0));
|
||||
|
||||
if (intent.getBooleanExtra(COMMAND_RESTART, false)) {
|
||||
Log.d(TAG, "onStartCommand: trying to restart service");
|
||||
|
||||
reInitKeyboard();
|
||||
|
||||
return Service.START_REDELIVER_INTENT;
|
||||
}
|
||||
}
|
||||
|
||||
return Service.START_STICKY;
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -350,26 +367,28 @@ public class LeanbackImeService extends InputMethodService {
|
||||
public void onStartInputView(EditorInfo info, boolean restarting) {
|
||||
super.onStartInputView(info, restarting);
|
||||
|
||||
// FireTV: fix accidental kbd pop-ups
|
||||
// more info: https://forum.xda-developers.com/fire-tv/general/guide-change-screen-keyboard-to-leankey-t3527675/page2
|
||||
updateInputViewShown();
|
||||
if (!mKeyboardController.showInputView()) {
|
||||
onHideIme();
|
||||
return;
|
||||
}
|
||||
|
||||
mKeyboardController.onStartInputView();
|
||||
sendBroadcast(new Intent(IME_OPEN));
|
||||
if (mKeyboardController.areSuggestionsEnabled()) {
|
||||
mSuggestionsFactory.createSuggestions();
|
||||
mKeyboardController.updateSuggestions(mSuggestionsFactory.getSuggestions());
|
||||
InputConnection connection = getCurrentInputConnection();
|
||||
if (connection != null) {
|
||||
String text = getEditorText(connection);
|
||||
connection.deleteSurroundingText(getCharLengthBeforeCursor(connection), getCharLengthAfterCursor(connection));
|
||||
connection.commitText(text, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: FileManager+ rename item fix: https://t.me/LeanKeyboard/931
|
||||
// NOTE: Code below deletes text that has selection.
|
||||
//InputConnection connection = getCurrentInputConnection();
|
||||
//if (connection != null) {
|
||||
// String text = LeanbackUtils.getEditorText(connection);
|
||||
// connection.deleteSurroundingText(LeanbackUtils.getCharLengthBeforeCursor(connection), LeanbackUtils.getCharLengthAfterCursor(connection));
|
||||
// connection.commitText(text, 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
private void reInitKeyboard() {
|
||||
initSettings();
|
||||
|
||||
if (mKeyboardController != null) {
|
||||
mKeyboardController.initKeyboards();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.google.android.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.inputmethodservice.Keyboard.Key;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
@@ -16,8 +15,9 @@ import android.view.View.OnTouchListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.google.android.leanback.ime.LeanbackKeyboardContainer.KeyFocus;
|
||||
import com.google.android.pano.util.TouchNavSpaceTracker;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer.KeyFocus;
|
||||
import com.liskovsoft.leankeyboard.ime.pano.util.TouchNavSpaceTracker;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -31,15 +31,15 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
private static final long KEY_CHANGE_REVERT_TIME_MS = 100L;
|
||||
private static final String TAG = "LbKbController";
|
||||
public static final String TAG_GO = "Go";
|
||||
private boolean clickConsumed;
|
||||
private long lastClickTime;
|
||||
private boolean mClickConsumed;
|
||||
private long mLastClickTime;
|
||||
private LeanbackKeyboardContainer mContainer;
|
||||
private InputMethodService mContext;
|
||||
private LeanbackKeyboardController.DoubleClickDetector mDoubleClickDetector;
|
||||
private LeanbackKeyboardContainer.KeyFocus mDownFocus;
|
||||
private DoubleClickDetector mDoubleClickDetector;
|
||||
private LeanbackKeyboardContainer.KeyFocus mCurrentFocus;
|
||||
private Handler mHandler;
|
||||
private LeanbackKeyboardController.InputListener mInputListener;
|
||||
ArrayList<LeanbackKeyboardController.KeyChange> mKeyChangeHistory;
|
||||
private InputListener mInputListener;
|
||||
ArrayList<KeyChange> mKeyChangeHistory;
|
||||
private LeanbackKeyboardContainer.KeyFocus mKeyDownKeyFocus;
|
||||
private boolean mKeyDownReceived;
|
||||
private boolean mLongPressHandled;
|
||||
@@ -55,33 +55,33 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
private int mLastEditorIdPhysicalKeyboardWasUsed;
|
||||
private boolean mHideKeyboardWhenPhysicalKeyboardUsed = true;
|
||||
|
||||
public LeanbackKeyboardController(final InputMethodService context, final LeanbackKeyboardController.InputListener listener) {
|
||||
public LeanbackKeyboardController(final InputMethodService context,
|
||||
final InputListener listener) {
|
||||
this(context, listener, new TouchNavSpaceTracker(), new LeanbackKeyboardContainer(context));
|
||||
}
|
||||
|
||||
public LeanbackKeyboardController(final InputMethodService context, final LeanbackKeyboardController.InputListener listener, final TouchNavSpaceTracker tracker,
|
||||
final LeanbackKeyboardContainer container) {
|
||||
mDoubleClickDetector = new LeanbackKeyboardController.DoubleClickDetector();
|
||||
mOnLayoutChangeListener = new OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
left = right - left;
|
||||
top = bottom - top;
|
||||
if (left > 0 && top > 0) {
|
||||
if (LeanbackKeyboardController.this.mSpaceTracker != null) {
|
||||
LeanbackKeyboardController.this.mSpaceTracker.setPixelSize((float) left, (float) top);
|
||||
}
|
||||
|
||||
if (left != oldRight - oldLeft || top != oldBottom - oldTop) {
|
||||
LeanbackKeyboardController.this.initInputView();
|
||||
}
|
||||
public LeanbackKeyboardController(final InputMethodService context,
|
||||
final InputListener listener,
|
||||
final TouchNavSpaceTracker tracker,
|
||||
final LeanbackKeyboardContainer container) {
|
||||
mDoubleClickDetector = new DoubleClickDetector();
|
||||
mOnLayoutChangeListener = (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
left = right - left;
|
||||
top = bottom - top;
|
||||
if (left > 0 && top > 0) {
|
||||
if (mSpaceTracker != null) {
|
||||
mSpaceTracker.setPixelSize((float) left, (float) top);
|
||||
}
|
||||
|
||||
if (left != oldRight - oldLeft || top != oldBottom - oldTop) {
|
||||
initInputView();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
mTouchEventListener = new LeanbackKeyboardController.TouchEventListener();
|
||||
mDownFocus = new LeanbackKeyboardContainer.KeyFocus();
|
||||
mTempFocus = new LeanbackKeyboardContainer.KeyFocus();
|
||||
mTouchEventListener = new TouchEventListener();
|
||||
mCurrentFocus = new KeyFocus();
|
||||
mTempFocus = new KeyFocus();
|
||||
mKeyChangeHistory = new ArrayList<>(11);
|
||||
mTempPoint = new PointF();
|
||||
mKeyDownReceived = false;
|
||||
@@ -111,16 +111,16 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
private boolean applyLETVFixesUp(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
this.mContainer.onLangKeyPress();
|
||||
mContainer.switchToNextKeyboard();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
this.fakeKeyIndex(0, 2);
|
||||
fakeKeyIndex(0, KeyFocus.TYPE_ACTION);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_REWIND:
|
||||
this.fakeKeyCode(-5);
|
||||
fakeKeyCode(LeanbackKeyboardView.KEYCODE_DELETE);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
|
||||
this.fakeKeyCode(32);
|
||||
fakeKeyCode(LeanbackKeyboardView.ASCII_SPACE);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -130,7 +130,7 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private void beginLongClickCountdown() {
|
||||
this.clickConsumed = false;
|
||||
this.mClickConsumed = false;
|
||||
Handler handler = this.mHandler;
|
||||
if (handler == null) {
|
||||
handler = new Handler();
|
||||
@@ -142,10 +142,10 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private void clearKeyIfNecessary() {
|
||||
++this.mMoveCount;
|
||||
if (this.mMoveCount >= 3) {
|
||||
this.mMoveCount = 0;
|
||||
this.mKeyDownKeyFocus = null;
|
||||
++mMoveCount;
|
||||
if (mMoveCount >= 3) {
|
||||
mMoveCount = 0;
|
||||
mKeyDownKeyFocus = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -155,16 +155,16 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: where all magic happens. Input from virtual kbd is processed here.
|
||||
* NOTE: Where all magic happens. Input from virtual kbd is processed here.
|
||||
* @param focus current key
|
||||
*/
|
||||
private void commitKey(LeanbackKeyboardContainer.KeyFocus focus) {
|
||||
private void commitKey(KeyFocus focus) {
|
||||
if (mContainer != null && focus != null) {
|
||||
switch (focus.type) {
|
||||
case KeyFocus.TYPE_VOICE:
|
||||
mContainer.onVoiceClick();
|
||||
return;
|
||||
case KeyFocus.TYPE_ACTION: // NOTE: user presses Go, Send, Search etc
|
||||
case KeyFocus.TYPE_ACTION: // User presses Go, Send, Search etc
|
||||
mInputListener.onEntry(InputListener.ENTRY_TYPE_ACTION, 0, null);
|
||||
// mContext.hideWindow(); // SmartYouTubeTV fix: force hide keyboard
|
||||
return;
|
||||
@@ -178,7 +178,6 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void fakeClickDown() {
|
||||
@@ -232,7 +231,7 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
LeanbackKeyboardController.KeyChange change = mKeyChangeHistory.get(count);
|
||||
if (currTime - mKeyChangeHistory.get(count + 1).time < 100L) {
|
||||
if (currTime - mKeyChangeHistory.get(count + 1).time < KEY_CHANGE_REVERT_TIME_MS) {
|
||||
pos = change.position;
|
||||
mKeyChangeHistory.clear();
|
||||
mKeyChangeHistory.add(new LeanbackKeyboardController.KeyChange(currTime, pos));
|
||||
@@ -287,7 +286,7 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keyboard key
|
||||
* NOTE: Specials keys (e.g. voice key) handled here
|
||||
* @param keyCode key code e.g {@link LeanbackKeyboardView#KEYCODE_SHIFT LeanbackKeyboardView.KEYCODE_SHIFT}
|
||||
* @param text typed content
|
||||
*/
|
||||
@@ -340,6 +339,13 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
|
||||
mContainer.onLangKeyClick();
|
||||
return;
|
||||
case LeanbackKeyboardView.KEYCODE_CLIPBOARD:
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "paste from clipboard");
|
||||
}
|
||||
|
||||
mContainer.onClipboardClick(mInputListener);
|
||||
return;
|
||||
default:
|
||||
mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, keyCode, text);
|
||||
mContainer.onTextEntry();
|
||||
@@ -424,16 +430,10 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private boolean handleKeyLongPress(int keyCode) {
|
||||
boolean isHandled;
|
||||
if (isEnterKey(keyCode) && mContainer.onKeyLongPress()) {
|
||||
isHandled = true;
|
||||
} else {
|
||||
isHandled = false;
|
||||
}
|
||||
mLongPressHandled = isEnterKey(keyCode) && mContainer.onKeyLongPress();
|
||||
|
||||
mLongPressHandled = isHandled;
|
||||
if (mContainer.isMiniKeyboardOnScreen()) {
|
||||
Log.d("LbKbController", "mini keyboard shown after long press");
|
||||
Log.d(TAG, "mini keyboard shown after long press");
|
||||
}
|
||||
|
||||
return mLongPressHandled;
|
||||
@@ -482,6 +482,11 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_CAPS_LOCK, null);
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_VOICE_ASSIST:
|
||||
case KeyEvent.KEYCODE_SEARCH:
|
||||
mContainer.startVoiceRecording();
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
@@ -539,17 +544,17 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
|
||||
private boolean isDoubleClick() {
|
||||
long currTimeMS = System.currentTimeMillis();
|
||||
long lastTime = this.lastClickTime;
|
||||
if (this.lastClickTime != 0L && currTimeMS - lastTime <= (long) 300) {
|
||||
long lastTime = mLastClickTime;
|
||||
if (mLastClickTime != 0L && currTimeMS - lastTime <= (long) 300) {
|
||||
return true;
|
||||
} else {
|
||||
this.lastClickTime = currTimeMS;
|
||||
mLastClickTime = currTimeMS;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnterKey(int keyCode) {
|
||||
keyCode = this.getSimplifiedKey(keyCode);
|
||||
keyCode = getSimplifiedKey(keyCode);
|
||||
return keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER;
|
||||
}
|
||||
|
||||
@@ -570,9 +575,10 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private boolean onDirectionalMove(int dir) {
|
||||
if (mContainer.getNextFocusInDirection(dir, mDownFocus, mTempFocus)) {
|
||||
if (mContainer.getNextFocusInDirection(dir, mCurrentFocus, mTempFocus)) {
|
||||
mContainer.updateCyclicFocus(dir, mCurrentFocus, mTempFocus);
|
||||
mContainer.setFocus(mTempFocus);
|
||||
mDownFocus.set(mTempFocus);
|
||||
mCurrentFocus.set(mTempFocus);
|
||||
clearKeyIfNecessary();
|
||||
}
|
||||
|
||||
@@ -580,13 +586,13 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private void performBestSnap(long time) {
|
||||
LeanbackKeyboardContainer.KeyFocus focus = this.mContainer.getCurrFocus();
|
||||
this.mTempPoint.x = (float) focus.rect.centerX();
|
||||
this.mTempPoint.y = (float) focus.rect.centerY();
|
||||
PointF pos = this.getBestSnapPosition(this.mTempPoint, time);
|
||||
this.mContainer.getBestFocus(pos.x, pos.y, this.mTempFocus);
|
||||
this.mContainer.setFocus(this.mTempFocus);
|
||||
this.updatePositionToCurrentFocus();
|
||||
LeanbackKeyboardContainer.KeyFocus focus = mContainer.getCurrFocus();
|
||||
mTempPoint.x = (float) focus.rect.centerX();
|
||||
mTempPoint.y = (float) focus.rect.centerY();
|
||||
PointF pos = getBestSnapPosition(mTempPoint, time);
|
||||
mContainer.getBestFocus(pos.x, pos.y, mTempFocus);
|
||||
mContainer.setFocus(mTempFocus);
|
||||
updatePositionToCurrentFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,19 +616,25 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
private void updatePositionToCurrentFocus() {
|
||||
PointF pos = this.getCurrentKeyPosition();
|
||||
if (pos != null && this.mSpaceTracker != null) {
|
||||
this.mSpaceTracker.setPixelPosition(pos.x, pos.y);
|
||||
PointF pos = getCurrentKeyPosition();
|
||||
if (pos != null && mSpaceTracker != null) {
|
||||
mSpaceTracker.setPixelPosition(pos.x, pos.y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean areSuggestionsEnabled() {
|
||||
return mContainer != null ? mContainer.areSuggestionsEnabled() : false;
|
||||
return mContainer != null && mContainer.areSuggestionsEnabled();
|
||||
}
|
||||
|
||||
public void setSuggestionsEnabled(boolean enabled) {
|
||||
if (mContainer != null) {
|
||||
mContainer.setSuggestionsEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean enableAutoEnterSpace() {
|
||||
return mContainer != null ? mContainer.enableAutoEnterSpace() : false;
|
||||
return mContainer != null && mContainer.enableAutoEnterSpace();
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
@@ -683,7 +695,7 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
//if (event.getDeviceId() > 0 && event.isPrintingKey()) onPhysicalKeyboardKeyPressed();
|
||||
if (event.isPrintingKey()) onPhysicalKeyboardKeyPressed();
|
||||
|
||||
mDownFocus.set(mContainer.getCurrFocus());
|
||||
mCurrentFocus.set(mContainer.getCurrFocus());
|
||||
if (mSpaceTracker != null && mSpaceTracker.onKeyDown(keyCode, event)) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -788,8 +800,8 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
break;
|
||||
}
|
||||
|
||||
if (!clickConsumed) {
|
||||
clickConsumed = true;
|
||||
if (!mClickConsumed) {
|
||||
mClickConsumed = true;
|
||||
if (isDoubleClick()) {
|
||||
mContainer.onKeyLongPress();
|
||||
break;
|
||||
@@ -814,8 +826,8 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!clickConsumed) {
|
||||
clickConsumed = true;
|
||||
if (!mClickConsumed) {
|
||||
mClickConsumed = true;
|
||||
fakeLongClickDown();
|
||||
}
|
||||
}
|
||||
@@ -831,8 +843,8 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
tracker.setKeyEventListener(mTouchEventListener);
|
||||
}
|
||||
|
||||
public void updateAddonKeyboard() {
|
||||
mContainer.updateAddonKeyboard();
|
||||
public void initKeyboards() {
|
||||
mContainer.initKeyboards();
|
||||
}
|
||||
|
||||
public void updateSuggestions(ArrayList<String> suggestions) {
|
||||
@@ -842,6 +854,10 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
|
||||
}
|
||||
|
||||
public void setHideWhenPhysicalKeyboardUsed(boolean hide) {
|
||||
mHideKeyboardWhenPhysicalKeyboardUsed = hide;
|
||||
}
|
||||
|
||||
private class DoubleClickDetector {
|
||||
final long DOUBLE_CLICK_TIMEOUT_MS;
|
||||
boolean mFirstClickShiftLocked;
|
||||
@@ -855,10 +871,10 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
public void addEvent(long currTime) {
|
||||
if (currTime - mFirstClickTime > DOUBLE_CLICK_TIMEOUT_MS) {
|
||||
mFirstClickTime = currTime;
|
||||
mFirstClickShiftLocked = LeanbackKeyboardController.this.mContainer.isCapsLockOn();
|
||||
LeanbackKeyboardController.this.commitKey();
|
||||
mFirstClickShiftLocked = mContainer.isCapsLockOn();
|
||||
commitKey();
|
||||
} else {
|
||||
LeanbackKeyboardController.this.mContainer.onShiftDoubleClick(mFirstClickShiftLocked);
|
||||
mContainer.onShiftDoubleClick(mFirstClickShiftLocked);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
@@ -903,37 +919,37 @@ public class LeanbackKeyboardController implements LeanbackKeyboardContainer.Voi
|
||||
}
|
||||
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (LeanbackKeyboardController.this.isEnterKey(keyCode)) {
|
||||
LeanbackKeyboardController.this.mKeyDownReceived = true;
|
||||
if (isEnterKey(keyCode)) {
|
||||
mKeyDownReceived = true;
|
||||
if (event.getRepeatCount() == 0) {
|
||||
LeanbackKeyboardController.this.mContainer.setTouchState(3);
|
||||
LeanbackKeyboardController.this.mSpaceTracker.blockMovementUntil(event.getEventTime() + CLICK_MOVEMENT_BLOCK_DURATION_MS);
|
||||
LeanbackKeyboardController.this.performBestSnap(event.getEventTime());
|
||||
mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK);
|
||||
mSpaceTracker.blockMovementUntil(event.getEventTime() + CLICK_MOVEMENT_BLOCK_DURATION_MS);
|
||||
performBestSnap(event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
return LeanbackKeyboardController.this.handleKeyDownEvent(keyCode, event.getRepeatCount());
|
||||
return handleKeyDownEvent(keyCode, event.getRepeatCount());
|
||||
}
|
||||
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
return LeanbackKeyboardController.this.handleKeyLongPress(keyCode);
|
||||
return handleKeyLongPress(keyCode);
|
||||
}
|
||||
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (LeanbackKeyboardController.this.isEnterKey(keyCode)) {
|
||||
if (!LeanbackKeyboardController.this.mKeyDownReceived || LeanbackKeyboardController.this.mLongPressHandled) {
|
||||
LeanbackKeyboardController.this.mLongPressHandled = false;
|
||||
if (isEnterKey(keyCode)) {
|
||||
if (!mKeyDownReceived || mLongPressHandled) {
|
||||
mLongPressHandled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
LeanbackKeyboardController.this.mKeyDownReceived = false;
|
||||
if (LeanbackKeyboardController.this.mContainer.getTouchState() == 3) {
|
||||
LeanbackKeyboardController.this.mContainer.setTouchState(1);
|
||||
LeanbackKeyboardController.this.mSpaceTracker.unblockMovement();
|
||||
mKeyDownReceived = false;
|
||||
if (mContainer.getTouchState() == LeanbackKeyboardContainer.TOUCH_STATE_CLICK) {
|
||||
mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_TOUCH_SNAP);
|
||||
mSpaceTracker.unblockMovement();
|
||||
}
|
||||
}
|
||||
|
||||
return LeanbackKeyboardController.this.handleKeyUpEvent(keyCode, event.getEventTime());
|
||||
return handleKeyUpEvent(keyCode, event.getEventTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
@@ -11,6 +11,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.Keyboard.Key;
|
||||
import android.util.AttributeSet;
|
||||
@@ -18,20 +19,23 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.liskovsoft.leankeyboard.utils.LeanKeyPreferences;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class LeanbackKeyboardView extends FrameLayout {
|
||||
private static final String TAG = "LbKbView";
|
||||
/**
|
||||
* space key index (important: wrong value will broke navigation)
|
||||
* Space key index (important: wrong value will broke navigation)
|
||||
*/
|
||||
public static final int ASCII_PERIOD = 47;
|
||||
/**
|
||||
* keys count among which space key spans (important: wrong value will broke navigation)
|
||||
* Keys count among which space key spans (important: wrong value will broke navigation)
|
||||
*/
|
||||
public static final int ASCII_PERIOD_LEN = 6;
|
||||
public static final int ASCII_PERIOD_LEN = 5;
|
||||
public static final int ASCII_SPACE = 32;
|
||||
private static final boolean DEBUG = false;
|
||||
public static final int KEYCODE_CAPS_LOCK = -6;
|
||||
@@ -43,14 +47,15 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
public static final int KEYCODE_SYM_TOGGLE = -2;
|
||||
public static final int KEYCODE_VOICE = -7;
|
||||
public static final int KEYCODE_LANG_TOGGLE = -9;
|
||||
private static final int NOT_A_KEY = -1;
|
||||
public static final int KEYCODE_CLIPBOARD = -10;
|
||||
public 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 final float mSquareIconScaleFactor;
|
||||
private int mColCount;
|
||||
private View mCurrentFocusView;
|
||||
private boolean mFocusClicked;
|
||||
@@ -59,19 +64,20 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
private final int mInactiveMiniKbAlpha;
|
||||
private ImageView[] mKeyImageViews;
|
||||
private int mKeyTextColor;
|
||||
private int mKeyTextSize;
|
||||
private Keyboard mKeyboard;
|
||||
private LeanbackKeyboardView.KeyHolder[] mKeys;
|
||||
private KeyHolder[] mKeys;
|
||||
private boolean mMiniKeyboardOnScreen;
|
||||
private int mModeChangeTextSize;
|
||||
private Rect mPadding;
|
||||
private Paint mPaint;
|
||||
private int mRowCount;
|
||||
private int mShiftState;
|
||||
private final int mUnfocusStartDelay;
|
||||
private final KeyConverter mConverter;
|
||||
protected Paint mPaint;
|
||||
protected int mKeyTextSize;
|
||||
protected int mModeChangeTextSize;
|
||||
private Drawable mCustomCapsLockDrawable;
|
||||
|
||||
private class KeyConverter {
|
||||
private static class KeyConverter {
|
||||
private static final int LOWER_CASE = 0;
|
||||
private static final int UPPER_CASE = 1;
|
||||
|
||||
@@ -129,30 +135,25 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
mKeyTextSize = (int) res.getDimension(R.dimen.key_font_size);
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setTextSize((float) mKeyTextSize);
|
||||
mPaint.setTextSize(mKeyTextSize);
|
||||
mPaint.setTextAlign(Align.CENTER);
|
||||
mPaint.setAlpha(255);
|
||||
mPadding = new Rect(0, 0, 0, 0);
|
||||
mModeChangeTextSize = (int) res.getDimension(R.dimen.function_key_mode_change_font_size);
|
||||
mKeyTextColor = res.getColor(R.color.key_text_default);
|
||||
mKeyTextColor = ContextCompat.getColor(getContext(), R.color.key_text_default);
|
||||
mFocusIndex = -1;
|
||||
mShiftState = 0;
|
||||
mFocusedScale = res.getFraction(R.fraction.focused_scale, 1, 1);
|
||||
mClickedScale = res.getFraction(R.fraction.clicked_scale, 1, 1);
|
||||
mSquareIconScaleFactor = res.getFraction(R.fraction.square_icon_scale_factor, 1, 1);
|
||||
mClickAnimDur = res.getInteger(R.integer.clicked_anim_duration);
|
||||
mUnfocusStartDelay = res.getInteger(R.integer.unfocused_anim_delay);
|
||||
mInactiveMiniKbAlpha = res.getInteger(R.integer.inactive_mini_kb_alpha);
|
||||
mConverter = new KeyConverter();
|
||||
}
|
||||
|
||||
private void adjustCase(LeanbackKeyboardView.KeyHolder keyHolder) {
|
||||
boolean flag;
|
||||
|
||||
if (keyHolder.isInMiniKb && keyHolder.isInvertible) {
|
||||
flag = true;
|
||||
} else {
|
||||
flag = false;
|
||||
}
|
||||
private void adjustCase(KeyHolder keyHolder) {
|
||||
boolean flag = keyHolder.isInMiniKb && keyHolder.isInvertible;
|
||||
|
||||
// ^ equals to !=
|
||||
if (mKeyboard.isShifted() ^ flag) {
|
||||
@@ -162,12 +163,15 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Adds key views to root window
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private ImageView createKeyImageView(final int keyIndex) {
|
||||
Rect padding = mPadding;
|
||||
int kbdPaddingLeft = getPaddingLeft();
|
||||
int kbdPaddingTop = getPaddingTop();
|
||||
LeanbackKeyboardView.KeyHolder keyHolder = mKeys[keyIndex];
|
||||
KeyHolder keyHolder = mKeys[keyIndex];
|
||||
Key key = keyHolder.key;
|
||||
adjustCase(keyHolder);
|
||||
String label;
|
||||
@@ -177,8 +181,8 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
label = key.label.toString();
|
||||
}
|
||||
|
||||
if (Log.isLoggable("LbKbView", Log.DEBUG)) {
|
||||
Log.d("LbKbView", "LABEL: " + key.label + "->" + label);
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "LABEL: " + key.label + "->" + label);
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(key.width, key.height, Config.ARGB_8888);
|
||||
@@ -190,20 +194,43 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
if (key.codes[0] == NOT_A_KEY) {
|
||||
switch (mShiftState) {
|
||||
case SHIFT_OFF:
|
||||
key.icon = getContext().getResources().getDrawable(R.drawable.ic_ime_shift_off);
|
||||
key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_off);
|
||||
break;
|
||||
case SHIFT_ON:
|
||||
key.icon = getContext().getResources().getDrawable(R.drawable.ic_ime_shift_on);
|
||||
key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_on);
|
||||
break;
|
||||
case SHIFT_LOCKED:
|
||||
key.icon = getContext().getResources().getDrawable(R.drawable.ic_ime_shift_lock_on);
|
||||
if (mCustomCapsLockDrawable != null) {
|
||||
key.icon = mCustomCapsLockDrawable;
|
||||
} else {
|
||||
key.icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_ime_shift_lock_on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int dx = (key.width - padding.left - padding.right - key.icon.getIntrinsicWidth()) / 2 + padding.left;
|
||||
int dy = (key.height - padding.top - padding.bottom - key.icon.getIntrinsicHeight()) / 2 + padding.top;
|
||||
// NOTE: Fix non proper scale of space key on low dpi
|
||||
|
||||
int iconWidth = key.width; // originally used key.icon.getIntrinsicWidth();
|
||||
int iconHeight = key.height; // originally used key.icon.getIntrinsicHeight();
|
||||
|
||||
if (key.width == key.height) { // square key proper fit
|
||||
int newSize = Math.round(key.width * mSquareIconScaleFactor);
|
||||
iconWidth = newSize;
|
||||
iconHeight = newSize;
|
||||
}
|
||||
|
||||
if (key.codes[0] == ASCII_SPACE && LeanKeyPreferences.instance(getContext()).getEnlargeKeyboard()) {
|
||||
// space fix for large interface
|
||||
float gap = getResources().getDimension(R.dimen.keyboard_horizontal_gap);
|
||||
float gapDelta = (gap * 1.3f) - gap;
|
||||
iconWidth -= gapDelta * (ASCII_PERIOD_LEN - 1);
|
||||
}
|
||||
|
||||
int dx = (key.width - padding.left - padding.right - iconWidth) / 2 + padding.left;
|
||||
int dy = (key.height - padding.top - padding.bottom - iconHeight) / 2 + padding.top;
|
||||
|
||||
canvas.translate((float) dx, (float) dy);
|
||||
key.icon.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
|
||||
key.icon.setBounds(0, 0, iconWidth, iconHeight);
|
||||
key.icon.draw(canvas);
|
||||
canvas.translate((float) (-dx), (float) (-dy));
|
||||
} else if (label != null) {
|
||||
@@ -215,15 +242,21 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
|
||||
}
|
||||
|
||||
canvas.drawText(label, (float) ((key.width - padding.left - padding.right) / 2 + padding.left), (float) ((key.height - padding.top - padding.bottom) /
|
||||
2) + (paint.getTextSize() - paint.descent()) / 2.0F + (float) padding.top, paint);
|
||||
canvas.drawText(
|
||||
label,
|
||||
(float) ((key.width - padding.left - padding.right) / 2 + padding.left),
|
||||
(float) ((key.height - padding.top - padding.bottom) / 2) + (paint.getTextSize() - paint.descent()) / 2.0F + (float) padding.top,
|
||||
paint
|
||||
);
|
||||
paint.setShadowLayer(0.0F, 0.0F, 0.0F, 0);
|
||||
}
|
||||
|
||||
ImageView image = new ImageView(getContext());
|
||||
image.setImageBitmap(bitmap);
|
||||
image.setContentDescription(label);
|
||||
addView(image, new LayoutParams(-2, -2));
|
||||
// Adds key views to root window
|
||||
addView(image, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
// Set position manually for each key
|
||||
image.setX((float) (key.x + kbdPaddingLeft));
|
||||
image.setY((float) (key.y + kbdPaddingTop));
|
||||
int opacity;
|
||||
@@ -235,10 +268,11 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
|
||||
image.setImageAlpha(opacity);
|
||||
image.setVisibility(View.VISIBLE);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private void createKeyImageViews(LeanbackKeyboardView.KeyHolder[] keys) {
|
||||
private void createKeyImageViews(KeyHolder[] keys) {
|
||||
if (mKeyImageViews != null) {
|
||||
ImageView[] images = mKeyImageViews;
|
||||
int totalImages = images.length;
|
||||
@@ -268,15 +302,18 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
Log.w(TAG, "method 'removeMessages()' not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Keys initialization routine.<br/>
|
||||
* Any manipulations with keys should be done here.
|
||||
*/
|
||||
private void setKeys(List<Key> keys) {
|
||||
mKeys = new LeanbackKeyboardView.KeyHolder[keys.size()];
|
||||
Iterator iterator = keys.iterator();
|
||||
mKeys = new KeyHolder[keys.size()];
|
||||
Iterator<Key> iterator = keys.iterator();
|
||||
|
||||
for (int i = 0; i < mKeys.length && iterator.hasNext(); ++i) {
|
||||
Key key = (Key) iterator.next();
|
||||
mKeys[i] = new LeanbackKeyboardView.KeyHolder(key);
|
||||
Key key = iterator.next();
|
||||
mKeys[i] = new KeyHolder(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean dismissMiniKeyboard() {
|
||||
@@ -352,7 +389,6 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
indexFull += mColCount * result;
|
||||
result = indexFull;
|
||||
if (indexFull > ASCII_PERIOD) { // key goes beyond space
|
||||
result = indexFull;
|
||||
if (indexFull < (ASCII_PERIOD + ASCII_PERIOD_LEN)) { // key stays within space boundary
|
||||
result = ASCII_PERIOD;
|
||||
}
|
||||
@@ -405,7 +441,7 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
}
|
||||
|
||||
public boolean isShifted() {
|
||||
return mShiftState == 1 || mShiftState == 2;
|
||||
return mShiftState == SHIFT_ON || mShiftState == SHIFT_LOCKED;
|
||||
}
|
||||
|
||||
public void onDraw(Canvas canvas) {
|
||||
@@ -414,6 +450,7 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
|
||||
public void onKeyLongPress() {
|
||||
int popupResId = mKeys[mFocusIndex].key.popupResId;
|
||||
|
||||
if (popupResId != 0) {
|
||||
dismissMiniKeyboard();
|
||||
mMiniKeyboardOnScreen = true;
|
||||
@@ -435,20 +472,26 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
accentKey.edgeFlags = mKeys[baseIndex + i].key.edgeFlags;
|
||||
mKeys[baseIndex + i].key = accentKey;
|
||||
mKeys[baseIndex + i].isInMiniKb = true;
|
||||
LeanbackKeyboardView.KeyHolder holder = mKeys[baseIndex + i];
|
||||
boolean invertible;
|
||||
if (i == 0) {
|
||||
invertible = true;
|
||||
} else {
|
||||
invertible = false;
|
||||
}
|
||||
KeyHolder holder = mKeys[baseIndex + i];
|
||||
|
||||
holder.isInvertible = invertible;
|
||||
holder.isInvertible = i == 0; // uppercase first char
|
||||
}
|
||||
|
||||
invalidateAllKeys();
|
||||
}
|
||||
} else {
|
||||
boolean isSpecialKey = mKeys[mFocusIndex].key.icon != null; // space, paste, voice input etc
|
||||
|
||||
if (!isSpecialKey) { // simply use the same char in uppercase
|
||||
dismissMiniKeyboard();
|
||||
mMiniKeyboardOnScreen = true;
|
||||
mBaseMiniKbIndex = mFocusIndex;
|
||||
|
||||
mKeys[mFocusIndex].isInMiniKb = true;
|
||||
mKeys[mFocusIndex].isInvertible = true;
|
||||
|
||||
invalidateAllKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
@@ -475,7 +518,7 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Move focus to the key specified by index
|
||||
* NOTE: Increase size of currently focused or clicked key
|
||||
* @param index index of the key
|
||||
* @param clicked key state
|
||||
* @param showFocusScale increase size
|
||||
@@ -504,15 +547,15 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
|
||||
if (mCurrentFocusView != null) {
|
||||
mCurrentFocusView.animate()
|
||||
.scaleX(1.0F)
|
||||
.scaleY(1.0F)
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator)
|
||||
.setStartDelay((long) mUnfocusStartDelay);
|
||||
.setStartDelay(mUnfocusStartDelay);
|
||||
|
||||
mCurrentFocusView.animate()
|
||||
.setDuration((long) mClickAnimDur)
|
||||
.setDuration(mClickAnimDur)
|
||||
.setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator)
|
||||
.setStartDelay((long) mUnfocusStartDelay);
|
||||
.setStartDelay(mUnfocusStartDelay);
|
||||
}
|
||||
|
||||
if (indexFull != -1) {
|
||||
@@ -527,7 +570,7 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.setInterpolator(LeanbackKeyboardContainer.sMovementInterpolator)
|
||||
.setDuration((long) mClickAnimDur)
|
||||
.setDuration(mClickAnimDur)
|
||||
.start();
|
||||
}
|
||||
|
||||
@@ -576,7 +619,7 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyHolder {
|
||||
private static class KeyHolder {
|
||||
public boolean isInMiniKb = false;
|
||||
public boolean isInvertible = false;
|
||||
public Key key;
|
||||
@@ -585,4 +628,12 @@ public class LeanbackKeyboardView extends FrameLayout {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCapsLockDrawable(Drawable drawable) {
|
||||
mCustomCapsLockDrawable = drawable;
|
||||
}
|
||||
|
||||
public void setKeyTextColor(int color) {
|
||||
mKeyTextColor = color;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.leanback.ime;
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.text.InputType;
|
||||
@@ -11,11 +11,11 @@ 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 String TAG = "LbSuggestionsFactory";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Use short text tag to fix "Log tag exceeds limit of 23 characters"
|
||||
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;
|
||||
@@ -28,6 +28,7 @@ public class LeanbackSuggestionsFactory {
|
||||
|
||||
public void clearSuggestions() {
|
||||
mSuggestions.clear();
|
||||
mSuggestions.add(null); // make room for user input, see LeanbackKeyboardContainer.addUserInputToSuggestions
|
||||
}
|
||||
|
||||
public void createSuggestions() {
|
||||
@@ -61,9 +62,9 @@ public class LeanbackSuggestionsFactory {
|
||||
mSuggestions.add(i, infos[i].getText().toString());
|
||||
}
|
||||
|
||||
if (Log.isLoggable("LbSuggestionsFactory", Log.DEBUG)) {
|
||||
if (DEBUG) {
|
||||
for (len = 0; len < mSuggestions.size(); ++len) {
|
||||
Log.d("LbSuggestionsFactory", "completion " + len + ": " + mSuggestions.get(len));
|
||||
Log.d(TAG, "completion " + len + ": " + mSuggestions.get(len));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ public class LeanbackSuggestionsFactory {
|
||||
|
||||
public void onStartInput(EditorInfo info) {
|
||||
mMode = MODE_DEFAULT;
|
||||
if ((info.inputType & 65536) != 0) {
|
||||
if ((info.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
|
||||
mMode = MODE_AUTO_COMPLETE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.liskovsoft.leankeyboard.ime;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackKeyboardContainer.KeyFocus;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class LeanbackUtils {
|
||||
private static final int ACCESSIBILITY_DELAY_MS = 250;
|
||||
private static final String EDITOR_LABEL = "label";
|
||||
private static final Handler sAccessibilityHandler = new Handler();
|
||||
private static final String TAG = LeanbackUtils.class.getSimpleName();
|
||||
|
||||
public static int getImeAction(EditorInfo info) {
|
||||
return info.imeOptions & (EditorInfo.IME_FLAG_NO_ENTER_ACTION | EditorInfo.IME_MASK_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class of the input
|
||||
* @param info attrs
|
||||
* @return constant e.g. {@link InputType#TYPE_CLASS_TEXT InputType.TYPE_CLASS_TEXT}
|
||||
*/
|
||||
public static int getInputTypeClass(EditorInfo info) {
|
||||
return info.inputType & InputType.TYPE_MASK_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variation of the input
|
||||
* @param info attrs
|
||||
* @return constant e.g. {@link InputType#TYPE_DATETIME_VARIATION_DATE InputType.TYPE_DATETIME_VARIATION_DATE}
|
||||
*/
|
||||
public static int getInputTypeVariation(EditorInfo info) {
|
||||
return info.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
}
|
||||
|
||||
public static boolean isAlphabet(int letter) {
|
||||
return Character.isLetter(letter);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static void sendAccessibilityEvent(final View view, boolean focusGained) {
|
||||
if (view != null && focusGained) {
|
||||
sAccessibilityHandler.removeCallbacksAndMessages(null);
|
||||
sAccessibilityHandler.postDelayed(() -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT), ACCESSIBILITY_DELAY_MS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static int getAmpersandLocation(InputConnection connection) {
|
||||
String text = getEditorText(connection);
|
||||
int pos = text.indexOf(64);
|
||||
if (pos < 0) { // not found
|
||||
pos = text.length();
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
public static int getCharLengthAfterCursor(InputConnection connection) {
|
||||
int len = 0;
|
||||
CharSequence after = connection.getTextAfterCursor(1000, 0);
|
||||
if (after != null) {
|
||||
len = after.length();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static int getCharLengthBeforeCursor(InputConnection connection) {
|
||||
int len = 0;
|
||||
CharSequence before = connection.getTextBeforeCursor(1000, 0);
|
||||
if (before != null) {
|
||||
len = before.length();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static String getEditorText(InputConnection connection) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
CharSequence before = connection.getTextBeforeCursor(1000, 0);
|
||||
CharSequence after = connection.getTextAfterCursor(1000, 0);
|
||||
if (before != null) {
|
||||
result.append(before);
|
||||
}
|
||||
|
||||
if (after != null) {
|
||||
result.append(after);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static void sendEnterKey(InputConnection connection) {
|
||||
connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
}
|
||||
|
||||
public static String getEditorLabel(EditorInfo info) {
|
||||
if (info != null && info.extras != null && info.extras.containsKey(EDITOR_LABEL)) {
|
||||
return info.extras.getString(EDITOR_LABEL);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DisplayMetrics createMetricsFrom(Context context, float factor) {
|
||||
DisplayMetrics metrics = null;
|
||||
Object service = context.getSystemService(Context.WINDOW_SERVICE);
|
||||
|
||||
if (service instanceof WindowManager) {
|
||||
WindowManager manager = (WindowManager) service;
|
||||
metrics = new DisplayMetrics();
|
||||
manager.getDefaultDisplay().getMetrics(metrics);
|
||||
Log.d(TAG, metrics.toString());
|
||||
|
||||
// new values
|
||||
metrics.density *= factor;
|
||||
metrics.densityDpi *= factor;
|
||||
metrics.heightPixels *= factor;
|
||||
metrics.widthPixels *= factor;
|
||||
metrics.scaledDensity *= factor;
|
||||
metrics.xdpi *= factor;
|
||||
metrics.ydpi *= factor;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public static void showKeyboardPicker(Context context) {
|
||||
if (context != null) {
|
||||
InputMethodManager imeManager = (InputMethodManager) context.getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imeManager != null) {
|
||||
imeManager.showInputMethodPicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getRtlLenAfterCursor(CharSequence text) {
|
||||
if (text == null || text.length() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BidiFormatter formatter = BidiFormatter.getInstance();
|
||||
int len = 0;
|
||||
|
||||
for (int i = 1; i < text.length(); i++) {
|
||||
CharSequence charSequence = text.subSequence(len, i);
|
||||
if (formatter.isRtl(charSequence)) {
|
||||
len++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static int getRtlLenBeforeCursor(CharSequence text) {
|
||||
if (text == null || text.length() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BidiFormatter formatter = BidiFormatter.getInstance();
|
||||
int len = 0;
|
||||
|
||||
for (int i = text.length(); i > 0; i--) {
|
||||
CharSequence charSequence = text.subSequence(i-1, i);
|
||||
if (formatter.isRtl(charSequence)) {
|
||||
len++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static boolean isSubmitButton(KeyFocus focus) {
|
||||
return focus.index == 0 && focus.type == KeyFocus.TYPE_ACTION;
|
||||
}
|
||||
|
||||
public static boolean isSuggestionsButton(KeyFocus focus) {
|
||||
return focus.type == KeyFocus.TYPE_SUGGESTION;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.pano.util;
|
||||
package com.liskovsoft.leankeyboard.ime.pano.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.InputDevice;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.google.android.pano.util;
|
||||
package com.liskovsoft.leankeyboard.ime.pano.util;
|
||||
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.graphics.PointF;
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.liskovsoft.leankeyboard.ime.voice;
|
||||
|
||||
import android.animation.TimeAnimator;
|
||||
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.Paint.Style;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import androidx.core.content.ContextCompat;
|
||||
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);
|
||||
mEmptyPaint = new Paint();
|
||||
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BitmapSoundLevelView, defStyleAttr, 0);
|
||||
mEnableBackgroundColor = styledAttrs.getColor(R.styleable.BitmapSoundLevelView_enabledBackgroundColor, Color.parseColor("#66FFFFFF"));
|
||||
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;
|
||||
}
|
||||
|
||||
mCenterTranslationX = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterX, 0);
|
||||
mCenterTranslationY = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_levelsCenterY, 0);
|
||||
mMinimumLevelSize = styledAttrs.getDimensionPixelOffset(R.styleable.BitmapSoundLevelView_minLevelRadius, 0);
|
||||
styledAttrs.recycle();
|
||||
if (primaryLevelEnabled) {
|
||||
mPrimaryLevel = BitmapFactory.decodeResource(getResources(), primaryLevelId);
|
||||
} else {
|
||||
mPrimaryLevel = null;
|
||||
}
|
||||
|
||||
if (peakLevelEnabled) {
|
||||
mTrailLevel = BitmapFactory.decodeResource(getResources(), trailLevelId);
|
||||
} else {
|
||||
mTrailLevel = null;
|
||||
}
|
||||
|
||||
mPaint = new Paint();
|
||||
mDestRect = new Rect();
|
||||
mEmptyPaint.setFilterBitmap(true);
|
||||
mLevelSource = new SpeechLevelSource();
|
||||
mLevelSource.setSpeechLevel(0);
|
||||
mAnimator = new TimeAnimator();
|
||||
mAnimator.setRepeatCount(-1);
|
||||
mAnimator.setTimeListener((animation, totalTime, deltaTime) -> invalidate());
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private void startAnimator() {
|
||||
if (!mAnimator.isStarted()) {
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private void stopAnimator() {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
|
||||
private void updateAnimatorState() {
|
||||
if (isEnabled()) {
|
||||
startAnimator();
|
||||
} else {
|
||||
stopAnimator();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
updateAnimatorState();
|
||||
}
|
||||
|
||||
protected void onDetachedFromWindow() {
|
||||
stopAnimator();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (isEnabled()) {
|
||||
canvas.drawColor(mEnableBackgroundColor);
|
||||
final int level = mLevelSource.getSpeechLevel();
|
||||
if (level > mPeakLevel) {
|
||||
mPeakLevel = level;
|
||||
mPeakLevelCountDown = 25;
|
||||
} else if (mPeakLevelCountDown == 0) {
|
||||
mPeakLevel = Math.max(0, mPeakLevel - 2);
|
||||
} else {
|
||||
--mPeakLevelCountDown;
|
||||
}
|
||||
|
||||
if (level > mCurrentVolume) {
|
||||
mCurrentVolume += (level - mCurrentVolume) / 4;
|
||||
} else {
|
||||
mCurrentVolume = (int) ((float) mCurrentVolume * 0.95F);
|
||||
}
|
||||
|
||||
final int centerX = mCenterTranslationX + getWidth() / 2;
|
||||
final int centerY = mCenterTranslationY + getWidth() / 2;
|
||||
int size;
|
||||
if (mTrailLevel != null) {
|
||||
size = (centerX - mMinimumLevelSize) * mPeakLevel / 100 + mMinimumLevelSize;
|
||||
mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size);
|
||||
canvas.drawBitmap(mTrailLevel, null, mDestRect, mEmptyPaint);
|
||||
}
|
||||
|
||||
if (mPrimaryLevel != null) {
|
||||
size = (centerX - mMinimumLevelSize) * mCurrentVolume / 100 + mMinimumLevelSize;
|
||||
mDestRect.set(centerX - size, centerY - size, centerX + size, centerY + size);
|
||||
canvas.drawBitmap(mPrimaryLevel, null, mDestRect, mEmptyPaint);
|
||||
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.search_mic_background));
|
||||
mPaint.setStyle(Style.FILL);
|
||||
canvas.drawCircle((float) centerX, (float) centerY, (float) (mMinimumLevelSize - 3), mPaint);
|
||||
}
|
||||
|
||||
if (mTrailLevel != null && mPrimaryLevel != null) {
|
||||
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.search_mic_levels_guideline));
|
||||
mPaint.setStyle(Style.STROKE);
|
||||
canvas.drawCircle((float) centerX, (float) centerY, (float) (centerX - 13), mPaint);
|
||||
}
|
||||
|
||||
} else {
|
||||
canvas.drawColor(mDisableBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean var1) {
|
||||
super.onWindowFocusChanged(var1);
|
||||
if (var1) {
|
||||
updateAnimatorState();
|
||||
} else {
|
||||
stopAnimator();
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnabled(boolean var1) {
|
||||
super.setEnabled(var1);
|
||||
updateAnimatorState();
|
||||
}
|
||||
|
||||
public void setLevelSource(SpeechLevelSource var1) {
|
||||
mLevelSource = var1;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
package com.google.android.leanback.ime.voice;
|
||||
package com.liskovsoft.leankeyboard.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.leankeyboard.ime.LeanbackUtils;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
public class RecognizerView extends RelativeLayout {
|
||||
@@ -23,48 +21,52 @@ public class RecognizerView extends RelativeLayout {
|
||||
private BitmapSoundLevelView mSoundLevels;
|
||||
private RecognizerView.State mState;
|
||||
|
||||
public RecognizerView(Context var1) {
|
||||
super(var1);
|
||||
private enum State {
|
||||
LISTENING, MIC_INITIALIZING, NOT_LISTENING, RECOGNIZING, RECORDING;
|
||||
}
|
||||
|
||||
public RecognizerView(Context var1, AttributeSet var2) {
|
||||
super(var1, var2);
|
||||
public RecognizerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RecognizerView(Context var1, AttributeSet var2, int var3) {
|
||||
super(var1, var2, var3);
|
||||
public RecognizerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
private void updateState(RecognizerView.State var1) {
|
||||
this.mState = var1;
|
||||
this.refreshUi();
|
||||
public RecognizerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void updateState(State state) {
|
||||
mState = state;
|
||||
refreshUi();
|
||||
}
|
||||
|
||||
public View getMicButton() {
|
||||
return this.mMicButton;
|
||||
return mMicButton;
|
||||
}
|
||||
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
this.refreshUi();
|
||||
refreshUi();
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
switch (this.mState) {
|
||||
switch (mState) {
|
||||
case MIC_INITIALIZING:
|
||||
default:
|
||||
return;
|
||||
case LISTENING:
|
||||
this.mCallback.onCancelRecordingClicked();
|
||||
mCallback.onCancelRecordingClicked();
|
||||
return;
|
||||
case RECORDING:
|
||||
this.mCallback.onStopRecordingClicked();
|
||||
mCallback.onStopRecordingClicked();
|
||||
return;
|
||||
case RECOGNIZING:
|
||||
this.mCallback.onCancelRecordingClicked();
|
||||
mCallback.onCancelRecordingClicked();
|
||||
return;
|
||||
case NOT_LISTENING:
|
||||
this.mCallback.onStartRecordingClicked();
|
||||
mCallback.onStartRecordingClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,51 +74,51 @@ public class RecognizerView extends RelativeLayout {
|
||||
@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;
|
||||
mSoundLevels = (BitmapSoundLevelView) findViewById(R.id.microphone);
|
||||
mMicButton = (ImageView) findViewById(R.id.recognizer_mic_button);
|
||||
mState = RecognizerView.State.NOT_LISTENING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable var1) {
|
||||
if (!(var1 instanceof RecognizerView.SavedState)) {
|
||||
super.onRestoreInstanceState(var1);
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (!(state instanceof RecognizerView.SavedState)) {
|
||||
super.onRestoreInstanceState(state);
|
||||
} else {
|
||||
RecognizerView.SavedState var2 = (RecognizerView.SavedState) var1;
|
||||
super.onRestoreInstanceState(var2.getSuperState());
|
||||
this.mState = var2.mState;
|
||||
RecognizerView.SavedState savedState = (RecognizerView.SavedState) state;
|
||||
super.onRestoreInstanceState(savedState.getSuperState());
|
||||
mState = savedState.mState;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
RecognizerView.SavedState var1 = new RecognizerView.SavedState(super.onSaveInstanceState());
|
||||
var1.mState = this.mState;
|
||||
return var1;
|
||||
RecognizerView.SavedState savedState = new RecognizerView.SavedState(super.onSaveInstanceState());
|
||||
savedState.mState = mState;
|
||||
return savedState;
|
||||
}
|
||||
|
||||
protected void refreshUi() {
|
||||
if (this.mEnabled) {
|
||||
switch (this.mState) {
|
||||
if (mEnabled) {
|
||||
switch (mState) {
|
||||
case MIC_INITIALIZING:
|
||||
this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector);
|
||||
this.mSoundLevels.setEnabled(false);
|
||||
mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector);
|
||||
mSoundLevels.setEnabled(false);
|
||||
return;
|
||||
case LISTENING:
|
||||
this.mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector);
|
||||
this.mSoundLevels.setEnabled(true);
|
||||
mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector);
|
||||
mSoundLevels.setEnabled(true);
|
||||
return;
|
||||
case RECORDING:
|
||||
this.mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector);
|
||||
this.mSoundLevels.setEnabled(true);
|
||||
mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector);
|
||||
mSoundLevels.setEnabled(true);
|
||||
return;
|
||||
case RECOGNIZING:
|
||||
this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector);
|
||||
this.mSoundLevels.setEnabled(false);
|
||||
mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector);
|
||||
mSoundLevels.setEnabled(false);
|
||||
return;
|
||||
case NOT_LISTENING:
|
||||
this.mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector);
|
||||
this.mSoundLevels.setEnabled(false);
|
||||
mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector);
|
||||
mSoundLevels.setEnabled(false);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
@@ -124,55 +126,55 @@ public class RecognizerView extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void setCallback(RecognizerView.Callback callback) {
|
||||
this.mCallback = callback;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void setMicEnabled(boolean enabled) {
|
||||
this.mEnabled = enabled;
|
||||
mEnabled = enabled;
|
||||
if (enabled) {
|
||||
this.mMicButton.setAlpha(1.0F);
|
||||
this.mMicButton.setImageResource(R.drawable.ic_voice_available);
|
||||
mMicButton.setAlpha(1.0F);
|
||||
mMicButton.setImageResource(R.drawable.ic_voice_available);
|
||||
} else {
|
||||
this.mMicButton.setAlpha(0.1F);
|
||||
this.mMicButton.setImageResource(R.drawable.ic_voice_off);
|
||||
mMicButton.setAlpha(0.1F);
|
||||
mMicButton.setImageResource(R.drawable.ic_voice_off);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMicFocused(boolean focused) {
|
||||
if (this.mEnabled) {
|
||||
if (mEnabled) {
|
||||
if (focused) {
|
||||
this.mMicButton.setImageResource(R.drawable.ic_voice_focus);
|
||||
mMicButton.setImageResource(R.drawable.ic_voice_focus);
|
||||
} else {
|
||||
this.mMicButton.setImageResource(R.drawable.ic_voice_available);
|
||||
mMicButton.setImageResource(R.drawable.ic_voice_available);
|
||||
}
|
||||
|
||||
LeanbackUtils.sendAccessibilityEvent(this.mMicButton, focused);
|
||||
LeanbackUtils.sendAccessibilityEvent(mMicButton, focused);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setSpeechLevelSource(SpeechLevelSource var1) {
|
||||
this.mSoundLevels.setLevelSource(var1);
|
||||
mSoundLevels.setLevelSource(var1);
|
||||
}
|
||||
|
||||
public void showInitializingMic() {
|
||||
this.updateState(RecognizerView.State.MIC_INITIALIZING);
|
||||
updateState(State.MIC_INITIALIZING);
|
||||
}
|
||||
|
||||
public void showListening() {
|
||||
this.updateState(RecognizerView.State.LISTENING);
|
||||
updateState(State.LISTENING);
|
||||
}
|
||||
|
||||
public void showNotListening() {
|
||||
this.updateState(RecognizerView.State.NOT_LISTENING);
|
||||
updateState(State.NOT_LISTENING);
|
||||
}
|
||||
|
||||
public void showRecognizing() {
|
||||
this.updateState(RecognizerView.State.RECOGNIZING);
|
||||
updateState(State.RECOGNIZING);
|
||||
}
|
||||
|
||||
public void showRecording() {
|
||||
this.updateState(RecognizerView.State.RECORDING);
|
||||
updateState(State.RECORDING);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
@@ -209,8 +211,4 @@ public class RecognizerView extends RelativeLayout {
|
||||
var1.writeString(this.mState.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static enum State {
|
||||
LISTENING, MIC_INITIALIZING, NOT_LISTENING, RECOGNIZING, RECORDING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.liskovsoft.leankeyboard.ime.voice;
|
||||
|
||||
public class SpeechLevelSource {
|
||||
private volatile int mSpeechLevel;
|
||||
|
||||
public int getSpeechLevel() {
|
||||
return mSpeechLevel;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return mSpeechLevel > 0;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mSpeechLevel = -1;
|
||||
}
|
||||
|
||||
public void setSpeechLevel(int speechLevel) {
|
||||
if (speechLevel >= 0 && speechLevel <= 100) {
|
||||
mSpeechLevel = speechLevel;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.liskovsoft.leankeyboard.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import com.liskovsoft.leankeyboard.ime.LeanbackImeService;
|
||||
|
||||
public class RestartServiceReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = RestartServiceReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
sendMessageToService(context);
|
||||
//restartService(context);
|
||||
}
|
||||
|
||||
private void sendMessageToService(Context context) {
|
||||
Log.d(TAG, "Sending restart message to the service");
|
||||
Intent intent = new Intent(context, LeanbackImeService.class);
|
||||
intent.putExtra(LeanbackImeService.COMMAND_RESTART, true);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
private void restartService(Context context) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.liskovsoft.leankeyboard.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class TextUpdateReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = TextUpdateReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, intent.toUri(0));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.liskovsoft.utils;
|
||||
package com.liskovsoft.leankeyboard.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Configuration;
|
||||
import com.liskovsoft.leankeyboard.helpers.Helpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -74,11 +75,7 @@ public class LangUpdater {
|
||||
}
|
||||
|
||||
Locale locale = parseLangCode(langCode);
|
||||
Locale.setDefault(locale);
|
||||
Configuration config = mContext.getResources().getConfiguration();
|
||||
config.locale = locale;
|
||||
mContext.getResources().updateConfiguration(config,
|
||||
mContext.getResources().getDisplayMetrics());
|
||||
LocaleUtility.forceLocaleOld(mContext, locale);
|
||||
}
|
||||
|
||||
private boolean isRussianPackage(String pkgName) {
|
||||
@@ -103,7 +100,7 @@ public class LangUpdater {
|
||||
|
||||
public String getLocale() {
|
||||
Configuration config = mContext.getResources().getConfiguration();
|
||||
return config.locale.getLanguage();
|
||||
return LocaleUtility.getSystemLocale(config).getLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.liskovsoft.leankeyboard.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public final class LeanKeyPreferences {
|
||||
private static final String APP_RUN_ONCE = "appRunOnce";
|
||||
private static final String BOOTSTRAP_SELECTED_LANGUAGE = "bootstrapSelectedLanguage";
|
||||
private static final String APP_KEYBOARD_INDEX = "appKeyboardIndex";
|
||||
private static final String FORCE_SHOW_KEYBOARD = "forceShowKeyboard";
|
||||
private static final String ENLARGE_KEYBOARD = "enlargeKeyboard";
|
||||
private static final String KEYBOARD_THEME = "keyboardTheme";
|
||||
public static final String THEME_DEFAULT = "Default";
|
||||
public static final String THEME_DARK = "Dark";
|
||||
public static final String THEME_DARK2 = "Dark2";
|
||||
public static final String THEME_DARK3 = "Dark3";
|
||||
private static final String SUGGESTIONS_ENABLED = "suggestionsEnabled";
|
||||
private static final String CYCLIC_NAVIGATION_ENABLED = "cyclicNavigationEnabled";
|
||||
private static final String AUTODETECT_LAYOUT = "autodetectLayout";
|
||||
private static LeanKeyPreferences sInstance;
|
||||
private final Context mContext;
|
||||
private SharedPreferences mPrefs;
|
||||
|
||||
public static LeanKeyPreferences instance(Context ctx) {
|
||||
if (sInstance == null)
|
||||
sInstance = new LeanKeyPreferences(ctx);
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public LeanKeyPreferences(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
}
|
||||
|
||||
public boolean isRunOnce() {
|
||||
return mPrefs.getBoolean(APP_RUN_ONCE, false);
|
||||
}
|
||||
|
||||
public void setRunOnce(boolean runOnce) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(APP_RUN_ONCE, runOnce)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void setPreferredLanguage(String name) {
|
||||
mPrefs.edit()
|
||||
.putString(BOOTSTRAP_SELECTED_LANGUAGE, name)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public String getPreferredLanguage() {
|
||||
return mPrefs.getString(BOOTSTRAP_SELECTED_LANGUAGE, "");
|
||||
}
|
||||
|
||||
public int getKeyboardIndex() {
|
||||
return mPrefs.getInt(APP_KEYBOARD_INDEX, 0);
|
||||
}
|
||||
|
||||
public void setKeyboardIndex(int idx) {
|
||||
mPrefs.edit()
|
||||
.putInt(APP_KEYBOARD_INDEX, idx)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public boolean getForceShowKeyboard() {
|
||||
return mPrefs.getBoolean(FORCE_SHOW_KEYBOARD, true);
|
||||
}
|
||||
|
||||
public void setForceShowKeyboard(boolean force) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(FORCE_SHOW_KEYBOARD, force)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public boolean getEnlargeKeyboard() {
|
||||
return mPrefs.getBoolean(ENLARGE_KEYBOARD, false);
|
||||
}
|
||||
|
||||
public void setEnlargeKeyboard(boolean enlarge) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(ENLARGE_KEYBOARD, enlarge)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void setCurrentTheme(String theme) {
|
||||
mPrefs.edit()
|
||||
.putString(KEYBOARD_THEME, theme)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public String getCurrentTheme() {
|
||||
return mPrefs.getString(KEYBOARD_THEME, THEME_DARK3);
|
||||
}
|
||||
|
||||
public void setSuggestionsEnabled(boolean enabled) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(SUGGESTIONS_ENABLED, enabled)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public boolean getSuggestionsEnabled() {
|
||||
return mPrefs.getBoolean(SUGGESTIONS_ENABLED, true);
|
||||
}
|
||||
|
||||
public void setCyclicNavigationEnabled(boolean enabled) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(CYCLIC_NAVIGATION_ENABLED, enabled)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public boolean getCyclicNavigationEnabled() {
|
||||
return mPrefs.getBoolean(CYCLIC_NAVIGATION_ENABLED, false);
|
||||
}
|
||||
|
||||
public boolean getAutodetectLayout() {
|
||||
return mPrefs.getBoolean(AUTODETECT_LAYOUT, false);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,9 @@
|
||||
package com.liskovsoft.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
package com.liskovsoft.leankeyboard.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2013 Phil Brown
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Get Script name by Locale
|
||||
* <br>
|
||||
* @author Phil Brown
|
||||
* @since 9:47:09 AM Dec 20, 2013
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Additional info:
|
||||
* https://en.wikipedia.org/wiki/Writing_system
|
||||
@@ -38,10 +12,10 @@ import java.util.Map;
|
||||
* http://unicode.org/iso15924/iso15924-codes.html
|
||||
*
|
||||
* Usage:
|
||||
* String script = LocaleUtilities.getScript(Locale.getDefault());
|
||||
* String script = LocaleUtilities.getScript(getDefaultLocale(myActivity));
|
||||
* String script = LocaleScript.getScript(Locale.getDefault());
|
||||
* String script = LocaleScript.getScript(getDefaultLocale(myActivity));
|
||||
*/
|
||||
public class LocaleUtility {
|
||||
class LocaleScript {
|
||||
|
||||
public static Map<String, Map<String, String>> scriptsByLocale = new HashMap<String, Map<String, String>>();
|
||||
|
||||
@@ -750,19 +724,4 @@ public class LocaleUtility {
|
||||
return script == null ? scripts.get("") : script;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain right locale even if the user changes their Locale in settings after your application process is running.
|
||||
* Android N (Api level 24) update (no warnings).
|
||||
* @param context activity
|
||||
* @return locale
|
||||
*/
|
||||
public static Locale getCurrentLocale(Context context){
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
|
||||
return context.getResources().getConfiguration().getLocales().get(0);
|
||||
} else{
|
||||
//noinspection deprecation
|
||||
return context.getResources().getConfiguration().locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.liskovsoft.leankeyboard.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build.VERSION;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class LocaleUtility extends LocaleScript {
|
||||
private static final String TAG = LocaleUtility.class.getSimpleName();
|
||||
|
||||
public static Locale getSystemLocale(Context context) {
|
||||
return getSystemLocale(context.getResources().getConfiguration());
|
||||
}
|
||||
|
||||
public static void setSystemLocale(Context context, Locale locale) {
|
||||
setSystemLocale(context.getResources().getConfiguration(), locale);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setSystemLocale(Configuration config, Locale locale) {
|
||||
if (VERSION.SDK_INT < 24) {
|
||||
config.locale = locale;
|
||||
} else {
|
||||
config.setLocale(locale);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Locale getSystemLocale(Configuration config) {
|
||||
if (VERSION.SDK_INT < 24) {
|
||||
return config.locale;
|
||||
} else {
|
||||
return config.getLocales().get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <a href="https://stackoverflow.com/questions/40221711/android-context-getresources-updateconfiguration-deprecated/40704077#40704077">Modern Solution</a>
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void forceLocaleOld(Context ctx, Locale locale) {
|
||||
Locale.setDefault(locale);
|
||||
Configuration config = ctx.getResources().getConfiguration();
|
||||
LocaleUtility.setSystemLocale(config, locale);
|
||||
ctx.getResources().updateConfiguration(config,
|
||||
ctx.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static void switchRuLocale(Context ctx) {
|
||||
Log.d(TAG, "Trying to switch locale back and forward");
|
||||
Locale savedLocale = Locale.getDefault();
|
||||
LocaleUtility.forceLocaleOld(ctx, new Locale("ru"));
|
||||
LocaleUtility.forceLocaleOld(ctx, savedLocale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/**
|
||||
* Example usage: https://github.com/devunwired/textdrawable/blob/master/sample/src/main/java/com/example/textdrawable/MyActivity.java
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright (c) 2012 Wireless Designs, LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.liskovsoft.leankeyboard.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.TypedValue;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* A Drawable object that draws text.
|
||||
* A TextDrawable accepts most of the same parameters that can be applied to
|
||||
* {@link android.widget.TextView} for displaying and formatting text.
|
||||
*
|
||||
* Optionally, a {@link Path} may be supplied on which to draw the text.
|
||||
*
|
||||
* A TextDrawable has an intrinsic size equal to that required to draw all
|
||||
* the text it has been supplied, when possible. In cases where a {@link Path}
|
||||
* has been supplied, the caller must explicitly call
|
||||
* {@link #setBounds(Rect) setBounds()} to provide the Drawable
|
||||
* size based on the Path constraints.
|
||||
*/
|
||||
public class TextDrawable extends Drawable {
|
||||
|
||||
/* Platform XML constants for typeface */
|
||||
private static final int SANS = 1;
|
||||
private static final int SERIF = 2;
|
||||
private static final int MONOSPACE = 3;
|
||||
|
||||
/* Resources for scaling values to the given device */
|
||||
private Resources mResources;
|
||||
/* Paint to hold most drawing primitives for the text */
|
||||
private TextPaint mTextPaint;
|
||||
/* Layout is used to measure and draw the text */
|
||||
private StaticLayout mTextLayout;
|
||||
/* Alignment of the text inside its bounds */
|
||||
private Layout.Alignment mTextAlignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
/* Optional path on which to draw the text */
|
||||
private Path mTextPath;
|
||||
/* Stateful text color list */
|
||||
private ColorStateList mTextColors;
|
||||
/* Container for the bounds to be reported to widgets */
|
||||
private Rect mTextBounds;
|
||||
/* Text string to draw */
|
||||
private CharSequence mText = "";
|
||||
private final Drawable mDrawable;
|
||||
|
||||
/* Attribute lists to pull default values from the current theme */
|
||||
private static final int[] themeAttributes = {
|
||||
android.R.attr.textAppearance
|
||||
};
|
||||
private static final int[] appearanceAttributes = {
|
||||
android.R.attr.textSize,
|
||||
android.R.attr.typeface,
|
||||
android.R.attr.textStyle,
|
||||
android.R.attr.textColor
|
||||
};
|
||||
private float mTextSizeFactor;
|
||||
|
||||
public TextDrawable(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextDrawable(Context context, Drawable drawable) {
|
||||
super();
|
||||
mDrawable = drawable;
|
||||
//Used to load and scale resource items
|
||||
mResources = context.getResources();
|
||||
//Definition of this drawables size
|
||||
mTextBounds = new Rect();
|
||||
//Paint to use for the text
|
||||
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.density = mResources.getDisplayMetrics().density;
|
||||
mTextPaint.setDither(true);
|
||||
|
||||
if (mDrawable != null) {
|
||||
setBounds(mDrawable.getBounds());
|
||||
}
|
||||
|
||||
int textSize = 15;
|
||||
ColorStateList textColor = null;
|
||||
int styleIndex = -1;
|
||||
int typefaceIndex = -1;
|
||||
|
||||
//Set default parameters from the current theme
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(themeAttributes);
|
||||
int appearanceId = a.getResourceId(0, -1);
|
||||
a.recycle();
|
||||
|
||||
TypedArray ap = null;
|
||||
if (appearanceId != -1) {
|
||||
ap = context.obtainStyledAttributes(appearanceId, appearanceAttributes);
|
||||
}
|
||||
if (ap != null) {
|
||||
for (int i=0; i < ap.getIndexCount(); i++) {
|
||||
int attr = ap.getIndex(i);
|
||||
switch (attr) {
|
||||
case 0: //Text Size
|
||||
textSize = a.getDimensionPixelSize(attr, textSize);
|
||||
break;
|
||||
case 1: //Typeface
|
||||
typefaceIndex = a.getInt(attr, typefaceIndex);
|
||||
break;
|
||||
case 2: //Text Style
|
||||
styleIndex = a.getInt(attr, styleIndex);
|
||||
break;
|
||||
case 3: //Text Color
|
||||
textColor = a.getColorStateList(attr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ap.recycle();
|
||||
}
|
||||
|
||||
setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
|
||||
setRawTextSize(textSize);
|
||||
|
||||
Typeface tf = null;
|
||||
switch (typefaceIndex) {
|
||||
case SANS:
|
||||
tf = Typeface.SANS_SERIF;
|
||||
break;
|
||||
|
||||
case SERIF:
|
||||
tf = Typeface.SERIF;
|
||||
break;
|
||||
|
||||
case MONOSPACE:
|
||||
tf = Typeface.MONOSPACE;
|
||||
break;
|
||||
}
|
||||
|
||||
setTypeface(tf, styleIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text that will be displayed
|
||||
* @param text Text to display
|
||||
*/
|
||||
public void setText(CharSequence text) {
|
||||
if (text == null) text = "";
|
||||
|
||||
mText = text;
|
||||
|
||||
measureContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text currently being displayed
|
||||
*/
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current text size, in pixels
|
||||
*/
|
||||
public float getTextSize() {
|
||||
return mTextPaint.getTextSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text size. The value will be interpreted in "sp" units
|
||||
* @param size Text size value, in sp
|
||||
*/
|
||||
public void setTextSize(float size) {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text size, using the supplied complex units
|
||||
* @param unit Units for the text size, such as dp or sp
|
||||
* @param size Text size value
|
||||
*/
|
||||
public void setTextSize(int unit, float size) {
|
||||
float dimension = TypedValue.applyDimension(unit, size,
|
||||
mResources.getDisplayMetrics());
|
||||
setRawTextSize(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Text size compare to canvas size
|
||||
*/
|
||||
public void setTextSizeFactor(float factor) {
|
||||
mTextSizeFactor = factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the text size, in raw pixels
|
||||
*/
|
||||
private void setRawTextSize(float size) {
|
||||
if (size != mTextPaint.getTextSize()) {
|
||||
mTextPaint.setTextSize(size);
|
||||
|
||||
measureContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the horizontal stretch factor of the text
|
||||
*/
|
||||
public float getTextScaleX() {
|
||||
return mTextPaint.getTextScaleX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal stretch factor of the text
|
||||
* @param size Text scale factor
|
||||
*/
|
||||
public void setTextScaleX(float size) {
|
||||
if (size != mTextPaint.getTextScaleX()) {
|
||||
mTextPaint.setTextScaleX(size);
|
||||
measureContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current text alignment setting
|
||||
*/
|
||||
public Layout.Alignment getTextAlign() {
|
||||
return mTextAlignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text alignment. The alignment itself is based on the text layout direction.
|
||||
* For LTR text NORMAL is left aligned and OPPOSITE is right aligned.
|
||||
* For RTL text, those alignments are reversed.
|
||||
* @param align Text alignment value. Should be set to one of:
|
||||
*
|
||||
* {@link Layout.Alignment#ALIGN_NORMAL},
|
||||
* {@link Layout.Alignment#ALIGN_NORMAL},
|
||||
* {@link Layout.Alignment#ALIGN_OPPOSITE}.
|
||||
*/
|
||||
public void setTextAlign(Layout.Alignment align) {
|
||||
if (mTextAlignment != align) {
|
||||
mTextAlignment = align;
|
||||
measureContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the typeface and style in which the text should be displayed.
|
||||
* Note that not all Typeface families actually have bold and italic
|
||||
* variants, so you may need to use
|
||||
* {@link #setTypeface(Typeface, int)} to get the appearance
|
||||
* that you actually want.
|
||||
*/
|
||||
public void setTypeface(Typeface tf) {
|
||||
if (mTextPaint.getTypeface() != tf) {
|
||||
mTextPaint.setTypeface(tf);
|
||||
|
||||
measureContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the typeface and style in which the text should be displayed,
|
||||
* and turns on the fake bold and italic bits in the Paint if the
|
||||
* Typeface that you provided does not have all the bits in the
|
||||
* style that you specified.
|
||||
*
|
||||
*/
|
||||
public void setTypeface(Typeface tf, int style) {
|
||||
if (style > 0) {
|
||||
if (tf == null) {
|
||||
tf = Typeface.defaultFromStyle(style);
|
||||
} else {
|
||||
tf = Typeface.create(tf, style);
|
||||
}
|
||||
|
||||
setTypeface(tf);
|
||||
// now compute what (if any) algorithmic styling is needed
|
||||
int typefaceStyle = tf != null ? tf.getStyle() : 0;
|
||||
int need = style & ~typefaceStyle;
|
||||
mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
|
||||
mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
|
||||
} else {
|
||||
mTextPaint.setFakeBoldText(false);
|
||||
mTextPaint.setTextSkewX(0);
|
||||
setTypeface(tf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current typeface and style that the Paint
|
||||
* using for display.
|
||||
*/
|
||||
public Typeface getTypeface() {
|
||||
return mTextPaint.getTypeface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single text color for all states
|
||||
* @param color Color value such as {@link Color#WHITE} or {@link Color#argb(int, int, int, int)}
|
||||
*/
|
||||
public void setTextColor(int color) {
|
||||
setTextColor(ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text color as a state list
|
||||
* @param colorStateList ColorStateList of text colors, such as inflated from an R.color resource
|
||||
*/
|
||||
public void setTextColor(ColorStateList colorStateList) {
|
||||
mTextColors = colorStateList;
|
||||
updateTextColors(getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional Path object on which to draw the text. If this is set,
|
||||
* TextDrawable cannot properly measure the bounds this drawable will need.
|
||||
* You must call {@link #setBounds(int, int, int, int) setBounds()} before
|
||||
* applying this TextDrawable to any View.
|
||||
*
|
||||
* Calling this method with <code>null</code> will remove any Path currently attached.
|
||||
*/
|
||||
public void setTextPath(Path path) {
|
||||
if (mTextPath != path) {
|
||||
mTextPath = path;
|
||||
measureContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to take measurements of the current contents and apply
|
||||
* the correct bounds when possible.
|
||||
*/
|
||||
private void measureContent() {
|
||||
//If drawing to a path, we cannot measure intrinsic bounds
|
||||
//We must resly on setBounds being called externally
|
||||
if (mTextPath != null) {
|
||||
//Clear any previous measurement
|
||||
mTextLayout = null;
|
||||
mTextBounds.setEmpty();
|
||||
} else {
|
||||
//Measure text bounds
|
||||
double desired = Math.ceil(Layout.getDesiredWidth(mText, mTextPaint));
|
||||
|
||||
if (mDrawable != null) {
|
||||
desired = mDrawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
mTextLayout = new StaticLayout(mText, mTextPaint, (int) desired,
|
||||
mTextAlignment, 1.0f, 0.0f, false);
|
||||
|
||||
mTextBounds.set(0, 0, mTextLayout.getWidth(), mTextLayout.getHeight());
|
||||
}
|
||||
|
||||
//We may need to be redrawn
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to apply the correct text color based on the drawable's state
|
||||
*/
|
||||
private boolean updateTextColors(int[] stateSet) {
|
||||
int newColor = mTextColors.getColorForState(stateSet, Color.WHITE);
|
||||
if (mTextPaint.getColor() != newColor) {
|
||||
mTextPaint.setColor(newColor);
|
||||
|
||||
// fully transparent text
|
||||
mTextPaint.setAlpha(1);
|
||||
mTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
//Update the internal bounds in response to any external requests
|
||||
mTextBounds.set(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
/*
|
||||
* The drawable's ability to represent state is based on
|
||||
* the text color list set
|
||||
*/
|
||||
return mTextColors.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
//Upon state changes, grab the correct text color
|
||||
return updateTextColors(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
//Return the vertical bounds measured, or -1 if none
|
||||
if (mTextBounds.isEmpty()) {
|
||||
return -1;
|
||||
} else {
|
||||
return (mTextBounds.bottom - mTextBounds.top);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
//Return the horizontal bounds measured, or -1 if none
|
||||
if (mTextBounds.isEmpty()) {
|
||||
return -1;
|
||||
} else {
|
||||
return (mTextBounds.right - mTextBounds.left);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
final Rect bounds = getBounds();
|
||||
|
||||
final int count = canvas.save();
|
||||
//canvas.translate(bounds.left, bounds.top);
|
||||
|
||||
if (mDrawable != null) {
|
||||
// scale drawable to fit canvas
|
||||
Rect clipBounds = canvas.getClipBounds();
|
||||
mDrawable.setBounds(clipBounds);
|
||||
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
if (mTextPath == null) {
|
||||
//Allow the layout to draw the text
|
||||
|
||||
// Center text vertically!!
|
||||
canvas.translate((bounds.width() / 2f) - (mTextLayout.getWidth() / 2f), (bounds.height() / 2f) - ((mTextLayout.getHeight() / 2f)));
|
||||
|
||||
if (mTextSizeFactor > 0) {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, bounds.height() * mTextSizeFactor);
|
||||
}
|
||||
|
||||
mTextLayout.draw(canvas);
|
||||
|
||||
// Set text transparent
|
||||
//canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
|
||||
} else {
|
||||
//Draw directly on the canvas using the supplied path
|
||||
canvas.drawTextOnPath(mText.toString(), mTextPath, 0, 0, mTextPaint);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
if (mTextPaint.getAlpha() != alpha) {
|
||||
mTextPaint.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mTextPaint.getAlpha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
if (mTextPaint.getColorFilter() != cf) {
|
||||
mTextPaint.setColorFilter(cf);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,3 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Useful links:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/de47f1c358c8186ff3e14b887d5869f69b9a9d6c/core/java/com/android/internal/widget/DialogTitle.java
|
||||
// com.android.internal.widget.DialogTitle: https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/alert_dialog.xml
|
||||
@@ -21,20 +5,21 @@
|
||||
// <declare-styleable name="TextAppearance">: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/res/values/attrs.xml
|
||||
|
||||
|
||||
package com.liskovsoft.other.widgets;
|
||||
package com.liskovsoft.leankeyboard.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
/**
|
||||
* Used by dialogs to change the font size and number of lines to try to fit
|
||||
* the text to the available space.
|
||||
*/
|
||||
public class DialogTitle extends android.support.v7.widget.AppCompatTextView {
|
||||
public class DialogTitle extends AppCompatTextView {
|
||||
|
||||
public DialogTitle(Context context, AttributeSet attrs,
|
||||
int defStyle) {
|
||||
@@ -1,133 +0,0 @@
|
||||
package com.liskovsoft.other;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.CheckedTextView;
|
||||
import com.liskovsoft.keyboardaddons.KeyboardInfo;
|
||||
import com.liskovsoft.keyboardaddons.reskbdfactory.ResKeyboardInfo;
|
||||
import com.liskovsoft.leankeykeyboard.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseKeyboardDialog implements OnClickListener {
|
||||
private final View mInputView;
|
||||
private final Context mContext;
|
||||
private final List<KeyboardInfo> mInfos;
|
||||
private AlertDialog alertDialog;
|
||||
private ArrayList<CheckedTextView> mLangViews;
|
||||
|
||||
/**
|
||||
* Main constructor. Use it in most of the cases.
|
||||
* @param ctx context
|
||||
*/
|
||||
public ChooseKeyboardDialog(Context ctx) {
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special constructor. Useful when others didn't work. E.g. when running dialog within input method.
|
||||
* @param ctx context
|
||||
* @param inputView view to get token
|
||||
*/
|
||||
public ChooseKeyboardDialog(Context ctx, View inputView) {
|
||||
mContext = ctx;
|
||||
mInfos = ResKeyboardInfo.getAllKeyboardInfos(ctx);
|
||||
mInputView = inputView;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
showDialog();
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
private void showDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
View title = LayoutInflater.from(mContext).inflate(R.layout.lang_selection_dialog_title, null);
|
||||
alertDialog = builder
|
||||
//.setTitle(R.string.language_dialog_title)
|
||||
.setCustomTitle(title)
|
||||
.setView(buildView(builder.getContext()))
|
||||
.setOnDismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
ResKeyboardInfo.updateAllKeyboardInfos(mContext, mInfos);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
if (mInputView != null) {
|
||||
initDialog(); // prepare to run from IME
|
||||
}
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private void initDialog() {
|
||||
Window window = alertDialog.getWindow();
|
||||
WindowManager.LayoutParams lp = window.getAttributes();
|
||||
lp.token = mInputView.getWindowToken();
|
||||
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
|
||||
window.setAttributes(lp);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private View buildView(Context context) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View view = inflater.inflate(R.layout.lang_selection_dialog, null);
|
||||
ViewGroup root = view.findViewById(R.id.root);
|
||||
|
||||
TypedArray attributeArray = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.selectableItemBackground});
|
||||
int selectableItemBackgroundResourceId = attributeArray.getResourceId(0, 0);
|
||||
attributeArray.recycle();
|
||||
|
||||
mLangViews = new ArrayList<>();
|
||||
|
||||
for (KeyboardInfo info : mInfos) {
|
||||
CheckedTextView langView = (CheckedTextView) inflater.inflate(android.R.layout.simple_list_item_multiple_choice, root, false);
|
||||
langView.setBackgroundResource(selectableItemBackgroundResourceId);
|
||||
langView.setText(info.getLangName());
|
||||
|
||||
langView.setFocusable(true);
|
||||
langView.setTag(info); // our TAG
|
||||
langView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources().getDimension(R.dimen.text_size_dp));
|
||||
langView.setOnClickListener(this);
|
||||
mLangViews.add(langView);
|
||||
root.addView(langView);
|
||||
}
|
||||
|
||||
updateViews();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
for (CheckedTextView view : mLangViews) {
|
||||
KeyboardInfo kbd = (KeyboardInfo) view.getTag();
|
||||
if (kbd.isEnabled()) {
|
||||
view.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
KeyboardInfo kbd = (KeyboardInfo) view.getTag();
|
||||
// todo
|
||||
CheckedTextView checkedView = (CheckedTextView) view;
|
||||
boolean checked = checkedView.isChecked();
|
||||
kbd.setEnabled(!checked);
|
||||
checkedView.setChecked(!checked);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.liskovsoft.other;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.util.*;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package com.liskovsoft.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Helpers {
|
||||
/**
|
||||
* Simple wildcard matching routine. Implemented without regex. So you may expect huge performance boost.
|
||||
* @param host
|
||||
* @param mask
|
||||
* @return
|
||||
*/
|
||||
public static boolean matchSubstr(String host, String mask) {
|
||||
String[] sections = mask.split("\\*");
|
||||
String text = host;
|
||||
for (String section : sections) {
|
||||
int index = text.indexOf(section);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
text = text.substring(index + section.length());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean matchSubstrNoCase(String host, String mask) {
|
||||
return matchSubstr(host.toLowerCase(), mask.toLowerCase());
|
||||
}
|
||||
|
||||
public static InputStream getAsset(Context ctx, String fileName) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = ctx.getAssets().open(fileName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
public static String encodeURI(byte[] data) {
|
||||
try {
|
||||
// make behaviour of java uri-encode the same as javascript's one
|
||||
return URLEncoder.encode(new String(data, "UTF-8"), "UTF-8").replace("+", "%20");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean floatEquals(float num1, float num2) {
|
||||
float epsilon = 0.1f;
|
||||
return Math.abs(num1 - num2) < epsilon;
|
||||
}
|
||||
|
||||
public static String getDeviceName() {
|
||||
return String.format("%s (%s)", Build.MODEL, Build.PRODUCT);
|
||||
}
|
||||
|
||||
public static boolean deviceMatch(String[] devicesToProcess) {
|
||||
String thisDeviceName = Helpers.getDeviceName();
|
||||
for (String deviceName : devicesToProcess) {
|
||||
boolean match = matchSubstrNoCase(thisDeviceName, deviceName);
|
||||
if (match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String toString(InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
|
||||
String result = s.hasNext() ? s.next() : "";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static InputStream toStream(String content) {
|
||||
return new ByteArrayInputStream(content.getBytes(Charset.forName("UTF8")));
|
||||
}
|
||||
|
||||
public static void postOnUiThread(Runnable runnable) {
|
||||
new Handler(Looper.getMainLooper()).post(runnable);
|
||||
}
|
||||
|
||||
public static String unixToLocalDate(Context ctx, String timestamp) {
|
||||
Locale current = ctx.getResources().getConfiguration().locale;
|
||||
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, current);
|
||||
Date date;
|
||||
if (timestamp == null) {
|
||||
date = new Date();
|
||||
} else {
|
||||
date = new Date((long) Integer.parseInt(timestamp) * 1000);
|
||||
}
|
||||
return dateFormat.format(date);
|
||||
}
|
||||
|
||||
public static String runMultiMatcher(String input, String... patterns) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern regex;
|
||||
Matcher matcher;
|
||||
String result = null;
|
||||
for (String pattern : patterns) {
|
||||
regex = Pattern.compile(pattern);
|
||||
matcher = regex.matcher(input);
|
||||
|
||||
if (matcher.find()) {
|
||||
result = matcher.group(matcher.groupCount()); // get last group
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isCallable(Context ctx, Intent intent) {
|
||||
List<ResolveInfo> list = ctx.getPackageManager().queryIntentActivities(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
return list.size() > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.liskovsoft.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public final class LeanKeyPreferences {
|
||||
private static final String APP_RUN_ONCE = "appRunOnce";
|
||||
private static final String BOOTSTRAP_SELECTED_LANGUAGE = "bootstrapSelectedLanguage";
|
||||
private static final String APP_KEYBOARD_INDEX = "appKeyboardIndex";
|
||||
private static LeanKeyPreferences sInstance;
|
||||
private final Context mContext;
|
||||
private SharedPreferences mPrefs;
|
||||
|
||||
public static LeanKeyPreferences instance(Context ctx) {
|
||||
if (sInstance == null)
|
||||
sInstance = new LeanKeyPreferences(ctx);
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public LeanKeyPreferences(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
}
|
||||
|
||||
public boolean isRunOnce() {
|
||||
return mPrefs.getBoolean(APP_RUN_ONCE, false);
|
||||
}
|
||||
|
||||
public void setRunOnce(boolean runOnce) {
|
||||
mPrefs.edit()
|
||||
.putBoolean(APP_RUN_ONCE, runOnce)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void setPreferredLanguage(String name) {
|
||||
mPrefs.edit()
|
||||
.putString(BOOTSTRAP_SELECTED_LANGUAGE, name)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public String getPreferredLanguage() {
|
||||
String name = mPrefs.getString(BOOTSTRAP_SELECTED_LANGUAGE, "");
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getKeyboardIndex() {
|
||||
int idx = mPrefs.getInt(APP_KEYBOARD_INDEX, 0);
|
||||
return idx;
|
||||
}
|
||||
|
||||
public void setKeyboardIndex(int idx) {
|
||||
mPrefs.edit()
|
||||
.putInt(APP_KEYBOARD_INDEX, idx)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 874 B |
|
Before Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 748 B |
|
Before Width: | Height: | Size: 541 B |