diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2ae7bc12c3..05eb86c62b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="118"
android:versionName="2.14.4">
-
+
-
-
+
diff --git a/res/layout/conversation_bubble_incoming.xml b/res/layout/conversation_bubble_incoming.xml
index d9e3999c13..6e0b4f0c0a 100644
--- a/res/layout/conversation_bubble_incoming.xml
+++ b/res/layout/conversation_bubble_incoming.xml
@@ -42,7 +42,8 @@
android:layout_below="@id/thumbnail_container"
android:orientation="vertical">
-
-
-
+ android:layout_height="45dp"
+ app:pstsShouldExpand="true"
+ app:pstsTabPaddingLeftRight="@dimen/emoji_drawer_left_right_padding"
+ app:pstsUnderlineColor="@color/emoji_tab_underline"
+ app:pstsIndicatorColor="@color/emoji_tab_indicator"
+ app:pstsIndicatorHeight="@dimen/emoji_drawer_indicator_height"
+ app:pstsTextAllCaps="false"/>
-
+
\ No newline at end of file
diff --git a/res/layout/emoji_drawer_stub.xml b/res/layout/emoji_drawer_stub.xml
deleted file mode 100644
index 72bf99b4f7..0000000000
--- a/res/layout/emoji_drawer_stub.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index d4e510c54c..58e787b36b 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -113,5 +113,4 @@
-
diff --git a/res/values/emoji.xml b/res/values/emoji.xml
new file mode 100644
index 0000000000..855d533606
--- /dev/null
+++ b/res/values/emoji.xml
@@ -0,0 +1,867 @@
+
+
+
+ - 0x263a
+ - 0x1f60a
+ - 0x1f600
+ - 0x1f601
+ - 0x1f602
+ - 0x1f603
+ - 0x1f604
+ - 0x1f605
+ - 0x1f606
+ - 0x1f607
+ - 0x1f608
+ - 0x1f609
+ - 0x1f62f
+ - 0x1f610
+ - 0x1f611
+ - 0x1f615
+ - 0x1f620
+ - 0x1f62c
+ - 0x1f621
+ - 0x1f622
+ - 0x1f634
+ - 0x1f62e
+ - 0x1f623
+ - 0x1f624
+ - 0x1f625
+ - 0x1f626
+ - 0x1f627
+ - 0x1f628
+ - 0x1f629
+ - 0x1f630
+ - 0x1f61f
+ - 0x1f631
+ - 0x1f632
+ - 0x1f633
+ - 0x1f635
+ - 0x1f636
+ - 0x1f637
+ - 0x1f61e
+ - 0x1f612
+ - 0x1f60d
+ - 0x1f61b
+ - 0x1f61c
+ - 0x1f61d
+ - 0x1f60b
+ - 0x1f617
+ - 0x1f619
+ - 0x1f618
+ - 0x1f61a
+ - 0x1f60e
+ - 0x1f62d
+ - 0x1f60c
+ - 0x1f616
+ - 0x1f614
+ - 0x1f62a
+ - 0x1f60f
+ - 0x1f613
+ - 0x1f62b
+ - 0x1f64b
+ - 0x1f64c
+ - 0x1f64d
+ - 0x1f645
+ - 0x1f646
+ - 0x1f647
+ - 0x1f64e
+ - 0x1f64f
+ - 0x1f63a
+ - 0x1f63c
+ - 0x1f638
+ - 0x1f639
+ - 0x1f63b
+ - 0x1f63d
+ - 0x1f63f
+ - 0x1f63e
+ - 0x1f640
+ - 0x1f648
+ - 0x1f649
+ - 0x1f64a
+ - 0x1f4a9
+ - 0x1f476
+ - 0x1f466
+ - 0x1f467
+ - 0x1f468
+ - 0x1f469
+ - 0x1f474
+ - 0x1f475
+ - 0x1f48f
+ - 0x1f491
+ - 0x1f46a
+ - 0x1f46b
+ - 0x1f46c
+ - 0x1f46d
+ - 0x1f464
+ - 0x1f465
+ - 0x1f46e
+ - 0x1f477
+ - 0x1f481
+ - 0x1f482
+ - 0x1f46f
+ - 0x1f470
+ - 0x1f478
+ - 0x1f385
+ - 0x1f47c
+ - 0x1f471
+ - 0x1f472
+ - 0x1f473
+ - 0x1f483
+ - 0x1f486
+ - 0x1f487
+ - 0x1f485
+ - 0x1f47b
+ - 0x1f479
+ - 0x1f47a
+ - 0x1f47d
+ - 0x1f47e
+ - 0x1f47f
+ - 0x1f480
+ - 0x1f4aa
+ - 0x1f440
+ - 0x1f442
+ - 0x1f443
+ - 0x1f463
+ - 0x1f444
+ - 0x1f445
+ - 0x1f48b
+ - 0x2764
+ - 0x1f499
+ - 0x1f49a
+ - 0x1f49b
+ - 0x1f49c
+ - 0x1f493
+ - 0x1f494
+ - 0x1f495
+ - 0x1f496
+ - 0x1f497
+ - 0x1f498
+ - 0x1f49d
+ - 0x1f49e
+ - 0x1f49f
+ - 0x1f44d
+ - 0x1f44e
+ - 0x1f44c
+ - 0x270a
+ - 0x270c
+ - 0x270b
+ - 0x1f44a
+ - 0x261d
+ - 0x1f446
+ - 0x1f447
+ - 0x1f448
+ - 0x1f449
+ - 0x1f44b
+ - 0x1f44f
+ - 0x1f450
+
+
+
+ - 0x1f530
+ - 0x1f484
+ - 0x1f45e
+ - 0x1f45f
+ - 0x1f451
+ - 0x1f452
+ - 0x1f3a9
+ - 0x1f393
+ - 0x1f453
+ - 0x231a
+ - 0x1f454
+ - 0x1f455
+ - 0x1f456
+ - 0x1f457
+ - 0x1f458
+ - 0x1f459
+ - 0x1f460
+ - 0x1f461
+ - 0x1f462
+ - 0x1f45a
+ - 0x1f45c
+ - 0x1f4bc
+ - 0x1f392
+ - 0x1f45d
+ - 0x1f45b
+ - 0x1f4b0
+ - 0x1f4b3
+ - 0x1f4b2
+ - 0x1f4b5
+ - 0x1f4b4
+ - 0x1f4b6
+ - 0x1f4b7
+ - 0x1f4b8
+ - 0x1f4b1
+ - 0x1f4b9
+ - 0x1f52b
+ - 0x1f52a
+ - 0x1f4a3
+ - 0x1f489
+ - 0x1f48a
+ - 0x1f6ac
+ - 0x1f514
+ - 0x1f515
+ - 0x1f6aa
+ - 0x1f52c
+ - 0x1f52d
+ - 0x1f52e
+ - 0x1f526
+ - 0x1f50b
+ - 0x1f50c
+ - 0x1f4dc
+ - 0x1f4d7
+ - 0x1f4d8
+ - 0x1f4d9
+ - 0x1f4da
+ - 0x1f4d4
+ - 0x1f4d2
+ - 0x1f4d1
+ - 0x1f4d3
+ - 0x1f4d5
+ - 0x1f4d6
+ - 0x1f4f0
+ - 0x1f4db
+ - 0x1f383
+ - 0x1f384
+ - 0x1f380
+ - 0x1f381
+ - 0x1f382
+ - 0x1f388
+ - 0x1f386
+ - 0x1f387
+ - 0x1f389
+ - 0x1f38a
+ - 0x1f38d
+ - 0x1f38f
+ - 0x1f38c
+ - 0x1f390
+ - 0x1f38b
+ - 0x1f38e
+ - 0x1f4f1
+ - 0x1f4f2
+ - 0x1f4df
+ - 0x260e
+ - 0x1f4de
+ - 0x1f4e0
+ - 0x1f4e6
+ - 0x2709
+ - 0x1f4e8
+ - 0x1f4e9
+ - 0x1f4ea
+ - 0x1f4eb
+ - 0x1f4ed
+ - 0x1f4ec
+ - 0x1f4ee
+ - 0x1f4e4
+ - 0x1f4e5
+ - 0x1f4ef
+ - 0x1f4e2
+ - 0x1f4e3
+ - 0x1f4e1
+ - 0x1f4ac
+ - 0x1f4ad
+ - 0x2712
+ - 0x270f
+ - 0x1f4dd
+ - 0x1f4cf
+ - 0x1f4d0
+ - 0x1f4cd
+ - 0x1f4cc
+ - 0x1f4ce
+ - 0x2702
+ - 0x1f4ba
+ - 0x1f4bb
+ - 0x1f4bd
+ - 0x1f4be
+ - 0x1f4bf
+ - 0x1f4c6
+ - 0x1f4c5
+ - 0x1f4c7
+ - 0x1f4cb
+ - 0x1f4c1
+ - 0x1f4c2
+ - 0x1f4c3
+ - 0x1f4c4
+ - 0x1f4ca
+ - 0x1f4c8
+ - 0x1f4c9
+ - 0x26fa
+ - 0x1f3a1
+ - 0x1f3a2
+ - 0x1f3a0
+ - 0x1f3aa
+ - 0x1f3a8
+ - 0x1f3ac
+ - 0x1f3a5
+ - 0x1f4f7
+ - 0x1f4f9
+ - 0x1f3a6
+ - 0x1f3ad
+ - 0x1f3ab
+ - 0x1f3ae
+ - 0x1f3b2
+ - 0x1f3b0
+ - 0x1f0cf
+ - 0x1f3b4
+ - 0x1f004
+ - 0x1f3af
+ - 0x1f4fa
+ - 0x1f4fb
+ - 0x1f4c0
+ - 0x1f4fc
+ - 0x1f3a7
+ - 0x1f3a4
+ - 0x1f3b5
+ - 0x1f3b6
+ - 0x1f3bc
+ - 0x1f3bb
+ - 0x1f3b9
+ - 0x1f3b7
+ - 0x1f3ba
+ - 0x1f3b8
+ - 0x303d
+
+
+
+ - 0x1f415
+ - 0x1f436
+ - 0x1f429
+ - 0x1f408
+ - 0x1f431
+ - 0x1f400
+ - 0x1f401
+ - 0x1f42d
+ - 0x1f439
+ - 0x1f422
+ - 0x1f407
+ - 0x1f430
+ - 0x1f413
+ - 0x1f414
+ - 0x1f423
+ - 0x1f424
+ - 0x1f425
+ - 0x1f426
+ - 0x1f40f
+ - 0x1f411
+ - 0x1f410
+ - 0x1f43a
+ - 0x1f403
+ - 0x1f402
+ - 0x1f404
+ - 0x1f42e
+ - 0x1f434
+ - 0x1f417
+ - 0x1f416
+ - 0x1f437
+ - 0x1f43d
+ - 0x1f438
+ - 0x1f40d
+ - 0x1f43c
+ - 0x1f427
+ - 0x1f418
+ - 0x1f428
+ - 0x1f412
+ - 0x1f435
+ - 0x1f406
+ - 0x1f42f
+ - 0x1f43b
+ - 0x1f42b
+ - 0x1f42a
+ - 0x1f40a
+ - 0x1f433
+ - 0x1f40b
+ - 0x1f41f
+ - 0x1f420
+ - 0x1f421
+ - 0x1f419
+ - 0x1f41a
+ - 0x1f42c
+ - 0x1f40c
+ - 0x1f41b
+ - 0x1f41c
+ - 0x1f41d
+ - 0x1f41e
+ - 0x1f432
+ - 0x1f409
+ - 0x1f43e
+ - 0x1f378
+ - 0x1f37a
+ - 0x1f37b
+ - 0x1f377
+ - 0x1f379
+ - 0x1f376
+ - 0x2615
+ - 0x1f375
+ - 0x1f37c
+ - 0x1f374
+ - 0x1f368
+ - 0x1f367
+ - 0x1f366
+ - 0x1f369
+ - 0x1f370
+ - 0x1f36a
+ - 0x1f36b
+ - 0x1f36c
+ - 0x1f36d
+ - 0x1f36e
+ - 0x1f36f
+ - 0x1f373
+ - 0x1f354
+ - 0x1f35f
+ - 0x1f35d
+ - 0x1f355
+ - 0x1f356
+ - 0x1f357
+ - 0x1f364
+ - 0x1f363
+ - 0x1f371
+ - 0x1f35e
+ - 0x1f35c
+ - 0x1f359
+ - 0x1f35a
+ - 0x1f35b
+ - 0x1f372
+ - 0x1f365
+ - 0x1f362
+ - 0x1f361
+ - 0x1f358
+ - 0x1f360
+ - 0x1f34c
+ - 0x1f34e
+ - 0x1f34f
+ - 0x1f34a
+ - 0x1f34b
+ - 0x1f344
+ - 0x1f345
+ - 0x1f346
+ - 0x1f347
+ - 0x1f348
+ - 0x1f349
+ - 0x1f350
+ - 0x1f351
+ - 0x1f352
+ - 0x1f353
+ - 0x1f34d
+ - 0x1f330
+ - 0x1f331
+ - 0x1f332
+ - 0x1f333
+ - 0x1f334
+ - 0x1f335
+ - 0x1f337
+ - 0x1f338
+ - 0x1f339
+ - 0x1f340
+ - 0x1f341
+ - 0x1f342
+ - 0x1f343
+ - 0x1f33a
+ - 0x1f33b
+ - 0x1f33c
+ - 0x1f33d
+ - 0x1f33e
+ - 0x1f33f
+ - 0x2600
+ - 0x1f308
+ - 0x26c5
+ - 0x2601
+ - 0x1f301
+ - 0x1f302
+ - 0x2614
+ - 0x1f4a7
+ - 0x26a1
+ - 0x1f300
+ - 0x2744
+ - 0x26c4
+ - 0x1f319
+ - 0x1f31e
+ - 0x1f31d
+ - 0x1f31a
+ - 0x1f31b
+ - 0x1f31c
+ - 0x1f311
+ - 0x1f312
+ - 0x1f313
+ - 0x1f314
+ - 0x1f315
+ - 0x1f316
+ - 0x1f317
+ - 0x1f318
+ - 0x1f391
+ - 0x1f304
+ - 0x1f305
+ - 0x1f307
+ - 0x1f306
+ - 0x1f303
+ - 0x1f30c
+ - 0x1f309
+ - 0x1f30a
+ - 0x1f30b
+ - 0x1f30e
+ - 0x1f30f
+ - 0x1f30d
+ - 0x1f310
+
+
+
+ - 0x1f3e0
+ - 0x1f3e1
+ - 0x1f3e2
+ - 0x1f3e3
+ - 0x1f3e4
+ - 0x1f3e5
+ - 0x1f3e6
+ - 0x1f3e7
+ - 0x1f3e8
+ - 0x1f3e9
+ - 0x1f3ea
+ - 0x1f3eb
+ - 0x26ea
+ - 0x26f2
+ - 0x1f3ec
+ - 0x1f3ef
+ - 0x1f3f0
+ - 0x1f3ed
+ - 0x1f5fb
+ - 0x1f5fc
+ - 0x1f5fd
+ - 0x1f5fe
+ - 0x1f5ff
+ - 0x2693
+ - 0x1f3ee
+ - 0x1f488
+ - 0x1f527
+ - 0x1f528
+ - 0x1f529
+ - 0x1f6bf
+ - 0x1f6c1
+ - 0x1f6c0
+ - 0x1f6bd
+ - 0x1f6be
+ - 0x1f3bd
+ - 0x1f3a3
+ - 0x1f3b1
+ - 0x1f3b3
+ - 0x26be
+ - 0x26f3
+ - 0x1f3be
+ - 0x26bd
+ - 0x1f3bf
+ - 0x1f3c0
+ - 0x1f3c1
+ - 0x1f3c2
+ - 0x1f3c3
+ - 0x1f3c4
+ - 0x1f3c6
+ - 0x1f3c7
+ - 0x1f40e
+ - 0x1f3c8
+ - 0x1f3c9
+ - 0x1f3ca
+ - 0x1f682
+ - 0x1f683
+ - 0x1f684
+ - 0x1f685
+ - 0x1f686
+ - 0x1f687
+ - 0x24c2
+ - 0x1f688
+ - 0x1f68a
+ - 0x1f68b
+ - 0x1f68c
+ - 0x1f68d
+ - 0x1f68e
+ - 0x1f68f
+ - 0x1f690
+ - 0x1f691
+ - 0x1f692
+ - 0x1f693
+ - 0x1f694
+ - 0x1f695
+ - 0x1f696
+ - 0x1f697
+ - 0x1f698
+ - 0x1f699
+ - 0x1f69a
+ - 0x1f69b
+ - 0x1f69c
+ - 0x1f69d
+ - 0x1f69e
+ - 0x1f69f
+ - 0x1f6a0
+ - 0x1f6a1
+ - 0x1f6a2
+ - 0x1f6a3
+ - 0x1f681
+ - 0x2708
+ - 0x1f6c2
+ - 0x1f6c3
+ - 0x1f6c4
+ - 0x1f6c5
+ - 0x26f5
+ - 0x1f6b2
+ - 0x1f6b3
+ - 0x1f6b4
+ - 0x1f6b5
+ - 0x1f6b7
+ - 0x1f6b8
+ - 0x1f689
+ - 0x1f680
+ - 0x1f6a4
+ - 0x1f6b6
+ - 0x26fd
+ - 0x1f17f
+ - 0x1f6a5
+ - 0x1f6a6
+ - 0x1f6a7
+ - 0x1f6a8
+ - 0x2668
+ - 0x1f48c
+ - 0x1f48d
+ - 0x1f48e
+ - 0x1f490
+ - 0x1f492
+ - 0xfe4e5
+ - 0xfe4e6
+ - 0xfe4e7
+ - 0xfe4e8
+ - 0xfe4e9
+ - 0xfe4ea
+ - 0xfe4eb
+ - 0xfe4ec
+ - 0xfe4ed
+ - 0xfe4ee
+
+
+
+ - 0x1f51d
+ - 0x1f519
+ - 0x1f51b
+ - 0x1f51c
+ - 0x1f51a
+ - 0x23f3
+ - 0x231b
+ - 0x23f0
+ - 0x2648
+ - 0x2649
+ - 0x264a
+ - 0x264b
+ - 0x264c
+ - 0x264d
+ - 0x264e
+ - 0x264f
+ - 0x2650
+ - 0x2651
+ - 0x2652
+ - 0x2653
+ - 0x26ce
+ - 0x1f531
+ - 0x1f52f
+ - 0x1f6bb
+ - 0x1f6ae
+ - 0x1f6af
+ - 0x1f6b0
+ - 0x1f6b1
+ - 0x1f170
+ - 0x1f171
+ - 0x1f18e
+ - 0x1f17e
+ - 0x1f4ae
+ - 0x1f4af
+ - 0x1f520
+ - 0x1f521
+ - 0x1f522
+ - 0x1f523
+ - 0x1f524
+ - 0x27bf
+ - 0x1f4f6
+ - 0x1f4f3
+ - 0x1f4f4
+ - 0x1f4f5
+ - 0x1f6b9
+ - 0x1f6ba
+ - 0x1f6bc
+ - 0x267f
+ - 0x267b
+ - 0x1f6ad
+ - 0x1f6a9
+ - 0x26a0
+ - 0x1f201
+ - 0x1f51e
+ - 0x26d4
+ - 0x1f192
+ - 0x1f197
+ - 0x1f195
+ - 0x1f198
+ - 0x1f199
+ - 0x1f193
+ - 0x1f196
+ - 0x1f19a
+ - 0x1f232
+ - 0x1f233
+ - 0x1f234
+ - 0x1f235
+ - 0x1f236
+ - 0x1f237
+ - 0x1f238
+ - 0x1f239
+ - 0x1f202
+ - 0x1f23a
+ - 0x1f250
+ - 0x1f251
+ - 0x3299
+ - 0x00ae
+ - 0x00a9
+ - 0x2122
+ - 0x1f21a
+ - 0x1f22f
+ - 0x3297
+ - 0x2b55
+ - 0x274c
+ - 0x274e
+ - 0x2139
+ - 0x1f6ab
+ - 0x2705
+ - 0x2714
+ - 0x1f517
+ - 0x2734
+ - 0x2733
+ - 0x2795
+ - 0x2796
+ - 0x2716
+ - 0x2797
+ - 0x1f4a0
+ - 0x1f4a1
+ - 0x1f4a4
+ - 0x1f4a2
+ - 0x1f525
+ - 0x1f4a5
+ - 0x1f4a8
+ - 0x1f4a6
+ - 0x1f4ab
+ - 0x1f55b
+ - 0x1f567
+ - 0x1f550
+ - 0x1f55c
+ - 0x1f551
+ - 0x1f55d
+ - 0x1f552
+ - 0x1f55e
+ - 0x1f553
+ - 0x1f55f
+ - 0x1f554
+ - 0x1f560
+ - 0x1f555
+ - 0x1f561
+ - 0x1f556
+ - 0x1f562
+ - 0x1f557
+ - 0x1f563
+ - 0x1f558
+ - 0x1f564
+ - 0x1f559
+ - 0x1f565
+ - 0x1f55a
+ - 0x1f566
+ - 0x2195
+ - 0x2b06
+ - 0x2197
+ - 0x27a1
+ - 0x2198
+ - 0x2b07
+ - 0x2199
+ - 0x2b05
+ - 0x2196
+ - 0x2194
+ - 0x2934
+ - 0x2935
+ - 0x23ea
+ - 0x23eb
+ - 0x23ec
+ - 0x23e9
+ - 0x25c0
+ - 0x25b6
+ - 0x1f53d
+ - 0x1f53c
+ - 0x2747
+ - 0x2728
+ - 0x1f534
+ - 0x1f535
+ - 0x26aa
+ - 0x26ab
+ - 0x1f533
+ - 0x1f532
+ - 0x2b50
+ - 0x1f31f
+ - 0x1f320
+ - 0x25ab
+ - 0x25aa
+ - 0x25fd
+ - 0x25fe
+ - 0x25fb
+ - 0x25fc
+ - 0x2b1c
+ - 0x2b1b
+ - 0x1f538
+ - 0x1f539
+ - 0x1f536
+ - 0x1f537
+ - 0x1f53a
+ - 0x1f53b
+ - 0x1f51f
+
+ - 0x2754
+ - 0x2753
+ - 0x2755
+ - 0x2757
+ - 0x203c
+ - 0x2049
+ - 0x3030
+ - 0x27b0
+ - 0x2660
+ - 0x2665
+ - 0x2663
+ - 0x2666
+ - 0x1f194
+ - 0x1f511
+ - 0x21a9
+ - 0x1f191
+ - 0x1f50d
+ - 0x1f512
+ - 0x1f513
+ - 0x21aa
+ - 0x1f510
+ - 0x2611
+ - 0x1f518
+ - 0x1f50e
+ - 0x1f516
+ - 0x1f50f
+ - 0x1f503
+ - 0x1f500
+ - 0x1f501
+ - 0x1f502
+ - 0x1f504
+ - 0x1f4e7
+ - 0x1f505
+ - 0x1f506
+ - 0x1f507
+ - 0x1f508
+ - 0x1f509
+ - 0x1f50a
+
+
+
+ - @array/emoji_smile
+ - @array/emoji_flower
+ - @array/emoji_bell
+ - @array/emoji_car
+ - @array/emoji_symbol
+
+
+
+ - @drawable/emoji_category_smile
+ - @drawable/emoji_category_flower
+ - @drawable/emoji_category_bell
+ - @drawable/emoji_category_car
+ - @drawable/emoji_category_symbol
+
+
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index ad9c0010bf..d656d9ad86 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -42,7 +42,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
-import android.view.ViewStub;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
@@ -53,8 +52,9 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.ComposeText;
-import org.thoughtcrime.securesms.components.EmojiDrawer;
-import org.thoughtcrime.securesms.components.EmojiToggle;
+import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
+import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
+import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
@@ -93,7 +93,6 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@@ -323,8 +322,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onBackPressed() {
- if (emojiDrawer.isPresent() && emojiDrawer.get().getVisibility() == View.VISIBLE) {
- emojiDrawer.get().setVisibility(View.GONE);
+ if (isEmojiDrawerOpen()) {
+ getEmojiDrawer().hide();
emojiToggle.toggle();
} else {
super.onBackPressed();
@@ -621,15 +620,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onPostExecute(List drafts) {
- boolean nativeEmojiSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
- Context context = ConversationActivity.this;
-
for (Draft draft : drafts) {
- if (draft.getType().equals(Draft.TEXT) && !nativeEmojiSupported) {
- composeText.setText(Emoji.getInstance(context).emojify(draft.getValue(),
- new Emoji.InvalidatingPageLoadedListener(composeText)),
- TextView.BufferType.SPANNABLE);
- } else if (draft.getType().equals(Draft.TEXT)) {
+ if (draft.getType().equals(Draft.TEXT)) {
composeText.setText(draft.getValue());
} else if (draft.getType().equals(Draft.IMAGE)) {
addAttachmentImage(Uri.parse(draft.getValue()));
@@ -713,10 +705,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiToggle.setOnClickListener(new EmojiToggleListener());
}
- private EmojiDrawer initializeEmojiDrawer() {
- EmojiDrawer emojiDrawer = (EmojiDrawer)((ViewStub)findViewById(R.id.emoji_drawer_stub)).inflate();
- emojiDrawer.setComposeEditText(composeText);
- return emojiDrawer;
+ private EmojiDrawer getEmojiDrawer() {
+ if (emojiDrawer.isPresent()) return emojiDrawer.get();
+
+ EmojiDrawer emojiDrawerFragment = EmojiDrawer.newInstance();
+ emojiDrawerFragment.setComposeEditText(composeText);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.emoji_drawer, emojiDrawerFragment)
+ .commit();
+ getSupportFragmentManager().executePendingTransactions();
+ emojiDrawer = Optional.of(emojiDrawerFragment);
+ return emojiDrawerFragment;
+ }
+
+ private boolean isEmojiDrawerOpen() {
+ return emojiDrawer.isPresent() && emojiDrawer.get().isOpen();
}
private void initializeResources() {
@@ -1094,16 +1097,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onClick(View v) {
InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- if (emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
+ if (isEmojiDrawerOpen()) {
input.showSoftInput(composeText, 0);
- emojiDrawer.get().hide();
+ getEmojiDrawer().hide();
} else {
- if (!emojiDrawer.isPresent()) {
- emojiDrawer = Optional.of(initializeEmojiDrawer());
- }
input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
- emojiDrawer.get().show();
+ getEmojiDrawer().show();
}
}
}
@@ -1141,7 +1141,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View v) {
- if (emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
+ if (isEmojiDrawerOpen()) {
emojiToggle.performClick();
}
}
@@ -1157,7 +1157,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus && emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
+ if (hasFocus && isEmojiDrawerOpen()) {
emojiToggle.performClick();
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index a413c17a51..188ddeca5e 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -53,7 +53,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
-import org.thoughtcrime.securesms.util.Emoji;
import java.util.Locale;
import java.util.Set;
@@ -239,9 +238,7 @@ public class ConversationItem extends LinearLayout {
if (isCaptionlessMms(messageRecord)) {
bodyText.setVisibility(View.GONE);
} else {
- bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(),
- new Emoji.InvalidatingPageLoadedListener(bodyText)),
- TextView.BufferType.SPANNABLE);
+ bodyText.setText(messageRecord.getDisplayBody());
bodyText.setVisibility(View.VISIBLE);
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java
index ff3b9664a8..5840cbc514 100644
--- a/src/org/thoughtcrime/securesms/ConversationListItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationListItem.java
@@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
-import org.thoughtcrime.securesms.util.Emoji;
import java.util.Locale;
import java.util.Set;
@@ -96,10 +95,7 @@ public class ConversationListItem extends RelativeLayout
this.recipients.addListener(this);
this.fromView.setText(recipients, read);
- this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
- Emoji.EMOJI_SMALL,
- new Emoji.InvalidatingPageLoadedListener(subjectView)),
- TextView.BufferType.SPANNABLE);
+ this.subjectView.setText(thread.getDisplayBody());
this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
if (thread.getDate() > 0) {
diff --git a/src/org/thoughtcrime/securesms/components/ComposeText.java b/src/org/thoughtcrime/securesms/components/ComposeText.java
index f72485423b..0099ccb734 100644
--- a/src/org/thoughtcrime/securesms/components/ComposeText.java
+++ b/src/org/thoughtcrime/securesms/components/ComposeText.java
@@ -9,7 +9,7 @@ import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
-public class ComposeText extends AppCompatEditText {
+public class ComposeText extends EmojiEditText {
public ComposeText(Context context) {
super(context);
}
diff --git a/src/org/thoughtcrime/securesms/components/EmojiDrawer.java b/src/org/thoughtcrime/securesms/components/EmojiDrawer.java
deleted file mode 100644
index fa2af88520..0000000000
--- a/src/org/thoughtcrime/securesms/components/EmojiDrawer.java
+++ /dev/null
@@ -1,273 +0,0 @@
-package org.thoughtcrime.securesms.components;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION_CODES;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-
-import com.astuetz.PagerSlidingTabStrip;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
-import org.thoughtcrime.securesms.util.Emoji;
-
-public class EmojiDrawer extends KeyboardAwareLinearLayout {
- private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
-
- private static final int RECENT_TYPE = 0;
- private static final int ALL_TYPE = 1;
-
-
- private FrameLayout[] gridLayouts = new FrameLayout[Emoji.PAGES.length+1];
- private EditText composeText;
- private Emoji emoji;
- private ViewPager pager;
- private PagerSlidingTabStrip strip;
-
- @SuppressWarnings("unused")
- public EmojiDrawer(Context context) {
- super(context);
- initialize();
- }
-
- @SuppressWarnings("unused")
- public EmojiDrawer(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- @SuppressWarnings("unused")
- @TargetApi(VERSION_CODES.HONEYCOMB)
- public EmojiDrawer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize();
- }
-
- public void setComposeEditText(EditText composeText) {
- this.composeText = composeText;
- }
-
- public boolean isOpen() {
- return getVisibility() == View.VISIBLE;
- }
-
- private void initialize() {
- LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.emoji_drawer, this, true);
-
- initializeResources();
-
- if (!this.isInEditMode()) {
- initializeEmojiGrid();
- }
- }
-
- private void initializeResources() {
- this.pager = (ViewPager) findViewById(R.id.emoji_pager);
- this.strip = (PagerSlidingTabStrip) findViewById(R.id.tabs);
- this.emoji = Emoji.getInstance(getContext());
-
- RepeatableImageKey backspace = (RepeatableImageKey)findViewById(R.id.backspace);
- backspace.setOnKeyEventListener(new KeyEventListener() {
- @Override public void onKeyEvent() {
- if (composeText != null && composeText.getText().length() > 0) {
- composeText.dispatchKeyEvent(DELETE_KEY_EVENT);
- }
- }
- });
- }
-
- public void hide() {
- setVisibility(View.GONE);
- }
-
- public void show() {
- int keyboardHeight = getKeyboardHeight();
- Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight);
- setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight));
- requestLayout();
- setVisibility(View.VISIBLE);
- }
-
- private void initializeEmojiGrid() {
- LayoutInflater inflater = LayoutInflater.from(getContext());
- for (int i = 0; i < gridLayouts.length; i++) {
- gridLayouts[i] = (FrameLayout) inflater.inflate(R.layout.emoji_grid_layout, pager, false);
- final GridView gridView = (GridView) gridLayouts[i].findViewById(R.id.emoji);
- gridLayouts[i].setTag(gridView);
- final int type = (i == 0 ? RECENT_TYPE : ALL_TYPE);
- gridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2*getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
- gridView.setAdapter(new EmojiGridAdapter(type, i-1));
- gridView.setOnItemClickListener(new EmojiClickListener(ALL_TYPE));
- }
-
- pager.setAdapter(new EmojiPagerAdapter());
-
- if (emoji.getRecentlyUsedAssetCount() <= 0) {
- pager.setCurrentItem(1);
- }
- strip.setTabPaddingLeftRight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_left_right_padding));
- strip.setAllCaps(false);
- strip.setShouldExpand(true);
- strip.setUnderlineColorResource(R.color.emoji_tab_underline);
- strip.setIndicatorColorResource(R.color.emoji_tab_indicator);
- strip.setIndicatorHeight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_indicator_height));
-
- strip.setViewPager(pager);
- }
-
- private class EmojiClickListener implements AdapterView.OnItemClickListener {
-
- private final int type;
-
- public EmojiClickListener(int type) {
- this.type = type;
- }
-
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- Integer unicodePoint = (Integer) view.getTag();
- insertEmoji(composeText, unicodePoint);
- if (type != RECENT_TYPE) {
- emoji.setRecentlyUsed(Integer.toHexString(unicodePoint));
- ((BaseAdapter)((GridView)gridLayouts[0].getTag()).getAdapter()).notifyDataSetChanged();
- }
- }
-
- private void insertEmoji(EditText editText, Integer unicodePoint) {
- final char[] chars = Character.toChars(unicodePoint);
- String characters = new String(chars);
- int start = editText.getSelectionStart();
- int end = editText.getSelectionEnd();
-
- CharSequence text = emoji.emojify(characters, new Emoji.InvalidatingPageLoadedListener(composeText));
- editText.getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length());
-
- editText.setSelection(end+chars.length);
- }
- }
-
-
-
- private class EmojiGridAdapter extends BaseAdapter {
-
- private final int type;
- private final int page;
- private final int emojiSize;
-
- public EmojiGridAdapter(int type, int page) {
- this.type = type;
- this.page = page;
- emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size);
- }
-
- @Override
- public int getCount() {
- if (type == RECENT_TYPE) return emoji.getRecentlyUsedAssetCount();
- else return Emoji.PAGES[page].length;
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final ImageView view;
- final int pad = getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
- if (convertView != null && convertView instanceof ImageView) {
- view = (ImageView) convertView;
- } else {
- ImageView imageView = new ImageView(getContext());
- imageView.setPadding(pad, pad, pad, pad);
- imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2*pad, emojiSize + 2*pad));
- view = imageView;
- }
-
- final Drawable drawable;
- final Integer unicodeTag;
- if (type == ALL_TYPE) {
- unicodeTag = Emoji.PAGES[page][position];
- drawable = emoji.getEmojiDrawable(new Emoji.DrawInfo(page, position),
- Emoji.EMOJI_HUGE,
- new Emoji.InvalidatingPageLoadedListener(view));
- } else {
- Pair recentlyUsed = emoji.getRecentlyUsed(position,
- Emoji.EMOJI_HUGE,
- new Emoji.InvalidatingPageLoadedListener(view));
- unicodeTag = recentlyUsed.first;
- drawable = recentlyUsed.second;
- }
-
- view.setImageDrawable(drawable);
- view.setPadding(pad, pad, pad, pad);
- view.setTag(unicodeTag);
- return view;
- }
- }
-
- private class EmojiPagerAdapter extends PagerAdapter implements PagerSlidingTabStrip.IconTabProvider {
-
- @Override
- public int getCount() {
- return gridLayouts.length;
- }
-
- @Override
- public boolean isViewFromObject(View view, Object o) {
- return view == o;
- }
-
- public Object instantiateItem(ViewGroup container, int position) {
- if (position < 0 || position >= gridLayouts.length)
- throw new AssertionError("position out of range!");
-
- container.addView(gridLayouts[position], 0);
-
- return gridLayouts[position];
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- Log.w("EmojiDrawer", "destroying item at " + position);
- container.removeView(gridLayouts[position]);
- }
-
- @Override
- public int getPageIconResId(int i) {
- switch (i) {
- case 0: return R.drawable.emoji_category_recent;
- case 1: return R.drawable.emoji_category_smile;
- case 2: return R.drawable.emoji_category_flower;
- case 3: return R.drawable.emoji_category_bell;
- case 4: return R.drawable.emoji_category_car;
- case 5: return R.drawable.emoji_category_symbol;
- default: return 0;
- }
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java
new file mode 100644
index 0000000000..40bd7945c5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java
@@ -0,0 +1,156 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.astuetz.PagerSlidingTabStrip;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
+import org.thoughtcrime.securesms.components.RepeatableImageKey;
+import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
+import org.thoughtcrime.securesms.components.emoji.EmojiPageFragment.EmojiSelectionListener;
+import org.thoughtcrime.securesms.util.ResUtil;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class EmojiDrawer extends Fragment {
+ private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
+
+ private EmojiEditText composeText;
+ private KeyboardAwareLinearLayout container;
+ private ViewPager pager;
+ private PagerSlidingTabStrip strip;
+
+ public static EmojiDrawer newInstance(@ArrayRes int categories, @ArrayRes int icons) {
+ final EmojiDrawer fragment = new EmojiDrawer();
+ final Bundle args = new Bundle();
+ args.putInt("categories", categories);
+ args.putInt("icons", icons);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public static EmojiDrawer newInstance() {
+ return newInstance(R.array.emoji_categories, R.array.emoji_category_icons);
+ }
+
+ public void setComposeEditText(EmojiEditText composeText) {
+ this.composeText = composeText;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View v = inflater.inflate(R.layout.emoji_drawer, container, false);
+ initializeResources(v);
+ initializeEmojiGrid();
+ return v;
+ }
+
+ private void initializeResources(View v) {
+ Log.w("EmojiDrawer", "initializeResources()");
+ this.container = (KeyboardAwareLinearLayout) v.findViewById(R.id.container);
+ this.pager = (ViewPager) v.findViewById(R.id.emoji_pager);
+ this.strip = (PagerSlidingTabStrip) v.findViewById(R.id.tabs);
+
+ RepeatableImageKey backspace = (RepeatableImageKey)v.findViewById(R.id.backspace);
+ backspace.setOnKeyEventListener(new KeyEventListener() {
+ @Override public void onKeyEvent() {
+ if (composeText != null && composeText.getText().length() > 0) {
+ composeText.dispatchKeyEvent(DELETE_KEY_EVENT);
+ }
+ }
+ });
+ }
+
+ public void hide() {
+ container.setVisibility(View.GONE);
+ }
+
+ public void show() {
+ int keyboardHeight = container.getKeyboardHeight();
+ Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight);
+ container.setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight));
+ container.requestLayout();
+ container.setVisibility(View.VISIBLE);
+ }
+
+ public boolean isOpen() {
+ return container.getVisibility() == View.VISIBLE;
+ }
+
+ private void initializeEmojiGrid() {
+
+ pager.setAdapter(new EmojiPagerAdapter(getActivity(),
+ getFragmentManager(),
+ getPageModels(getArguments().getInt("categories"),
+ getArguments().getInt("icons")),
+ new EmojiSelectionListener() {
+ @Override public void onEmojiSelected(int emojiCode) {
+ composeText.insertEmoji(emojiCode);
+ }
+ }));
+ strip.setViewPager(pager);
+ }
+
+ private List getPageModels(@ArrayRes int pagesRes, @ArrayRes int iconsRes) {
+ final int[] icons = ResUtil.getResourceIds(getActivity(), iconsRes);
+ final int[] pages = ResUtil.getResourceIds(getActivity(), pagesRes);
+ final List models = new LinkedList<>();
+ models.add(new RecentEmojiPageModel(getActivity()));
+ for (int i = 0; i < icons.length; i++) {
+ models.add(new StaticEmojiPageModel(icons[i], getResources().getIntArray(pages[i])));
+ }
+ return models;
+ }
+
+ public static class EmojiPagerAdapter extends FragmentStatePagerAdapter
+ implements PagerSlidingTabStrip.CustomTabProvider
+ {
+ private Context context;
+ private List pages;
+ private EmojiSelectionListener listener;
+
+ public EmojiPagerAdapter(@NonNull Context context,
+ @NonNull FragmentManager fm,
+ @NonNull List pages,
+ @Nullable EmojiSelectionListener listener)
+ {
+ super(fm);
+ this.context = context;
+ this.pages = pages;
+ this.listener = listener;
+ }
+
+ @Override
+ public int getCount() {
+ return pages.size();
+ }
+
+ @Override public Fragment getItem(int i) {
+ return EmojiPageFragment.newInstance(pages.get(i), listener);
+ }
+
+ @Override public View getCustomTabView(ViewGroup viewGroup, int i) {
+ ImageView image = new ImageView(context);
+ image.setImageResource(pages.get(i).getIconRes());
+ return image;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java
new file mode 100644
index 0000000000..5a7494f3e2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java
@@ -0,0 +1,38 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatEditText;
+import android.util.AttributeSet;
+
+public class EmojiEditText extends AppCompatEditText {
+ public EmojiEditText(Context context) {
+ super(context);
+ init();
+ }
+
+ public EmojiEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EmojiEditText(Context context, AttributeSet attrs,
+ int defStyleAttr)
+ {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ setTransformationMethod(new EmojiTransformationMethod());
+ }
+
+ public void insertEmoji(int codePoint) {
+ final char[] chars = Character.toChars(codePoint);
+ final String text = new String(chars);
+ final int start = getSelectionStart();
+ final int end = getSelectionEnd();
+
+ getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length());
+ setSelection(end + chars.length);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiPageFragment.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageFragment.java
new file mode 100644
index 0000000000..c9e7f33d39
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageFragment.java
@@ -0,0 +1,116 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
+
+public class EmojiPageFragment extends Fragment {
+ private static final String TAG = EmojiPageFragment.class.getSimpleName();
+
+ private EmojiPageModel model;
+ private EmojiSelectionListener listener;
+
+ public static EmojiPageFragment newInstance(@NonNull EmojiPageModel model,
+ @Nullable EmojiSelectionListener listener)
+ {
+ EmojiPageFragment fragment = new EmojiPageFragment();
+ fragment.setModel(model);
+ fragment.setEmojiSelectedListener(listener);
+ return fragment;
+ }
+
+ @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState)
+ {
+ final View view = inflater.inflate(R.layout.emoji_grid_layout, container, false);
+ final GridView grid = (GridView) view.findViewById(R.id.emoji);
+ grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
+ grid.setOnItemClickListener(new OnItemClickListener() {
+ @Override public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ model.onCodePointSelected((Integer)view.getTag());
+ if (listener != null) listener.onEmojiSelected((Integer)view.getTag());
+ }
+ });
+ grid.setAdapter(new EmojiGridAdapter(getActivity(), model));
+ return view;
+ }
+
+ public void setModel(EmojiPageModel model) {
+ this.model = model;
+ }
+
+ public void setEmojiSelectedListener(EmojiSelectionListener listener) {
+ this.listener = listener;
+ }
+
+ private static class EmojiGridAdapter extends BaseAdapter {
+
+ protected final Context context;
+ private final int emojiSize;
+ private final EmojiPageModel model;
+
+ public EmojiGridAdapter(Context context, EmojiPageModel model) {
+ this.context = context;
+ this.emojiSize = (int) context.getResources().getDimension(R.dimen.emoji_drawer_size);
+ this.model = model;
+ }
+
+ @Override public int getCount() {
+ return model.getCodePoints().length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ final ImageView view;
+ final int pad = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
+ if (convertView != null && convertView instanceof ImageView) {
+ view = (ImageView)convertView;
+ } else {
+ ImageView imageView = new ImageView(context);
+ imageView.setPadding(pad, pad, pad, pad);
+ imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
+ view = imageView;
+ }
+
+ final Integer unicodeTag = model.getCodePoints()[position];
+ final EmojiProvider provider = EmojiProvider.getInstance(context);
+ final Drawable drawable = provider.getEmojiDrawable(unicodeTag,
+ EmojiProvider.EMOJI_HUGE,
+ new InvalidatingPageLoadedListener(view));
+
+ view.setImageDrawable(drawable);
+ view.setPadding(pad, pad, pad, pad);
+ view.setTag(unicodeTag);
+ return view;
+ }
+ }
+
+ public interface EmojiSelectionListener {
+ void onEmojiSelected(int emojiCode);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java
new file mode 100644
index 0000000000..efe0b4eee3
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiPageModel.java
@@ -0,0 +1,7 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+public interface EmojiPageModel {
+ int getIconRes();
+ int[] getCodePoints();
+ void onCodePointSelected(int codePoint);
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
new file mode 100644
index 0000000000..f0982728c9
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java
@@ -0,0 +1,257 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.BitmapDecodingException;
+import org.thoughtcrime.securesms.util.BitmapUtil;
+import org.thoughtcrime.securesms.util.ResUtil;
+import org.thoughtcrime.securesms.util.Util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EmojiProvider {
+ private static final String TAG = EmojiProvider.class.getSimpleName();
+ private static final ExecutorService executor = Util.newSingleThreadedLifoExecutor();
+ private static volatile EmojiProvider instance = null;
+ private static final SparseArray> bitmaps = new SparseArray<>();
+
+ private final SparseArray offsets = new SparseArray<>();
+
+ @SuppressWarnings("MalformedRegex")
+ // 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
+ // |==== misc ====||======== emoticons ========||========= flags ==========|
+ private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
+
+ public static final double EMOJI_HUGE = 1.00;
+ public static final double EMOJI_LARGE = 0.75;
+ public static final double EMOJI_SMALL = 0.60;
+ public static final int EMOJI_RAW_SIZE = 128;
+ public static final int EMOJI_PER_ROW = 16;
+
+ private final Context context;
+ private final int bigDrawSize;
+ private final int[] pages;
+
+ public static EmojiProvider getInstance(Context context) {
+ if (instance == null) {
+ synchronized (EmojiProvider.class) {
+ if (instance == null) {
+ instance = new EmojiProvider(context);
+ }
+ }
+ }
+ return instance;
+ }
+
+ private EmojiProvider(Context context) {
+ this.context = context.getApplicationContext();
+ this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
+ this.pages = ResUtil.getResourceIds(context, R.array.emoji_categories);
+ for (int i = 0; i < pages.length; i++) {
+ final int[] page = context.getResources().getIntArray(pages[i]);
+ for (int j = 0; j < page.length; j++) {
+ offsets.put(page[j], new DrawInfo(i, j));
+ }
+ }
+ }
+
+ private void preloadPage(final int page, final PageLoadedListener pageLoadListener) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ loadPage(page);
+ if (pageLoadListener != null) {
+ Log.w(TAG, "onPageLoaded("+page+")");
+ pageLoadListener.onPageLoaded();
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ }
+ }
+ });
+ }
+
+ private void loadPage(int page) throws IOException {
+ if (page < 0 || page >= pages.length) {
+ throw new IndexOutOfBoundsException("can't load page that doesn't exist");
+ }
+
+ if (bitmaps.get(page) != null && bitmaps.get(page).get() != null) return;
+
+ try {
+ final String file = "emoji_" + page + "_wrapped.png";
+ final InputStream measureStream = context.getAssets().open(file);
+ final InputStream bitmapStream = context.getAssets().open(file);
+ final Bitmap bitmap = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
+ bitmaps.put(page, new SoftReference<>(bitmap));
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ throw ioe;
+ } catch (BitmapDecodingException bde) {
+ Log.w(TAG, bde);
+ throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
+ }
+ }
+
+ public CharSequence emojify(CharSequence text, PageLoadedListener pageLoadedListener) {
+ return emojify(text, EMOJI_LARGE, pageLoadedListener);
+ }
+
+ public CharSequence emojify(CharSequence text, double size, PageLoadedListener pageLoadedListener) {
+ Matcher matches = EMOJI_RANGE.matcher(text);
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+
+ while (matches.find()) {
+ int codePoint = matches.group().codePointAt(0);
+ Drawable drawable = getEmojiDrawable(codePoint, size, pageLoadedListener);
+ if (drawable != null) {
+ ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
+ char[] chars = new char[matches.end() - matches.start()];
+ Arrays.fill(chars, ' ');
+ builder.setSpan(imageSpan, matches.start(), matches.end(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+ return builder;
+ }
+
+ public Drawable getEmojiDrawable(int emojiCode, double size, PageLoadedListener pageLoadedListener) {
+ return getEmojiDrawable(offsets.get(emojiCode), size, pageLoadedListener);
+ }
+
+ private Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
+ if (drawInfo == null) {
+ return null;
+ }
+ final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
+ drawable.setBounds(0, 0, (int)((double)bigDrawSize * size), (int)((double)bigDrawSize * size));
+ if (bitmaps.get(drawInfo.page) == null || bitmaps.get(drawInfo.page).get() == null) {
+ preloadPage(drawInfo.page, pageLoadedListener);
+ }
+ return drawable;
+ }
+
+ public static class EmojiDrawable extends Drawable {
+ private final int index;
+ private final int page;
+ private final int emojiSize;
+ private static final Paint paint;
+ private Bitmap bmp;
+
+ static {
+ paint = new Paint();
+ paint.setFilterBitmap(true);
+ }
+
+ public EmojiDrawable(DrawInfo info, int emojiSize) {
+ this.index = info.index;
+ this.page = info.page;
+ this.emojiSize = emojiSize;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (bitmaps.get(page) == null || bitmaps.get(page).get() == null) {
+ Log.w(TAG, "bitmap for this page was null");
+ return;
+ }
+ if (bmp == null) {
+ bmp = bitmaps.get(page).get();
+ }
+
+ Rect b = copyBounds();
+
+ final int row = index / EMOJI_PER_ROW;
+ final int row_index = index % EMOJI_PER_ROW;
+
+ canvas.drawBitmap(bmp,
+ new Rect(row_index * emojiSize,
+ row * emojiSize,
+ (row_index + 1) * emojiSize,
+ (row + 1) * emojiSize),
+ b,
+ paint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) { }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) { }
+
+ @Override
+ public String toString() {
+ return "EmojiDrawable{" +
+ "page=" + page +
+ ", index=" + index +
+ '}';
+ }
+ }
+
+ public static class InvalidatingPageLoadedListener implements PageLoadedListener {
+ private final View view;
+
+ public InvalidatingPageLoadedListener(final View view) {
+ this.view = view;
+ }
+
+ @Override
+ public void onPageLoaded() {
+ view.postInvalidate();
+ }
+
+ @Override
+ public String toString() {
+ return "InvalidatingPageLoadedListener{}";
+ }
+ }
+
+ class DrawInfo {
+ int page;
+ int index;
+
+ public DrawInfo(final int page, final int index) {
+ this.page = page;
+ this.index = index;
+ }
+
+ @Override
+ public String toString() {
+ return "DrawInfo{" +
+ "page=" + page +
+ ", index=" + index +
+ '}';
+ }
+ }
+
+ interface PageLoadedListener {
+ void onPageLoaded();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
new file mode 100644
index 0000000000..e304a0c2ef
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
@@ -0,0 +1,34 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+public class EmojiTextView extends TextView {
+ public EmojiTextView(Context context) {
+ super(context);
+ init();
+ }
+
+ public EmojiTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ @TargetApi(VERSION_CODES.LOLLIPOP)
+ public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ private void init() {
+ setTransformationMethod(new EmojiTransformationMethod());
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/EmojiToggle.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java
similarity index 97%
rename from src/org/thoughtcrime/securesms/components/EmojiToggle.java
rename to src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java
index 5d51b8f166..2d969a1b9f 100644
--- a/src/org/thoughtcrime/securesms/components/EmojiToggle.java
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.components;
+package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiTransformationMethod.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiTransformationMethod.java
new file mode 100644
index 0000000000..e3f57db74b
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiTransformationMethod.java
@@ -0,0 +1,17 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.graphics.Rect;
+import android.text.method.TransformationMethod;
+import android.view.View;
+
+import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
+
+class EmojiTransformationMethod implements TransformationMethod {
+
+ @Override public CharSequence getTransformation(CharSequence source, View view) {
+ return EmojiProvider.getInstance(view.getContext()).emojify(source, EmojiProvider.EMOJI_SMALL, new InvalidatingPageLoadedListener(view));
+ }
+
+ @Override public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
+ int direction, Rect previouslyFocusedRect) { }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java b/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java
new file mode 100644
index 0000000000..fc7b1e2cd0
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/RecentEmojiPageModel.java
@@ -0,0 +1,111 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.JsonUtils;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+
+public class RecentEmojiPageModel implements EmojiPageModel {
+ private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
+ private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji";
+ private static final int EMOJI_LRU_SIZE = 50;
+
+ private final SharedPreferences prefs;
+ private final LinkedHashSet recentlyUsed;
+
+ public RecentEmojiPageModel(Context context) {
+ this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ this.recentlyUsed = getPersistedCache();
+ }
+
+ private LinkedHashSet getPersistedCache() {
+ String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
+ LinkedHashSet recentlyUsedStrings;
+ try {
+ CollectionType collectionType = TypeFactory.defaultInstance()
+ .constructCollectionType(LinkedHashSet.class, String.class);
+ recentlyUsedStrings = JsonUtils.getMapper().readValue(serialized, collectionType);
+ return fromHexString(recentlyUsedStrings);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return new LinkedHashSet<>();
+ }
+ }
+
+ @Override public int getIconRes() {
+ return R.drawable.emoji_category_recent;
+ }
+
+ @Override public int[] getCodePoints() {
+ return toPrimitiveArray(recentlyUsed);
+ }
+
+ @Override public void onCodePointSelected(int codePoint) {
+ recentlyUsed.remove(codePoint);
+ recentlyUsed.add(codePoint);
+
+ if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
+ Iterator iterator = recentlyUsed.iterator();
+ iterator.next();
+ iterator.remove();
+ }
+
+ final LinkedHashSet latestRecentlyUsed = new LinkedHashSet<>(recentlyUsed);
+ new AsyncTask() {
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ String serialized = JsonUtils.toJson(toHexString(latestRecentlyUsed));
+ prefs.edit()
+ .putString(EMOJI_LRU_PREFERENCE, serialized)
+ .apply();
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+
+ return null;
+ }
+ }.execute();
+ }
+
+ private LinkedHashSet fromHexString(@Nullable LinkedHashSet stringSet) {
+ final LinkedHashSet integerSet = new LinkedHashSet<>(stringSet != null ? stringSet.size() : 0);
+ if (stringSet != null) {
+ for (String hexString : stringSet) {
+ integerSet.add(Integer.valueOf(hexString, 16));
+ }
+ }
+ return integerSet;
+ }
+
+ private LinkedHashSet toHexString(@NonNull LinkedHashSet integerSet) {
+ final LinkedHashSet stringSet = new LinkedHashSet<>(integerSet.size());
+ for (Integer integer : integerSet) {
+ stringSet.add(Integer.toHexString(integer));
+ }
+ return stringSet;
+ }
+
+ private int[] toPrimitiveArray(@NonNull LinkedHashSet integerSet) {
+ int[] ints = new int[integerSet.size()];
+ int i = 0;
+ for (Integer integer : integerSet) {
+ ints[i++] = integer;
+ }
+ return ints;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java b/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java
new file mode 100644
index 0000000000..3175c61b4a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/emoji/StaticEmojiPageModel.java
@@ -0,0 +1,24 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+
+public class StaticEmojiPageModel implements EmojiPageModel {
+ @DrawableRes private final int icon;
+ @NonNull private final int[] codePoints;
+
+ public StaticEmojiPageModel(@DrawableRes int icon, @NonNull int[] codePoints) {
+ this.icon = icon;
+ this.codePoints = codePoints;
+ }
+
+ public int getIconRes() {
+ return icon;
+ }
+
+ @NonNull public int[] getCodePoints() {
+ return codePoints;
+ }
+
+ @Override public void onCodePointSelected(int codePoint) { }
+}
diff --git a/src/org/thoughtcrime/securesms/util/Emoji.java b/src/org/thoughtcrime/securesms/util/Emoji.java
deleted file mode 100644
index e50b33b8e9..0000000000
--- a/src/org/thoughtcrime/securesms/util/Emoji.java
+++ /dev/null
@@ -1,474 +0,0 @@
-package org.thoughtcrime.securesms.util;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.preference.PreferenceManager;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.ImageSpan;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.View;
-
-import com.fasterxml.jackson.databind.type.TypeFactory;
-
-import org.thoughtcrime.securesms.R;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.concurrent.ExecutorService;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Emoji {
-
- private static final String TAG = Emoji.class.getSimpleName();
-
- private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
-
- public static final int[][] PAGES = {
- {
- 0x263a, 0x1f60a, 0x1f600, 0x1f601, 0x1f602, 0x1f603, 0x1f604, 0x1f605,
- 0x1f606, 0x1f607, 0x1f608, 0x1f609, 0x1f62f, 0x1f610, 0x1f611, 0x1f615,
- 0x1f620, 0x1f62c, 0x1f621, 0x1f622, 0x1f634, 0x1f62e, 0x1f623, 0x1f624,
- 0x1f625, 0x1f626, 0x1f627, 0x1f628, 0x1f629, 0x1f630, 0x1f61f, 0x1f631,
- 0x1f632, 0x1f633, 0x1f635, 0x1f636, 0x1f637, 0x1f61e, 0x1f612, 0x1f60d,
- 0x1f61b, 0x1f61c, 0x1f61d, 0x1f60b, 0x1f617, 0x1f619, 0x1f618, 0x1f61a,
- 0x1f60e, 0x1f62d, 0x1f60c, 0x1f616, 0x1f614, 0x1f62a, 0x1f60f, 0x1f613,
- 0x1f62b, 0x1f64b, 0x1f64c, 0x1f64d, 0x1f645, 0x1f646, 0x1f647, 0x1f64e,
- 0x1f64f, 0x1f63a, 0x1f63c, 0x1f638, 0x1f639, 0x1f63b, 0x1f63d, 0x1f63f,
- 0x1f63e, 0x1f640, 0x1f648, 0x1f649, 0x1f64a, 0x1f4a9, 0x1f476, 0x1f466,
- 0x1f467, 0x1f468, 0x1f469, 0x1f474, 0x1f475, 0x1f48f, 0x1f491, 0x1f46a,
- 0x1f46b, 0x1f46c, 0x1f46d, 0x1f464, 0x1f465, 0x1f46e, 0x1f477, 0x1f481,
- 0x1f482, 0x1f46f, 0x1f470, 0x1f478, 0x1f385, 0x1f47c, 0x1f471, 0x1f472,
- 0x1f473, 0x1f483, 0x1f486, 0x1f487, 0x1f485, 0x1f47b, 0x1f479, 0x1f47a,
- 0x1f47d, 0x1f47e, 0x1f47f, 0x1f480, 0x1f4aa, 0x1f440, 0x1f442, 0x1f443,
- 0x1f463, 0x1f444, 0x1f445, 0x1f48b, 0x2764, 0x1f499, 0x1f49a, 0x1f49b,
- 0x1f49c, 0x1f493, 0x1f494, 0x1f495, 0x1f496, 0x1f497, 0x1f498, 0x1f49d,
- 0x1f49e, 0x1f49f, 0x1f44d, 0x1f44e, 0x1f44c, 0x270a, 0x270c, 0x270b,
- 0x1f44a, 0x261d, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44b, 0x1f44f,
- 0x1f450
- },
- {
- 0x1f530, 0x1f484, 0x1f45e, 0x1f45f, 0x1f451, 0x1f452, 0x1f3a9, 0x1f393,
- 0x1f453, 0x231a, 0x1f454, 0x1f455, 0x1f456, 0x1f457, 0x1f458, 0x1f459,
- 0x1f460, 0x1f461, 0x1f462, 0x1f45a, 0x1f45c, 0x1f4bc, 0x1f392, 0x1f45d,
- 0x1f45b, 0x1f4b0, 0x1f4b3, 0x1f4b2, 0x1f4b5, 0x1f4b4, 0x1f4b6, 0x1f4b7,
- 0x1f4b8, 0x1f4b1, 0x1f4b9, 0x1f52b, 0x1f52a, 0x1f4a3, 0x1f489, 0x1f48a,
- 0x1f6ac, 0x1f514, 0x1f515, 0x1f6aa, 0x1f52c, 0x1f52d, 0x1f52e, 0x1f526,
- 0x1f50b, 0x1f50c, 0x1f4dc, 0x1f4d7, 0x1f4d8, 0x1f4d9, 0x1f4da, 0x1f4d4,
- 0x1f4d2, 0x1f4d1, 0x1f4d3, 0x1f4d5, 0x1f4d6, 0x1f4f0, 0x1f4db, 0x1f383,
- 0x1f384, 0x1f380, 0x1f381, 0x1f382, 0x1f388, 0x1f386, 0x1f387, 0x1f389,
- 0x1f38a, 0x1f38d, 0x1f38f, 0x1f38c, 0x1f390, 0x1f38b, 0x1f38e, 0x1f4f1,
- 0x1f4f2, 0x1f4df, 0x260e, 0x1f4de, 0x1f4e0, 0x1f4e6, 0x2709, 0x1f4e8,
- 0x1f4e9, 0x1f4ea, 0x1f4eb, 0x1f4ed, 0x1f4ec, 0x1f4ee, 0x1f4e4, 0x1f4e5,
- 0x1f4ef, 0x1f4e2, 0x1f4e3, 0x1f4e1, 0x1f4ac, 0x1f4ad, 0x2712, 0x270f,
- 0x1f4dd, 0x1f4cf, 0x1f4d0, 0x1f4cd, 0x1f4cc, 0x1f4ce, 0x2702, 0x1f4ba,
- 0x1f4bb, 0x1f4bd, 0x1f4be, 0x1f4bf, 0x1f4c6, 0x1f4c5, 0x1f4c7, 0x1f4cb,
- 0x1f4c1, 0x1f4c2, 0x1f4c3, 0x1f4c4, 0x1f4ca, 0x1f4c8, 0x1f4c9, 0x26fa,
- 0x1f3a1, 0x1f3a2, 0x1f3a0, 0x1f3aa, 0x1f3a8, 0x1f3ac, 0x1f3a5, 0x1f4f7,
- 0x1f4f9, 0x1f3a6, 0x1f3ad, 0x1f3ab, 0x1f3ae, 0x1f3b2, 0x1f3b0, 0x1f0cf,
- 0x1f3b4, 0x1f004, 0x1f3af, 0x1f4fa, 0x1f4fb, 0x1f4c0, 0x1f4fc, 0x1f3a7,
- 0x1f3a4, 0x1f3b5, 0x1f3b6, 0x1f3bc, 0x1f3bb, 0x1f3b9, 0x1f3b7, 0x1f3ba,
- 0x1f3b8, 0x303d
- },
- {
- 0x1f415, 0x1f436, 0x1f429, 0x1f408, 0x1f431, 0x1f400, 0x1f401, 0x1f42d,
- 0x1f439, 0x1f422, 0x1f407, 0x1f430, 0x1f413, 0x1f414, 0x1f423, 0x1f424,
- 0x1f425, 0x1f426, 0x1f40f, 0x1f411, 0x1f410, 0x1f43a, 0x1f403, 0x1f402,
- 0x1f404, 0x1f42e, 0x1f434, 0x1f417, 0x1f416, 0x1f437, 0x1f43d, 0x1f438,
- 0x1f40d, 0x1f43c, 0x1f427, 0x1f418, 0x1f428, 0x1f412, 0x1f435, 0x1f406,
- 0x1f42f, 0x1f43b, 0x1f42b, 0x1f42a, 0x1f40a, 0x1f433, 0x1f40b, 0x1f41f,
- 0x1f420, 0x1f421, 0x1f419, 0x1f41a, 0x1f42c, 0x1f40c, 0x1f41b, 0x1f41c,
- 0x1f41d, 0x1f41e, 0x1f432, 0x1f409, 0x1f43e, 0x1f378, 0x1f37a, 0x1f37b,
- 0x1f377, 0x1f379, 0x1f376, 0x2615, 0x1f375, 0x1f37c, 0x1f374, 0x1f368,
- 0x1f367, 0x1f366, 0x1f369, 0x1f370, 0x1f36a, 0x1f36b, 0x1f36c, 0x1f36d,
- 0x1f36e, 0x1f36f, 0x1f373, 0x1f354, 0x1f35f, 0x1f35d, 0x1f355, 0x1f356,
- 0x1f357, 0x1f364, 0x1f363, 0x1f371, 0x1f35e, 0x1f35c, 0x1f359, 0x1f35a,
- 0x1f35b, 0x1f372, 0x1f365, 0x1f362, 0x1f361, 0x1f358, 0x1f360, 0x1f34c,
- 0x1f34e, 0x1f34f, 0x1f34a, 0x1f34b, 0x1f344, 0x1f345, 0x1f346, 0x1f347,
- 0x1f348, 0x1f349, 0x1f350, 0x1f351, 0x1f352, 0x1f353, 0x1f34d, 0x1f330,
- 0x1f331, 0x1f332, 0x1f333, 0x1f334, 0x1f335, 0x1f337, 0x1f338, 0x1f339,
- 0x1f340, 0x1f341, 0x1f342, 0x1f343, 0x1f33a, 0x1f33b, 0x1f33c, 0x1f33d,
- 0x1f33e, 0x1f33f, 0x2600, 0x1f308, 0x26c5, 0x2601, 0x1f301, 0x1f302,
- 0x2614, 0x1f4a7, 0x26a1, 0x1f300, 0x2744, 0x26c4, 0x1f319, 0x1f31e,
- 0x1f31d, 0x1f31a, 0x1f31b, 0x1f31c, 0x1f311, 0x1f312, 0x1f313, 0x1f314,
- 0x1f315, 0x1f316, 0x1f317, 0x1f318, 0x1f391, 0x1f304, 0x1f305, 0x1f307,
- 0x1f306, 0x1f303, 0x1f30c, 0x1f309, 0x1f30a, 0x1f30b, 0x1f30e, 0x1f30f,
- 0x1f30d, 0x1f310
- },
- {
- 0x1f3e0, 0x1f3e1, 0x1f3e2, 0x1f3e3, 0x1f3e4, 0x1f3e5, 0x1f3e6, 0x1f3e7,
- 0x1f3e8, 0x1f3e9, 0x1f3ea, 0x1f3eb, 0x26ea, 0x26f2, 0x1f3ec, 0x1f3ef,
- 0x1f3f0, 0x1f3ed, 0x1f5fb, 0x1f5fc, 0x1f5fd, 0x1f5fe, 0x1f5ff, 0x2693,
- 0x1f3ee, 0x1f488, 0x1f527, 0x1f528, 0x1f529, 0x1f6bf, 0x1f6c1, 0x1f6c0,
- 0x1f6bd, 0x1f6be, 0x1f3bd, 0x1f3a3, 0x1f3b1, 0x1f3b3, 0x26be, 0x26f3,
- 0x1f3be, 0x26bd, 0x1f3bf, 0x1f3c0, 0x1f3c1, 0x1f3c2, 0x1f3c3, 0x1f3c4,
- 0x1f3c6, 0x1f3c7, 0x1f40e, 0x1f3c8, 0x1f3c9, 0x1f3ca, 0x1f682, 0x1f683,
- 0x1f684, 0x1f685, 0x1f686, 0x1f687, 0x24c2, 0x1f688, 0x1f68a, 0x1f68b,
- 0x1f68c, 0x1f68d, 0x1f68e, 0x1f68f, 0x1f690, 0x1f691, 0x1f692, 0x1f693,
- 0x1f694, 0x1f695, 0x1f696, 0x1f697, 0x1f698, 0x1f699, 0x1f69a, 0x1f69b,
- 0x1f69c, 0x1f69d, 0x1f69e, 0x1f69f, 0x1f6a0, 0x1f6a1, 0x1f6a2, 0x1f6a3,
- 0x1f681, 0x2708, 0x1f6c2, 0x1f6c3, 0x1f6c4, 0x1f6c5, 0x26f5, 0x1f6b2,
- 0x1f6b3, 0x1f6b4, 0x1f6b5, 0x1f6b7, 0x1f6b8, 0x1f689, 0x1f680, 0x1f6a4,
- 0x1f6b6, 0x26fd, 0x1f17f, 0x1f6a5, 0x1f6a6, 0x1f6a7, 0x1f6a8, 0x2668,
- 0x1f48c, 0x1f48d, 0x1f48e, 0x1f490, 0x1f492, 0xfe4e5, 0xfe4e6, 0xfe4e7,
- 0xfe4e8, 0xfe4e9, 0xfe4ea, 0xfe4eb, 0xfe4ec, 0xfe4ed, 0xfe4ee
- },
- {
- 0x1f51d, 0x1f519, 0x1f51b, 0x1f51c, 0x1f51a, 0x23f3, 0x231b, 0x23f0,
- 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f,
- 0x2650, 0x2651, 0x2652, 0x2653, 0x26ce, 0x1f531, 0x1f52f, 0x1f6bb,
- 0x1f6ae, 0x1f6af, 0x1f6b0, 0x1f6b1, 0x1f170, 0x1f171, 0x1f18e, 0x1f17e,
- 0x1f4ae, 0x1f4af, 0x1f520, 0x1f521, 0x1f522, 0x1f523, 0x1f524, 0x27bf,
- 0x1f4f6, 0x1f4f3, 0x1f4f4, 0x1f4f5, 0x1f6b9, 0x1f6ba, 0x1f6bc, 0x267f,
- 0x267b, 0x1f6ad, 0x1f6a9, 0x26a0, 0x1f201, 0x1f51e, 0x26d4, 0x1f192,
- 0x1f197, 0x1f195, 0x1f198, 0x1f199, 0x1f193, 0x1f196, 0x1f19a, 0x1f232,
- 0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f202,
- 0x1f23a, 0x1f250, 0x1f251, 0x3299, 0x00ae, 0x00a9, 0x2122, 0x1f21a,
- 0x1f22f, 0x3297, 0x2b55, 0x274c, 0x274e, 0x2139, 0x1f6ab, 0x2705,
- 0x2714, 0x1f517, 0x2734, 0x2733, 0x2795, 0x2796, 0x2716, 0x2797,
- 0x1f4a0, 0x1f4a1, 0x1f4a4, 0x1f4a2, 0x1f525, 0x1f4a5, 0x1f4a8, 0x1f4a6,
- 0x1f4ab, 0x1f55b, 0x1f567, 0x1f550, 0x1f55c, 0x1f551, 0x1f55d, 0x1f552,
- 0x1f55e, 0x1f553, 0x1f55f, 0x1f554, 0x1f560, 0x1f555, 0x1f561, 0x1f556,
- 0x1f562, 0x1f557, 0x1f563, 0x1f558, 0x1f564, 0x1f559, 0x1f565, 0x1f55a,
- 0x1f566, 0x2195, 0x2b06, 0x2197, 0x27a1, 0x2198, 0x2b07, 0x2199,
- 0x2b05, 0x2196, 0x2194, 0x2934, 0x2935, 0x23ea, 0x23eb, 0x23ec,
- 0x23e9, 0x25c0, 0x25b6, 0x1f53d, 0x1f53c, 0x2747, 0x2728, 0x1f534,
- 0x1f535, 0x26aa, 0x26ab, 0x1f533, 0x1f532, 0x2b50, 0x1f31f, 0x1f320,
- 0x25ab, 0x25aa, 0x25fd, 0x25fe, 0x25fb, 0x25fc, 0x2b1c, 0x2b1b,
- 0x1f538, 0x1f539, 0x1f536, 0x1f537, 0x1f53a, 0x1f53b, 0x1f51f, /*0x20e3,*/
- 0x2754, 0x2753, 0x2755, 0x2757, 0x203c, 0x2049, 0x3030, 0x27b0,
- 0x2660, 0x2665, 0x2663, 0x2666, 0x1f194, 0x1f511, 0x21a9, 0x1f191,
- 0x1f50d, 0x1f512, 0x1f513, 0x21aa, 0x1f510, 0x2611, 0x1f518, 0x1f50e,
- 0x1f516, 0x1f50f, 0x1f503, 0x1f500, 0x1f501, 0x1f502, 0x1f504, 0x1f4e7,
- 0x1f505, 0x1f506, 0x1f507, 0x1f508, 0x1f509, 0x1f50a
- }
- };
-
- private static final SparseArray offsets;
-
- static {
- offsets = new SparseArray();
- for (int i = 0; i < PAGES.length; i++) {
- for (int j = 0; j < PAGES[i].length; j++) {
- offsets.put(PAGES[i][j], new DrawInfo(i, j));
- }
- }
- }
-
- private static Bitmap[] bitmaps = new Bitmap[PAGES.length];
-
- private static Emoji instance = null;
-
- public synchronized static Emoji getInstance(Context context) {
- if (instance == null) {
- instance = new Emoji(context);
- }
-
- return instance;
- }
-
- @SuppressWarnings("MalformedRegex")
- // 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
- // |==== misc ====||======== emoticons ========||========= flags ==========|
- private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
-
- public static final double EMOJI_HUGE = 1.00;
- public static final double EMOJI_LARGE = 0.75;
- public static final double EMOJI_SMALL = 0.50;
- public static final int EMOJI_RAW_SIZE = 128;
- public static final int EMOJI_PER_ROW = 16;
-
- private final Context context;
- private final int bigDrawSize;
-
- private Emoji(Context context) {
- this.context = context.getApplicationContext();
- this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
- }
-
- private void preloadPage(final int page, final PageLoadedListener pageLoadListener) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- try {
- loadPage(page);
- if (pageLoadListener != null) pageLoadListener.onPageLoaded();
- } catch (IOException ioe) {
- Log.w("Emoji", ioe);
- }
- }
- });
- }
-
- private void loadPage(int page) throws IOException {
- if (page < 0 || page >= PAGES.length) {
- throw new IndexOutOfBoundsException("can't load page that doesn't exist");
- }
-
- if (bitmaps[page] != null) return;
-
- try {
- final String file = "emoji_" + page + "_wrapped.png";
- final InputStream measureStream = context.getAssets().open(file);
- final InputStream bitmapStream = context.getAssets().open(file);
-
- bitmaps[page] = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
- } catch (IOException ioe) {
- Log.w("Emoji", ioe);
- throw ioe;
- } catch (BitmapDecodingException bde) {
- Log.w("Emoji", bde);
- throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
- }
- }
-
- public SpannableString emojify(String text, PageLoadedListener pageLoadedListener) {
- return emojify(new SpannableString(text), pageLoadedListener);
- }
-
- public SpannableString emojify(SpannableString text, PageLoadedListener pageLoadedListener) {
- return emojify(text, EMOJI_LARGE, pageLoadedListener);
- }
-
- public SpannableString emojify(SpannableString text, double size, PageLoadedListener pageLoadedListener) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return text;
-
- Matcher matches = EMOJI_RANGE.matcher(text);
-
- while (matches.find()) {
- String resource = Integer.toHexString(matches.group().codePointAt(0));
-
- Drawable drawable = getEmojiDrawable(resource, size, pageLoadedListener);
- if (drawable != null) {
- ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
- text.setSpan(imageSpan, matches.start(), matches.end(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
-
- return text;
- }
-
- public Pair getRecentlyUsed(int position, double size, PageLoadedListener pageLoadedListener) {
- String[] recentlyUsed = EmojiLRU.getRecentlyUsed(context);
- String code = recentlyUsed[recentlyUsed.length - 1 - position];
- return new Pair(Integer.parseInt(code, 16), getEmojiDrawable(code, size, pageLoadedListener));
- }
-
- public void setRecentlyUsed(String emojiCode) {
- EmojiLRU.putRecentlyUsed(context, emojiCode);
- }
-
- public int getRecentlyUsedAssetCount() {
- return EmojiLRU.getRecentlyUsedCount(context);
- }
-
- public Drawable getEmojiDrawable(String emojiCode, double size, PageLoadedListener pageLoadedListener) {
- return getEmojiDrawable(offsets.get(Integer.parseInt(emojiCode, 16)), size, pageLoadedListener);
- }
-
- public Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
- if (drawInfo == null) {
- return null;
- }
- final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
- drawable.setBounds(0, 0, (int) ((double) bigDrawSize * size), (int) ((double) bigDrawSize * size));
- if (bitmaps[drawInfo.page] == null) {
- preloadPage(drawInfo.page, pageLoadedListener);
- }
- return drawable;
- }
-
- private static class EmojiLRU {
- private static SharedPreferences prefs = null;
- private static LinkedHashSet recentlyUsed = null;
- private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji";
- private static final int EMOJI_LRU_SIZE = 50;
-
- private static void initializeCache(Context context) {
- if (prefs == null) {
- prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
-
- try {
- recentlyUsed = JsonUtils.getMapper().readValue(serialized, TypeFactory.defaultInstance()
- .constructCollectionType(LinkedHashSet.class, String.class));
- } catch (IOException e) {
- Log.w(TAG, e);
- recentlyUsed = new LinkedHashSet<>();
- }
- }
-
- public static String[] getRecentlyUsed(Context context) {
- if (recentlyUsed == null) initializeCache(context);
- return recentlyUsed.toArray(new String[recentlyUsed.size()]);
- }
-
- public static int getRecentlyUsedCount(Context context) {
- if (recentlyUsed == null) initializeCache(context);
- return recentlyUsed.size();
- }
-
- public static void putRecentlyUsed(Context context, String asset) {
- if (recentlyUsed == null) initializeCache(context);
- if (prefs == null) {
- prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- recentlyUsed.remove(asset);
- recentlyUsed.add(asset);
-
- if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
- Iterator iterator = recentlyUsed.iterator();
- iterator.next();
- iterator.remove();
- }
-
- final LinkedHashSet latestRecentlyUsed = new LinkedHashSet(recentlyUsed);
- new AsyncTask() {
-
- @Override
- protected Void doInBackground(Void... params) {
- try {
- String serialized = JsonUtils.toJson(latestRecentlyUsed);
- prefs.edit()
- .putString(EMOJI_LRU_PREFERENCE, serialized)
- .apply();
- } catch (IOException e) {
- Log.w(TAG, e);
- }
-
- return null;
- }
- }.execute();
-
- }
- }
-
- public static class EmojiDrawable extends Drawable {
- private final int index;
- private final int page;
- private final int emojiSize;
- private static final Paint paint;
- private Bitmap bmp;
-
- static {
- paint = new Paint();
- paint.setFilterBitmap(true);
- }
-
- public EmojiDrawable(DrawInfo info, int emojiSize) {
- this.index = info.index;
- this.page = info.page;
- this.emojiSize = emojiSize;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (bitmaps[page] == null) {
- Log.w("Emoji", "bitmap for this page was null");
- return;
- }
- if (bmp == null) {
- bmp = bitmaps[page];
- }
-
- Rect b = copyBounds();
-// int cX = b.centerX(), cY = b.centerY();
-// b.left = cX - emojiSize / 2;
-// b.right = cX + emojiSize / 2;
-// b.top = cY - emojiSize / 2;
-// b.bottom = cY + emojiSize / 2;
-
- final int row = index / EMOJI_PER_ROW;
- final int row_index = index % EMOJI_PER_ROW;
-
- canvas.drawBitmap(bmp,
- new Rect(row_index * emojiSize,
- row * emojiSize,
- (row_index + 1) * emojiSize,
- (row + 1) * emojiSize),
- b,
- paint);
- }
-
- @Override
- public int getOpacity() {
- return 0;
- }
-
- @Override
- public void setAlpha(int alpha) { }
-
- @Override
- public void setColorFilter(ColorFilter cf) { }
-
- @Override
- public String toString() {
- return "EmojiDrawable{" +
- "page=" + page +
- ", index=" + index +
- '}';
- }
- }
-
- public static interface PageLoadedListener {
- public void onPageLoaded();
- }
-
- public static class InvalidatingPageLoadedListener implements PageLoadedListener {
- private final View view;
-
- public InvalidatingPageLoadedListener(final View view) {
- this.view = view;
- }
-
- @Override
- public void onPageLoaded() {
- view.post(new Runnable() {
- @Override
- public void run() {
- view.invalidate();
- }
- });
- }
-
- @Override
- public String toString() {
- return "InvalidatingPageLoadedListener{}";
- }
- }
-
- public static class DrawInfo {
- int page;
- int index;
-
- public DrawInfo(final int page, final int index) {
- this.page = page;
- this.index = index;
- }
-
- @Override
- public String toString() {
- return "DrawInfo{" +
- "page=" + page +
- ", index=" + index +
- '}';
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/util/ResUtil.java b/src/org/thoughtcrime/securesms/util/ResUtil.java
index 86910a5c6f..0984f10819 100644
--- a/src/org/thoughtcrime/securesms/util/ResUtil.java
+++ b/src/org/thoughtcrime/securesms/util/ResUtil.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.support.annotation.ArrayRes;
import android.support.annotation.AttrRes;
import android.util.TypedValue;
@@ -44,6 +45,16 @@ public class ResUtil {
}
public static Drawable getDrawable(Context c, @AttrRes int attr) {
- return c.getResources().getDrawable(getDrawableRes(c, attr));
+ return c.getResources().getDrawable(getDrawableRes(c, attr), c.getTheme());
+ }
+
+ public static int[] getResourceIds(Context c, @ArrayRes int array) {
+ final TypedArray typedArray = c.getResources().obtainTypedArray(array);
+ final int[] resourceIds = new int[typedArray.length()];
+ for (int i = 0; i < typedArray.length(); i++) {
+ resourceIds[i] = typedArray.getResourceId(i, 0);
+ }
+ typedArray.recycle();
+ return resourceIds;
}
}