diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.java b/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.java deleted file mode 100644 index fe1ebe69..00000000 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.java +++ /dev/null @@ -1,572 +0,0 @@ -package com.sukisu.ultra.ui.util; -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.text.TextUtils; -import android.util.Log; - -import java.text.Collator; -import java.util.ArrayList; -import java.util.Locale; - -/** - * An object to convert Chinese character to its corresponding pinyin string. For characters with - * multiple possible pinyin string, only one is selected according to collator. Polyphone is not - * supported in this implementation. This class is implemented to achieve the best runtime - * performance and minimum runtime resources with tolerable sacrifice of accuracy. This - * implementation highly depends on zh_CN ICU collation data and must be always synchronized with - * ICU. - *

- * Currently this file is aligned to zh.txt in ICU 4.6 - */ -@SuppressWarnings("SizeReplaceableByIsEmpty") -public record HanziToPinyin(boolean mHasChinaCollator) { - private static final String TAG = "HanziToPinyin"; - - // Turn on this flag when we want to check internal data structure. - private static final boolean DEBUG = false; - - /** - * Unihans array. - *

- * Each unihans is the first one within same pinyin when collator is zh_CN. - */ - public static final char[] UNIHANS = { - '阿', '哎', '安', '肮', '凹', '八', - '挀', '扳', '邦', '勹', '陂', '奔', - '伻', '屄', '边', '灬', '憋', '汃', - '冫', '癶', '峬', '嚓', '偲', '参', - '仓', '撡', '冊', '嵾', '曽', '曾', - '層', '叉', '芆', '辿', '伥', '抄', - '车', '抻', '沈', '沉', '阷', '吃', - '充', '抽', '出', '欻', '揣', '巛', - '刅', '吹', '旾', '逴', '呲', '匆', - '凑', '粗', '汆', '崔', '邨', '搓', - '咑', '呆', '丹', '当', '刀', '嘚', - '扥', '灯', '氐', '嗲', '甸', '刁', - '爹', '丁', '丟', '东', '吺', '厾', - '耑', '襨', '吨', '多', '妸', '诶', - '奀', '鞥', '儿', '发', '帆', '匚', - '飞', '分', '丰', '覅', '仏', '紑', - '伕', '旮', '侅', '甘', '冈', '皋', - '戈', '给', '根', '刯', '工', '勾', - '估', '瓜', '乖', '关', '光', '归', - '丨', '呙', '哈', '咍', '佄', '夯', - '茠', '诃', '黒', '拫', '亨', '噷', - '叿', '齁', '乯', '花', '怀', '犿', - '巟', '灰', '昏', '吙', '丌', '加', - '戋', '江', '艽', '阶', '巾', '坕', - '冂', '丩', '凥', '姢', '噘', '军', - '咔', '开', '刊', '忼', '尻', '匼', - '肎', '劥', '空', '抠', '扝', '夸', - '蒯', '宽', '匡', '亏', '坤', '扩', - '垃', '来', '兰', '啷', '捞', '肋', - '勒', '崚', '刕', '俩', '奁', '良', - '撩', '列', '拎', '刢', '溜', '囖', - '龙', '瞜', '噜', '娈', '畧', '抡', - '罗', '呣', '妈', '埋', '嫚', '牤', - '猫', '么', '呅', '门', '甿', '咪', - '宀', '喵', '乜', '民', '名', '谬', - '摸', '哞', '毪', '嗯', '拏', '腉', - '囡', '囔', '孬', '疒', '娞', '恁', - '能', '妮', '拈', '嬢', '鸟', '捏', - '囜', '宁', '妞', '农', '羺', '奴', - '奻', '疟', '黁', '郍', '喔', '讴', - '妑', '拍', '眅', '乓', '抛', '呸', - '喷', '匉', '丕', '囨', '剽', '氕', - '姘', '乒', '钋', '剖', '仆', '七', - '掐', '千', '呛', '悄', '癿', '亲', - '狅', '芎', '丘', '区', '峑', '缺', - '夋', '呥', '穣', '娆', '惹', '人', - '扔', '日', '茸', '厹', '邚', '挼', - '堧', '婑', '瞤', '捼', '仨', '毢', - '三', '桒', '掻', '閪', '森', '僧', - '杀', '筛', '山', '伤', '弰', '奢', - '申', '莘', '敒', '升', '尸', '収', - '书', '刷', '衰', '闩', '双', '谁', - '吮', '说', '厶', '忪', '捜', '苏', - '狻', '夊', '孙', '唆', '他', '囼', - '坍', '汤', '夲', '忑', '熥', '剔', - '天', '旫', '帖', '厅', '囲', '偷', - '凸', '湍', '推', '吞', '乇', '穵', - '歪', '弯', '尣', '危', '昷', '翁', - '挝', '乌', '夕', '虲', '仚', '乡', - '灱', '些', '心', '星', '凶', '休', - '吁', '吅', '削', '坃', '丫', '恹', - '央', '幺', '倻', '一', '囙', '应', - '哟', '佣', '优', '扜', '囦', '曰', - '晕', '筠', '筼', '帀', '災', '兂', - '匨', '傮', '则', '贼', '怎', '増', - '扎', '捚', '沾', '张', '长', '長', - '佋', '蜇', '贞', '争', '之', '峙', - '庢', '中', '州', '朱', '抓', '拽', - '专', '妆', '隹', '宒', '卓', '乲', - '宗', '邹', '租', '钻', '厜', '尊', - '昨', '兙', '鿃', '鿄'}; - - /** - * Pinyin array. - *

- * Each pinyin is corresponding to unihans of same - * offset in the unihans array. - */ - public static final byte[][] PINYINS = { - {65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0}, - {65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0}, - {65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0}, - {66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0}, - {66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0}, - {66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0}, - {66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0}, - {66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0}, - {66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0}, - {66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0}, - {66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0}, - {67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0}, - {67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0}, - {67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0}, - {67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0}, - {67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0}, - {67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0}, - {67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0}, - {67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0}, - {83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0}, - {67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0}, - {67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0}, - {67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0}, - {67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0}, - {67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0}, - {67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0}, - {67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0}, - {67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0}, - {67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0}, - {67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0}, - {68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0}, - {68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0}, - {68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0}, - {68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0}, - {68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0}, - {68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0}, - {68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0}, - {68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0}, - {68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0}, - {68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0}, - {68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0}, - {69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0}, - {69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0}, - {69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0}, - {70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0}, - {70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0}, - {70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0}, - {70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0}, - {70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0}, - {71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0}, - {71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0}, - {71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0}, - {71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0}, - {71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0}, - {71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0}, - {71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0}, - {71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0}, - {71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0}, - {72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0}, - {72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0}, - {72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0}, - {72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0}, - {72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0}, - {72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0}, - {72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0}, - {72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0}, - {72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0}, - {72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0}, - {74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0}, - {74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0}, - {74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0}, - {74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0}, - {74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0}, - {74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0}, - {74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0}, - {75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0}, - {75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0}, - {75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0}, - {75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0}, - {75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0}, - {75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0}, - {75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0}, - {75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0}, - {75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0}, - {76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0}, - {76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0}, - {76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0}, - {76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0}, - {76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0}, - {76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0}, - {76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0}, - {76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0}, - {76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0}, - {76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0}, - {76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0}, - {76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0}, - {76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0}, - {77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0}, - {77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0}, - {77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0}, - {77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0}, - {77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0}, - {77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0}, - {77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0}, - {77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0}, - {77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0}, - {77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0}, - {78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0}, - {78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0}, - {78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0}, - {78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0}, - {78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0}, - {78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0}, - {78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0}, - {78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0}, - {78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0}, - {78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0}, - {78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0}, - {78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0}, - {79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0}, - {80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0}, - {80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0}, - {80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0}, - {80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0}, - {80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0}, - {80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0}, - {80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0}, - {80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0}, - {80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0}, - {81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0}, - {81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0}, - {81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0}, - {81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0}, - {81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0}, - {81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0}, - {81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0}, - {82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0}, - {82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0}, - {82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0}, - {82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0}, - {82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0}, - {82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0}, - {82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0}, - {83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0}, - {83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0}, - {83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0}, - {83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0}, - {83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0}, - {83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0}, - {83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0}, - {83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0}, - {83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0}, - {83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0}, - {83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0}, - {83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0}, - {83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0}, - {83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0}, - {83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0}, - {83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0}, - {83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0}, - {83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0}, - {84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0}, - {84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0}, - {84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0}, - {84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0}, - {84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0}, - {84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0}, - {84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0}, - {84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0}, - {84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0}, - {84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0}, - {87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0}, - {87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0}, - {87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0}, - {87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0}, - {88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0}, - {88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0}, - {88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0}, - {88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0}, - {88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0}, - {88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0}, - {88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0}, - {89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0}, - {89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0}, - {89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0}, - {89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0}, - {89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0}, - {89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0}, - {89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0}, - {89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0}, - {89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0}, - {90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0}, - {90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0}, - {90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0}, - {90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0}, - {90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0}, - {90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0}, - {67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0}, - {90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0}, - {90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0}, - {90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0}, - {90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0}, - {90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0}, - {90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0}, - {90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71}, - {90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0}, - {90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0}, - {90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0}, - {90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0}, - {90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0}, - {90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, - {83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0}}; - - /** - * First and last Chinese character with known Pinyin according to zh collation - */ - private static final String FIRST_PINYIN_UNIHAN = "阿"; - private static final String LAST_PINYIN_UNIHAN = "鿿"; - - private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA); - - private static HanziToPinyin sInstance; - - public static class Token { - /** - * Separator between target string for each source char - */ - public static final String SEPARATOR = " "; - - public static final int LATIN = 1; - public static final int PINYIN = 2; - public static final int UNKNOWN = 3; - - public Token() { - } - - public Token(int type, String source, String target) { - this.type = type; - this.source = source; - this.target = target; - } - - /** - * Type of this token, ASCII, PINYIN or UNKNOWN. - */ - public int type; - /** - * Original string before translation. - */ - public String source; - /** - * Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is - * original string in source. - */ - public String target; - } - - public static HanziToPinyin getInstance() { - synchronized (HanziToPinyin.class) { - if (sInstance != null) { - return sInstance; - } - // Check if zh_CN collation data is available - final Locale[] locale = Collator.getAvailableLocales(); - for (Locale value : locale) { - if (value.equals(Locale.CHINA) || value.getLanguage().contains("zh")) { - // Do self validation just once. - if (DEBUG) { - Log.d(TAG, "Self validation. Result: " + doSelfValidation()); - } - sInstance = new HanziToPinyin(true); - return sInstance; - } - } - if (sInstance == null) {//这个判断是用于处理国产ROM的兼容性问题 - if (Locale.CHINA.equals(Locale.getDefault())) { - sInstance = new HanziToPinyin(true); - return sInstance; - } - } - Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled"); - sInstance = new HanziToPinyin(false); - return sInstance; - } - } - - /** - * Validate if our internal table has some wrong value. - * - * @return true when the table looks correct. - */ - private static boolean doSelfValidation() { - char lastChar = UNIHANS[0]; - String lastString = Character.toString(lastChar); - for (char c : UNIHANS) { - if (lastChar == c) { - continue; - } - final String curString = Character.toString(c); - int cmp = COLLATOR.compare(lastString, curString); - if (cmp >= 0) { - Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString - + "\" is greater than current string \"" + curString + "\"."); - return false; - } - lastString = curString; - } - return true; - } - - private Token getToken(char character) { - Token token = new Token(); - final String letter = Character.toString(character); - token.source = letter; - int offset = -1; - int cmp; - if (character < 256) { - token.type = Token.LATIN; - token.target = letter; - return token; - } else { - cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN); - if (cmp < 0) { - token.type = Token.UNKNOWN; - token.target = letter; - return token; - } else if (cmp == 0) { - token.type = Token.PINYIN; - offset = 0; - } else { - cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN); - if (cmp > 0) { - token.type = Token.UNKNOWN; - token.target = letter; - return token; - } else if (cmp == 0) { - token.type = Token.PINYIN; - offset = UNIHANS.length - 1; - } - } - } - - token.type = Token.PINYIN; - if (offset < 0) { - int begin = 0; - int end = UNIHANS.length - 1; - while (begin <= end) { - offset = (begin + end) / 2; - final String unihan = Character.toString(UNIHANS[offset]); - cmp = COLLATOR.compare(letter, unihan); - if (cmp == 0) { - break; - } else if (cmp > 0) { - begin = offset + 1; - } else { - end = offset - 1; - } - } - } - if (cmp < 0) { - offset--; - } - StringBuilder pinyin = new StringBuilder(); - for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) { - pinyin.append((char) PINYINS[offset][j]); - } - token.target = pinyin.toString(); - if (TextUtils.isEmpty(token.target)) { - token.type = Token.UNKNOWN; - token.target = token.source; - } - return token; - } - - /** - * Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without - * space will be put into a Token, One Hanzi character which has pinyin will be treated as a - * Token. If these is no China collator, the empty token array is returned. - */ - public ArrayList get(final String input) { - ArrayList tokens = new ArrayList<>(); - if (!mHasChinaCollator || TextUtils.isEmpty(input)) { - // return empty tokens. - return tokens; - } - final int inputLength = input.length(); - final StringBuilder sb = new StringBuilder(); - int tokenType = Token.LATIN; - // Go through the input, create a new token when - // a. Token type changed - // b. Get the Pinyin of current charater. - // c. current character is space. - for (int i = 0; i < inputLength; i++) { - final char character = input.charAt(i); - if (character == ' ') { - if (sb.length() > 0) { - addToken(sb, tokens, tokenType); - } - } else if (character < 256) { - if (tokenType != Token.LATIN && sb.length() > 0) { - addToken(sb, tokens, tokenType); - } - tokenType = Token.LATIN; - sb.append(character); - } else { - Token t = getToken(character); - if (t.type == Token.PINYIN) { - if (sb.length() > 0) { - addToken(sb, tokens, tokenType); - } - tokens.add(t); - tokenType = Token.PINYIN; - } else { - if (tokenType != t.type && sb.length() > 0) { - addToken(sb, tokens, tokenType); - } - tokenType = t.type; - sb.append(character); - } - } - } - if (sb.length() > 0) { - addToken(sb, tokens, tokenType); - } - return tokens; - } - - private void addToken( - final StringBuilder sb, final ArrayList tokens, final int tokenType) { - String str = sb.toString(); - tokens.add(new Token(tokenType, str, str)); - sb.setLength(0); - } - - public String toPinyinString(String string) { - if (string == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - ArrayList tokens = get(string); - for (Token token : tokens) { - sb.append(token.target); - } - return sb.toString().toLowerCase(); - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.kt new file mode 100644 index 00000000..edfbdf52 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/HanziToPinyin.kt @@ -0,0 +1,522 @@ +package com.sukisu.ultra.ui.util + +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.text.TextUtils +import android.util.Log +import java.text.Collator +import java.util.Locale + +class HanziToPinyin private constructor(val hasChinaCollator: Boolean) { + + class Token( + var type: Int = 0, + var source: String = "", + var target: String = "" + ) { + companion object { + const val LATIN = 1 + const val PINYIN = 2 + const val UNKNOWN = 3 + } + } + + private fun getToken(character: Char): Token { + val token = Token() + val letter = character.toString() + token.source = letter + var offset = -1 + var cmp: Int + + if (character < 256.toChar()) { + token.type = Token.LATIN + token.target = letter + return token + } else { + cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN) + if (cmp < 0) { + token.type = Token.UNKNOWN + token.target = letter + return token + } else if (cmp == 0) { + token.type = Token.PINYIN + offset = 0 + } else { + cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN) + if (cmp > 0) { + token.type = Token.UNKNOWN + token.target = letter + return token + } else if (cmp == 0) { + token.type = Token.PINYIN + offset = UNIHANS.size - 1 + } + } + } + + token.type = Token.PINYIN + if (offset < 0) { + var begin = 0 + var end = UNIHANS.size - 1 + while (begin <= end) { + offset = (begin + end) / 2 + val unihan = UNIHANS[offset].toString() + cmp = COLLATOR.compare(letter, unihan) + when { + cmp == 0 -> break + cmp > 0 -> begin = offset + 1 + else -> end = offset - 1 + } + } + } + if (cmp < 0) { + offset-- + } + + val pinyin = StringBuilder() + for (j in PINYINS[offset].indices) { + if (PINYINS[offset][j] == 0.toByte()) break + pinyin.append(PINYINS[offset][j].toInt().toChar()) + } + token.target = pinyin.toString() + if (TextUtils.isEmpty(token.target)) { + token.type = Token.UNKNOWN + token.target = token.source + } + return token + } + + fun get(input: String?): ArrayList { + val tokens = ArrayList() + if (!hasChinaCollator || TextUtils.isEmpty(input)) { + return tokens + } + + val inputLength = input!!.length + val sb = StringBuilder() + var tokenType = Token.LATIN + + for (i in 0 until inputLength) { + val character = input[i] + when { + character == ' ' -> { + if (sb.isNotEmpty()) { + addToken(sb, tokens, tokenType) + } + } + character < 256.toChar() -> { + if (tokenType != Token.LATIN && sb.isNotEmpty()) { + addToken(sb, tokens, tokenType) + } + tokenType = Token.LATIN + sb.append(character) + } + else -> { + val t = getToken(character) + if (t.type == Token.PINYIN) { + if (sb.isNotEmpty()) { + addToken(sb, tokens, tokenType) + } + tokens.add(t) + tokenType = Token.PINYIN + } else { + if (tokenType != t.type && sb.isNotEmpty()) { + addToken(sb, tokens, tokenType) + } + tokenType = t.type + sb.append(character) + } + } + } + } + if (sb.isNotEmpty()) { + addToken(sb, tokens, tokenType) + } + return tokens + } + + private fun addToken(sb: StringBuilder, tokens: ArrayList, tokenType: Int) { + val str = sb.toString() + tokens.add(Token(tokenType, str, str)) + sb.setLength(0) + } + + fun toPinyinString(string: String?): String? { + if (string == null) { + return null + } + val sb = StringBuilder() + val tokens = get(string) + for (token in tokens) { + sb.append(token.target) + } + return sb.toString().lowercase() + } + + companion object { + private const val TAG = "HanziToPinyin" + private const val DEBUG = false + + val UNIHANS = charArrayOf( + '阿', '哎', '安', '肮', '凹', '八', + '挀', '扳', '邦', '勹', '陂', '奔', + '伻', '屄', '边', '灬', '憋', '汃', + '冫', '癶', '峬', '嚓', '偲', '参', + '仓', '撡', '冊', '嵾', '曽', '曾', + '層', '叉', '芆', '辿', '伥', '抄', + '车', '抻', '沈', '沉', '阷', '吃', + '充', '抽', '出', '欻', '揣', '巛', + '刅', '吹', '旾', '逴', '呲', '匆', + '凑', '粗', '汆', '崔', '邨', '搓', + '咑', '呆', '丹', '当', '刀', '嘚', + '扥', '灯', '氐', '嗲', '甸', '刁', + '爹', '丁', '丟', '东', '吺', '厾', + '耑', '襨', '吨', '多', '妸', '诶', + '奀', '鞥', '儿', '发', '帆', '匚', + '飞', '分', '丰', '覅', '仏', '紑', + '伕', '旮', '侅', '甘', '冈', '皋', + '戈', '给', '根', '刯', '工', '勾', + '估', '瓜', '乖', '关', '光', '归', + '丨', '呙', '哈', '咍', '佄', '夯', + '茠', '诃', '黒', '拫', '亨', '噷', + '叿', '齁', '乯', '花', '怀', '犿', + '巟', '灰', '昏', '吙', '丌', '加', + '戋', '江', '艽', '阶', '巾', '坕', + '冂', '丩', '凥', '姢', '噘', '军', + '咔', '开', '刊', '忼', '尻', '匼', + '肎', '劥', '空', '抠', '扝', '夸', + '蒯', '宽', '匡', '亏', '坤', '扩', + '垃', '来', '兰', '啷', '捞', '肋', + '勒', '崚', '刕', '俩', '奁', '良', + '撩', '列', '拎', '刢', '溜', '囖', + '龙', '瞜', '噜', '娈', '畧', '抡', + '罗', '呣', '妈', '埋', '嫚', '牤', + '猫', '么', '呅', '门', '甿', '咪', + '宀', '喵', '乜', '民', '名', '谬', + '摸', '哞', '毪', '嗯', '拏', '腉', + '囡', '囔', '孬', '疒', '娞', '恁', + '能', '妮', '拈', '嬢', '鸟', '捏', + '囜', '宁', '妞', '农', '羺', '奴', + '奻', '疟', '黁', '郍', '喔', '讴', + '妑', '拍', '眅', '乓', '抛', '呸', + '喷', '匉', '丕', '囨', '剽', '氕', + '姘', '乒', '钋', '剖', '仆', '七', + '掐', '千', '呛', '悄', '癿', '亲', + '狅', '芎', '丘', '区', '峑', '缺', + '夋', '呥', '穣', '娆', '惹', '人', + '扔', '日', '茸', '厹', '邚', '挼', + '堧', '婑', '瞤', '捼', '仨', '毢', + '三', '桒', '掻', '閪', '森', '僧', + '杀', '筛', '山', '伤', '弰', '奢', + '申', '莘', '敒', '升', '尸', '収', + '书', '刷', '衰', '闩', '双', '谁', + '吮', '说', '厶', '忪', '捜', '苏', + '狻', '夊', '孙', '唆', '他', '囼', + '坍', '汤', '夲', '忑', '熥', '剔', + '天', '旫', '帖', '厅', '囲', '偷', + '凸', '湍', '推', '吞', '乇', '穵', + '歪', '弯', '尣', '危', '昷', '翁', + '挝', '乌', '夕', '虲', '仚', '乡', + '灱', '些', '心', '星', '凶', '休', + '吁', '吅', '削', '坃', '丫', '恹', + '央', '幺', '倻', '一', '囙', '应', + '哟', '佣', '优', '扜', '囦', '曰', + '晕', '筠', '筼', '帀', '災', '兂', + '匨', '傮', '则', '贼', '怎', '増', + '扎', '捚', '沾', '张', '长', '長', + '佋', '蜇', '贞', '争', '之', '峙', + '庢', '中', '州', '朱', '抓', '拽', + '专', '妆', '隹', '宒', '卓', '乲', + '宗', '邹', '租', '钻', '厜', '尊', + '昨', '兙', '鿃', '鿄' + ) + + val PINYINS = arrayOf( + byteArrayOf(65, 0, 0, 0, 0, 0), byteArrayOf(65, 73, 0, 0, 0, 0), + byteArrayOf(65, 78, 0, 0, 0, 0), byteArrayOf(65, 78, 71, 0, 0, 0), + byteArrayOf(65, 79, 0, 0, 0, 0), byteArrayOf(66, 65, 0, 0, 0, 0), + byteArrayOf(66, 65, 73, 0, 0, 0), byteArrayOf(66, 65, 78, 0, 0, 0), + byteArrayOf(66, 65, 78, 71, 0, 0), byteArrayOf(66, 65, 79, 0, 0, 0), + byteArrayOf(66, 69, 73, 0, 0, 0), byteArrayOf(66, 69, 78, 0, 0, 0), + byteArrayOf(66, 69, 78, 71, 0, 0), byteArrayOf(66, 73, 0, 0, 0, 0), + byteArrayOf(66, 73, 65, 78, 0, 0), byteArrayOf(66, 73, 65, 79, 0, 0), + byteArrayOf(66, 73, 69, 0, 0, 0), byteArrayOf(66, 73, 78, 0, 0, 0), + byteArrayOf(66, 73, 78, 71, 0, 0), byteArrayOf(66, 79, 0, 0, 0, 0), + byteArrayOf(66, 85, 0, 0, 0, 0), byteArrayOf(67, 65, 0, 0, 0, 0), + byteArrayOf(67, 65, 73, 0, 0, 0), byteArrayOf(67, 65, 78, 0, 0, 0), + byteArrayOf(67, 65, 78, 71, 0, 0), byteArrayOf(67, 65, 79, 0, 0, 0), + byteArrayOf(67, 69, 0, 0, 0, 0), byteArrayOf(67, 69, 78, 0, 0, 0), + byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0), + byteArrayOf(67, 69, 78, 71, 0, 0), byteArrayOf(67, 72, 65, 0, 0, 0), + byteArrayOf(67, 72, 65, 73, 0, 0), byteArrayOf(67, 72, 65, 78, 0, 0), + byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(67, 72, 65, 79, 0, 0), + byteArrayOf(67, 72, 69, 0, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0), + byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(67, 72, 69, 78, 0, 0), + byteArrayOf(67, 72, 69, 78, 71, 0), byteArrayOf(67, 72, 73, 0, 0, 0), + byteArrayOf(67, 72, 79, 78, 71, 0), byteArrayOf(67, 72, 79, 85, 0, 0), + byteArrayOf(67, 72, 85, 0, 0, 0), byteArrayOf(67, 72, 85, 65, 0, 0), + byteArrayOf(67, 72, 85, 65, 73, 0), byteArrayOf(67, 72, 85, 65, 78, 0), + byteArrayOf(67, 72, 85, 65, 78, 71), byteArrayOf(67, 72, 85, 73, 0, 0), + byteArrayOf(67, 72, 85, 78, 0, 0), byteArrayOf(67, 72, 85, 79, 0, 0), + byteArrayOf(67, 73, 0, 0, 0, 0), byteArrayOf(67, 79, 78, 71, 0, 0), + byteArrayOf(67, 79, 85, 0, 0, 0), byteArrayOf(67, 85, 0, 0, 0, 0), + byteArrayOf(67, 85, 65, 78, 0, 0), byteArrayOf(67, 85, 73, 0, 0, 0), + byteArrayOf(67, 85, 78, 0, 0, 0), byteArrayOf(67, 85, 79, 0, 0, 0), + byteArrayOf(68, 65, 0, 0, 0, 0), byteArrayOf(68, 65, 73, 0, 0, 0), + byteArrayOf(68, 65, 78, 0, 0, 0), byteArrayOf(68, 65, 78, 71, 0, 0), + byteArrayOf(68, 65, 79, 0, 0, 0), byteArrayOf(68, 69, 0, 0, 0, 0), + byteArrayOf(68, 69, 78, 0, 0, 0), byteArrayOf(68, 69, 78, 71, 0, 0), + byteArrayOf(68, 73, 0, 0, 0, 0), byteArrayOf(68, 73, 65, 0, 0, 0), + byteArrayOf(68, 73, 65, 78, 0, 0), byteArrayOf(68, 73, 65, 79, 0, 0), + byteArrayOf(68, 73, 69, 0, 0, 0), byteArrayOf(68, 73, 78, 71, 0, 0), + byteArrayOf(68, 73, 85, 0, 0, 0), byteArrayOf(68, 79, 78, 71, 0, 0), + byteArrayOf(68, 79, 85, 0, 0, 0), byteArrayOf(68, 85, 0, 0, 0, 0), + byteArrayOf(68, 85, 65, 78, 0, 0), byteArrayOf(68, 85, 73, 0, 0, 0), + byteArrayOf(68, 85, 78, 0, 0, 0), byteArrayOf(68, 85, 79, 0, 0, 0), + byteArrayOf(69, 0, 0, 0, 0, 0), byteArrayOf(69, 73, 0, 0, 0, 0), + byteArrayOf(69, 78, 0, 0, 0, 0), byteArrayOf(69, 78, 71, 0, 0, 0), + byteArrayOf(69, 82, 0, 0, 0, 0), byteArrayOf(70, 65, 0, 0, 0, 0), + byteArrayOf(70, 65, 78, 0, 0, 0), byteArrayOf(70, 65, 78, 71, 0, 0), + byteArrayOf(70, 69, 73, 0, 0, 0), byteArrayOf(70, 69, 78, 0, 0, 0), + byteArrayOf(70, 69, 78, 71, 0, 0), byteArrayOf(70, 73, 65, 79, 0, 0), + byteArrayOf(70, 79, 0, 0, 0, 0), byteArrayOf(70, 79, 85, 0, 0, 0), + byteArrayOf(70, 85, 0, 0, 0, 0), byteArrayOf(71, 65, 0, 0, 0, 0), + byteArrayOf(71, 65, 73, 0, 0, 0), byteArrayOf(71, 65, 78, 0, 0, 0), + byteArrayOf(71, 65, 78, 71, 0, 0), byteArrayOf(71, 65, 79, 0, 0, 0), + byteArrayOf(71, 69, 0, 0, 0, 0), byteArrayOf(71, 69, 73, 0, 0, 0), + byteArrayOf(71, 69, 78, 0, 0, 0), byteArrayOf(71, 69, 78, 71, 0, 0), + byteArrayOf(71, 79, 78, 71, 0, 0), byteArrayOf(71, 79, 85, 0, 0, 0), + byteArrayOf(71, 85, 0, 0, 0, 0), byteArrayOf(71, 85, 65, 0, 0, 0), + byteArrayOf(71, 85, 65, 73, 0, 0), byteArrayOf(71, 85, 65, 78, 0, 0), + byteArrayOf(71, 85, 65, 78, 71, 0), byteArrayOf(71, 85, 73, 0, 0, 0), + byteArrayOf(71, 85, 78, 0, 0, 0), byteArrayOf(71, 85, 79, 0, 0, 0), + byteArrayOf(72, 65, 0, 0, 0, 0), byteArrayOf(72, 65, 73, 0, 0, 0), + byteArrayOf(72, 65, 78, 0, 0, 0), byteArrayOf(72, 65, 78, 71, 0, 0), + byteArrayOf(72, 65, 79, 0, 0, 0), byteArrayOf(72, 69, 0, 0, 0, 0), + byteArrayOf(72, 69, 73, 0, 0, 0), byteArrayOf(72, 69, 78, 0, 0, 0), + byteArrayOf(72, 69, 78, 71, 0, 0), byteArrayOf(72, 77, 0, 0, 0, 0), + byteArrayOf(72, 79, 78, 71, 0, 0), byteArrayOf(72, 79, 85, 0, 0, 0), + byteArrayOf(72, 85, 0, 0, 0, 0), byteArrayOf(72, 85, 65, 0, 0, 0), + byteArrayOf(72, 85, 65, 73, 0, 0), byteArrayOf(72, 85, 65, 78, 0, 0), + byteArrayOf(72, 85, 65, 78, 71, 0), byteArrayOf(72, 85, 73, 0, 0, 0), + byteArrayOf(72, 85, 78, 0, 0, 0), byteArrayOf(72, 85, 79, 0, 0, 0), + byteArrayOf(74, 73, 0, 0, 0, 0), byteArrayOf(74, 73, 65, 0, 0, 0), + byteArrayOf(74, 73, 65, 78, 0, 0), byteArrayOf(74, 73, 65, 78, 71, 0), + byteArrayOf(74, 73, 65, 79, 0, 0), byteArrayOf(74, 73, 69, 0, 0, 0), + byteArrayOf(74, 73, 78, 0, 0, 0), byteArrayOf(74, 73, 78, 71, 0, 0), + byteArrayOf(74, 73, 79, 78, 71, 0), byteArrayOf(74, 73, 85, 0, 0, 0), + byteArrayOf(74, 85, 0, 0, 0, 0), byteArrayOf(74, 85, 65, 78, 0, 0), + byteArrayOf(74, 85, 69, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0), + byteArrayOf(75, 65, 0, 0, 0, 0), byteArrayOf(75, 65, 73, 0, 0, 0), + byteArrayOf(75, 65, 78, 0, 0, 0), byteArrayOf(75, 65, 78, 71, 0, 0), + byteArrayOf(75, 65, 79, 0, 0, 0), byteArrayOf(75, 69, 0, 0, 0, 0), + byteArrayOf(75, 69, 78, 0, 0, 0), byteArrayOf(75, 69, 78, 71, 0, 0), + byteArrayOf(75, 79, 78, 71, 0, 0), byteArrayOf(75, 79, 85, 0, 0, 0), + byteArrayOf(75, 85, 0, 0, 0, 0), byteArrayOf(75, 85, 65, 0, 0, 0), + byteArrayOf(75, 85, 65, 73, 0, 0), byteArrayOf(75, 85, 65, 78, 0, 0), + byteArrayOf(75, 85, 65, 78, 71, 0), byteArrayOf(75, 85, 73, 0, 0, 0), + byteArrayOf(75, 85, 78, 0, 0, 0), byteArrayOf(75, 85, 79, 0, 0, 0), + byteArrayOf(76, 65, 0, 0, 0, 0), byteArrayOf(76, 65, 73, 0, 0, 0), + byteArrayOf(76, 65, 78, 0, 0, 0), byteArrayOf(76, 65, 78, 71, 0, 0), + byteArrayOf(76, 65, 79, 0, 0, 0), byteArrayOf(76, 69, 0, 0, 0, 0), + byteArrayOf(76, 69, 73, 0, 0, 0), byteArrayOf(76, 69, 78, 71, 0, 0), + byteArrayOf(76, 73, 0, 0, 0, 0), byteArrayOf(76, 73, 65, 0, 0, 0), + byteArrayOf(76, 73, 65, 78, 0, 0), byteArrayOf(76, 73, 65, 78, 71, 0), + byteArrayOf(76, 73, 65, 79, 0, 0), byteArrayOf(76, 73, 69, 0, 0, 0), + byteArrayOf(76, 73, 78, 0, 0, 0), byteArrayOf(76, 73, 78, 71, 0, 0), + byteArrayOf(76, 73, 85, 0, 0, 0), byteArrayOf(76, 79, 0, 0, 0, 0), + byteArrayOf(76, 79, 78, 71, 0, 0), byteArrayOf(76, 79, 85, 0, 0, 0), + byteArrayOf(76, 85, 0, 0, 0, 0), byteArrayOf(76, 85, 65, 78, 0, 0), + byteArrayOf(76, 85, 69, 0, 0, 0), byteArrayOf(76, 85, 78, 0, 0, 0), + byteArrayOf(76, 85, 79, 0, 0, 0), byteArrayOf(77, 0, 0, 0, 0, 0), + byteArrayOf(77, 65, 0, 0, 0, 0), byteArrayOf(77, 65, 73, 0, 0, 0), + byteArrayOf(77, 65, 78, 0, 0, 0), byteArrayOf(77, 65, 78, 71, 0, 0), + byteArrayOf(77, 65, 79, 0, 0, 0), byteArrayOf(77, 69, 0, 0, 0, 0), + byteArrayOf(77, 69, 73, 0, 0, 0), byteArrayOf(77, 69, 78, 0, 0, 0), + byteArrayOf(77, 69, 78, 71, 0, 0), byteArrayOf(77, 73, 0, 0, 0, 0), + byteArrayOf(77, 73, 65, 78, 0, 0), byteArrayOf(77, 73, 65, 79, 0, 0), + byteArrayOf(77, 73, 69, 0, 0, 0), byteArrayOf(77, 73, 78, 0, 0, 0), + byteArrayOf(77, 73, 78, 71, 0, 0), byteArrayOf(77, 73, 85, 0, 0, 0), + byteArrayOf(77, 79, 0, 0, 0, 0), byteArrayOf(77, 79, 85, 0, 0, 0), + byteArrayOf(77, 85, 0, 0, 0, 0), byteArrayOf(78, 0, 0, 0, 0, 0), + byteArrayOf(78, 65, 0, 0, 0, 0), byteArrayOf(78, 65, 73, 0, 0, 0), + byteArrayOf(78, 65, 78, 0, 0, 0), byteArrayOf(78, 65, 78, 71, 0, 0), + byteArrayOf(78, 65, 79, 0, 0, 0), byteArrayOf(78, 69, 0, 0, 0, 0), + byteArrayOf(78, 69, 73, 0, 0, 0), byteArrayOf(78, 69, 78, 0, 0, 0), + byteArrayOf(78, 69, 78, 71, 0, 0), byteArrayOf(78, 73, 0, 0, 0, 0), + byteArrayOf(78, 73, 65, 78, 0, 0), byteArrayOf(78, 73, 65, 78, 71, 0), + byteArrayOf(78, 73, 65, 79, 0, 0), byteArrayOf(78, 73, 69, 0, 0, 0), + byteArrayOf(78, 73, 78, 0, 0, 0), byteArrayOf(78, 73, 78, 71, 0, 0), + byteArrayOf(78, 73, 85, 0, 0, 0), byteArrayOf(78, 79, 78, 71, 0, 0), + byteArrayOf(78, 79, 85, 0, 0, 0), byteArrayOf(78, 85, 0, 0, 0, 0), + byteArrayOf(78, 85, 65, 78, 0, 0), byteArrayOf(78, 85, 69, 0, 0, 0), + byteArrayOf(78, 85, 78, 0, 0, 0), byteArrayOf(78, 85, 79, 0, 0, 0), + byteArrayOf(79, 0, 0, 0, 0, 0), byteArrayOf(79, 85, 0, 0, 0, 0), + byteArrayOf(80, 65, 0, 0, 0, 0), byteArrayOf(80, 65, 73, 0, 0, 0), + byteArrayOf(80, 65, 78, 0, 0, 0), byteArrayOf(80, 65, 78, 71, 0, 0), + byteArrayOf(80, 65, 79, 0, 0, 0), byteArrayOf(80, 69, 73, 0, 0, 0), + byteArrayOf(80, 69, 78, 0, 0, 0), byteArrayOf(80, 69, 78, 71, 0, 0), + byteArrayOf(80, 73, 0, 0, 0, 0), byteArrayOf(80, 73, 65, 78, 0, 0), + byteArrayOf(80, 73, 65, 79, 0, 0), byteArrayOf(80, 73, 69, 0, 0, 0), + byteArrayOf(80, 73, 78, 0, 0, 0), byteArrayOf(80, 73, 78, 71, 0, 0), + byteArrayOf(80, 79, 0, 0, 0, 0), byteArrayOf(80, 79, 85, 0, 0, 0), + byteArrayOf(80, 85, 0, 0, 0, 0), byteArrayOf(81, 73, 0, 0, 0, 0), + byteArrayOf(81, 73, 65, 0, 0, 0), byteArrayOf(81, 73, 65, 78, 0, 0), + byteArrayOf(81, 73, 65, 78, 71, 0), byteArrayOf(81, 73, 65, 79, 0, 0), + byteArrayOf(81, 73, 69, 0, 0, 0), byteArrayOf(81, 73, 78, 0, 0, 0), + byteArrayOf(81, 73, 78, 71, 0, 0), byteArrayOf(81, 73, 79, 78, 71, 0), + byteArrayOf(81, 73, 85, 0, 0, 0), byteArrayOf(81, 85, 0, 0, 0, 0), + byteArrayOf(81, 85, 65, 78, 0, 0), byteArrayOf(81, 85, 69, 0, 0, 0), + byteArrayOf(81, 85, 78, 0, 0, 0), byteArrayOf(82, 65, 78, 0, 0, 0), + byteArrayOf(82, 65, 78, 71, 0, 0), byteArrayOf(82, 65, 79, 0, 0, 0), + byteArrayOf(82, 69, 0, 0, 0, 0), byteArrayOf(82, 69, 78, 0, 0, 0), + byteArrayOf(82, 69, 78, 71, 0, 0), byteArrayOf(82, 73, 0, 0, 0, 0), + byteArrayOf(82, 79, 78, 71, 0, 0), byteArrayOf(82, 79, 85, 0, 0, 0), + byteArrayOf(82, 85, 0, 0, 0, 0), byteArrayOf(82, 85, 65, 0, 0, 0), + byteArrayOf(82, 85, 65, 78, 0, 0), byteArrayOf(82, 85, 73, 0, 0, 0), + byteArrayOf(82, 85, 78, 0, 0, 0), byteArrayOf(82, 85, 79, 0, 0, 0), + byteArrayOf(83, 65, 0, 0, 0, 0), byteArrayOf(83, 65, 73, 0, 0, 0), + byteArrayOf(83, 65, 78, 0, 0, 0), byteArrayOf(83, 65, 78, 71, 0, 0), + byteArrayOf(83, 65, 79, 0, 0, 0), byteArrayOf(83, 69, 0, 0, 0, 0), + byteArrayOf(83, 69, 78, 0, 0, 0), byteArrayOf(83, 69, 78, 71, 0, 0), + byteArrayOf(83, 72, 65, 0, 0, 0), byteArrayOf(83, 72, 65, 73, 0, 0), + byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(83, 72, 65, 78, 71, 0), + byteArrayOf(83, 72, 65, 79, 0, 0), byteArrayOf(83, 72, 69, 0, 0, 0), + byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(88, 73, 78, 0, 0, 0), + byteArrayOf(83, 72, 69, 78, 0, 0), byteArrayOf(83, 72, 69, 78, 71, 0), + byteArrayOf(83, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 79, 85, 0, 0), + byteArrayOf(83, 72, 85, 0, 0, 0), byteArrayOf(83, 72, 85, 65, 0, 0), + byteArrayOf(83, 72, 85, 65, 73, 0), byteArrayOf(83, 72, 85, 65, 78, 0), + byteArrayOf(83, 72, 85, 65, 78, 71), byteArrayOf(83, 72, 85, 73, 0, 0), + byteArrayOf(83, 72, 85, 78, 0, 0), byteArrayOf(83, 72, 85, 79, 0, 0), + byteArrayOf(83, 73, 0, 0, 0, 0), byteArrayOf(83, 79, 78, 71, 0, 0), + byteArrayOf(83, 79, 85, 0, 0, 0), byteArrayOf(83, 85, 0, 0, 0, 0), + byteArrayOf(83, 85, 65, 78, 0, 0), byteArrayOf(83, 85, 73, 0, 0, 0), + byteArrayOf(83, 85, 78, 0, 0, 0), byteArrayOf(83, 85, 79, 0, 0, 0), + byteArrayOf(84, 65, 0, 0, 0, 0), byteArrayOf(84, 65, 73, 0, 0, 0), + byteArrayOf(84, 65, 78, 0, 0, 0), byteArrayOf(84, 65, 78, 71, 0, 0), + byteArrayOf(84, 65, 79, 0, 0, 0), byteArrayOf(84, 69, 0, 0, 0, 0), + byteArrayOf(84, 69, 78, 71, 0, 0), byteArrayOf(84, 73, 0, 0, 0, 0), + byteArrayOf(84, 73, 65, 78, 0, 0), byteArrayOf(84, 73, 65, 79, 0, 0), + byteArrayOf(84, 73, 69, 0, 0, 0), byteArrayOf(84, 73, 78, 71, 0, 0), + byteArrayOf(84, 79, 78, 71, 0, 0), byteArrayOf(84, 79, 85, 0, 0, 0), + byteArrayOf(84, 85, 0, 0, 0, 0), byteArrayOf(84, 85, 65, 78, 0, 0), + byteArrayOf(84, 85, 73, 0, 0, 0), byteArrayOf(84, 85, 78, 0, 0, 0), + byteArrayOf(84, 85, 79, 0, 0, 0), byteArrayOf(87, 65, 0, 0, 0, 0), + byteArrayOf(87, 65, 73, 0, 0, 0), byteArrayOf(87, 65, 78, 0, 0, 0), + byteArrayOf(87, 65, 78, 71, 0, 0), byteArrayOf(87, 69, 73, 0, 0, 0), + byteArrayOf(87, 69, 78, 0, 0, 0), byteArrayOf(87, 69, 78, 71, 0, 0), + byteArrayOf(87, 79, 0, 0, 0, 0), byteArrayOf(87, 85, 0, 0, 0, 0), + byteArrayOf(88, 73, 0, 0, 0, 0), byteArrayOf(88, 73, 65, 0, 0, 0), + byteArrayOf(88, 73, 65, 78, 0, 0), byteArrayOf(88, 73, 65, 78, 71, 0), + byteArrayOf(88, 73, 65, 79, 0, 0), byteArrayOf(88, 73, 69, 0, 0, 0), + byteArrayOf(88, 73, 78, 0, 0, 0), byteArrayOf(88, 73, 78, 71, 0, 0), + byteArrayOf(88, 73, 79, 78, 71, 0), byteArrayOf(88, 73, 85, 0, 0, 0), + byteArrayOf(88, 85, 0, 0, 0, 0), byteArrayOf(88, 85, 65, 78, 0, 0), + byteArrayOf(88, 85, 69, 0, 0, 0), byteArrayOf(88, 85, 78, 0, 0, 0), + byteArrayOf(89, 65, 0, 0, 0, 0), byteArrayOf(89, 65, 78, 0, 0, 0), + byteArrayOf(89, 65, 78, 71, 0, 0), byteArrayOf(89, 65, 79, 0, 0, 0), + byteArrayOf(89, 69, 0, 0, 0, 0), byteArrayOf(89, 73, 0, 0, 0, 0), + byteArrayOf(89, 73, 78, 0, 0, 0), byteArrayOf(89, 73, 78, 71, 0, 0), + byteArrayOf(89, 79, 0, 0, 0, 0), byteArrayOf(89, 79, 78, 71, 0, 0), + byteArrayOf(89, 79, 85, 0, 0, 0), byteArrayOf(89, 85, 0, 0, 0, 0), + byteArrayOf(89, 85, 65, 78, 0, 0), byteArrayOf(89, 85, 69, 0, 0, 0), + byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(74, 85, 78, 0, 0, 0), + byteArrayOf(89, 85, 78, 0, 0, 0), byteArrayOf(90, 65, 0, 0, 0, 0), + byteArrayOf(90, 65, 73, 0, 0, 0), byteArrayOf(90, 65, 78, 0, 0, 0), + byteArrayOf(90, 65, 78, 71, 0, 0), byteArrayOf(90, 65, 79, 0, 0, 0), + byteArrayOf(90, 69, 0, 0, 0, 0), byteArrayOf(90, 69, 73, 0, 0, 0), + byteArrayOf(90, 69, 78, 0, 0, 0), byteArrayOf(90, 69, 78, 71, 0, 0), + byteArrayOf(90, 72, 65, 0, 0, 0), byteArrayOf(90, 72, 65, 73, 0, 0), + byteArrayOf(90, 72, 65, 78, 0, 0), byteArrayOf(90, 72, 65, 78, 71, 0), + byteArrayOf(67, 72, 65, 78, 71, 0), byteArrayOf(90, 72, 65, 78, 71, 0), + byteArrayOf(90, 72, 65, 79, 0, 0), byteArrayOf(90, 72, 69, 0, 0, 0), + byteArrayOf(90, 72, 69, 78, 0, 0), byteArrayOf(90, 72, 69, 78, 71, 0), + byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(83, 72, 73, 0, 0, 0), + byteArrayOf(90, 72, 73, 0, 0, 0), byteArrayOf(90, 72, 79, 78, 71, 0), + byteArrayOf(90, 72, 79, 85, 0, 0), byteArrayOf(90, 72, 85, 0, 0, 0), + byteArrayOf(90, 72, 85, 65, 0, 0), byteArrayOf(90, 72, 85, 65, 73, 0), + byteArrayOf(90, 72, 85, 65, 78, 0), byteArrayOf(90, 72, 85, 65, 78, 71), + byteArrayOf(90, 72, 85, 73, 0, 0), byteArrayOf(90, 72, 85, 78, 0, 0), + byteArrayOf(90, 72, 85, 79, 0, 0), byteArrayOf(90, 73, 0, 0, 0, 0), + byteArrayOf(90, 79, 78, 71, 0, 0), byteArrayOf(90, 79, 85, 0, 0, 0), + byteArrayOf(90, 85, 0, 0, 0, 0), byteArrayOf(90, 85, 65, 78, 0, 0), + byteArrayOf(90, 85, 73, 0, 0, 0), byteArrayOf(90, 85, 78, 0, 0, 0), + byteArrayOf(90, 85, 79, 0, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0), + byteArrayOf(83, 72, 65, 78, 0, 0), byteArrayOf(0, 0, 0, 0, 0, 0) + ) + + private const val FIRST_PINYIN_UNIHAN = "阿" + private const val LAST_PINYIN_UNIHAN = "鿿" + + private val COLLATOR: Collator = Collator.getInstance(Locale.CHINA) + + private var sInstance: HanziToPinyin? = null + + fun getInstance(): HanziToPinyin { + synchronized(HanziToPinyin::class.java) { + if (sInstance != null) { + return sInstance!! + } + + val locale = Collator.getAvailableLocales() + for (value in locale) { + if (value == Locale.CHINA || value.language.contains("zh")) { + if (DEBUG) { + Log.d(TAG, "Self validation. Result: ${doSelfValidation()}") + } + sInstance = HanziToPinyin(true) + return sInstance!! + } + } + + if (sInstance == null) { + if (Locale.CHINA == Locale.getDefault()) { + sInstance = HanziToPinyin(true) + return sInstance!! + } + } + + Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled") + sInstance = HanziToPinyin(false) + return sInstance!! + } + } + + private fun doSelfValidation(): Boolean { + val lastChar = UNIHANS[0] + var lastString = lastChar.toString() + for (c in UNIHANS) { + if (lastChar == c) { + continue + } + val curString = c.toString() + val cmp = COLLATOR.compare(lastString, curString) + if (cmp >= 0) { + Log.e( + TAG, + "Internal error in Unihan table. The last string \"$lastString\" " + + "is greater than current string \"$curString\"." + ) + return false + } + lastString = curString + } + return true + } + } +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt index 91f66d8f..f1cb02ed 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt @@ -105,7 +105,7 @@ class ModuleViewModel : ViewModel() { ).thenBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id) modules.filter { it.id.contains(search, true) || it.name.contains(search, true) || HanziToPinyin.getInstance() - .toPinyinString(it.name).contains(search, true) + .toPinyinString(it.name)?.contains(search, true) == true }.sortedWith(comparator).also { isRefreshing = false } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt index 16cedc20..19e9ab5e 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt @@ -352,7 +352,7 @@ class SuperUserViewModel : ViewModel() { group.apps.any { app -> app.label.contains(search, true) || app.packageName.contains(search, true) || - HanziToPinyin.getInstance().toPinyinString(app.label).contains(search, true) + HanziToPinyin.getInstance().toPinyinString(app.label)?.contains(search, true) == true } }.filter { group -> group.uid == 2000 || showSystemApps || diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.java b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.java deleted file mode 100644 index 495be9f2..00000000 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.sukisu.ultra.ui.webui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.LruCache; -import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel; - -public class AppIconUtil { - // Limit cache size to 200 icons - private static final int CACHE_SIZE = 200; - private static final LruCache iconCache = new LruCache<>(CACHE_SIZE); - - public static synchronized Bitmap loadAppIconSync(Context context, String packageName, int sizePx) { - Bitmap cached = iconCache.get(packageName); - if (cached != null) return cached; - - try { - Drawable drawable = SuperUserViewModel.getAppIconDrawable(context, packageName); - if (drawable == null) { - return null; - } - Bitmap raw = drawableToBitmap(drawable, sizePx); - Bitmap icon = Bitmap.createScaledBitmap(raw, sizePx, sizePx, true); - if (raw != icon) raw.recycle(); - iconCache.put(packageName, icon); - return icon; - } catch (Exception e) { - return null; - } - } - - private static Bitmap drawableToBitmap(Drawable drawable, int size) { - if (drawable instanceof BitmapDrawable) return ((BitmapDrawable) drawable).getBitmap(); - - int width = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : size; - int height = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : size; - - Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bmp); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bmp; - } -} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.kt new file mode 100644 index 00000000..3ca47b1a --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/AppIconUtil.kt @@ -0,0 +1,47 @@ +package com.sukisu.ultra.ui.webui + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.LruCache +import androidx.core.graphics.createBitmap +import androidx.core.graphics.scale +import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel.Companion.getAppIconDrawable + +object AppIconUtil { + // Limit cache size to 200 icons + private const val CACHE_SIZE = 200 + private val iconCache = LruCache(CACHE_SIZE) + + @Synchronized + fun loadAppIconSync(context: Context, packageName: String, sizePx: Int): Bitmap? { + val cached = iconCache.get(packageName) + if (cached != null) return cached + + try { + val drawable = getAppIconDrawable(context, packageName) ?: return null + val raw = drawableToBitmap(drawable, sizePx) + val icon = raw.scale(sizePx, sizePx) + if (raw != icon) raw.recycle() + iconCache.put(packageName, icon) + return icon + } catch (_: Exception) { + return null + } + } + + private fun drawableToBitmap(drawable: Drawable, size: Int): Bitmap { + if (drawable is BitmapDrawable) return drawable.bitmap + + val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else size + val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else size + + val bmp = createBitmap(width, height) + val canvas = Canvas(bmp) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bmp + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.java b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.java deleted file mode 100644 index 5a801039..00000000 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.sukisu.ultra.ui.webui; - -import java.net.URLConnection; - -class MimeUtil { - - public static String getMimeFromFileName(String fileName) { - if (fileName == null) { - return null; - } - - // Copying the logic and mapping that Chromium follows. - // First we check against the OS (this is a limited list by default) - // but app developers can extend this. - // We then check against a list of hardcoded mime types above if the - // OS didn't provide a result. - String mimeType = URLConnection.guessContentTypeFromName(fileName); - - if (mimeType != null) { - return mimeType; - } - - return guessHardcodedMime(fileName); - } - - // We should keep this map in sync with the lists under - // //net/base/mime_util.cc in Chromium. - // A bunch of the mime types don't really apply to Android land - // like word docs so feel free to filter out where necessary. - private static String guessHardcodedMime(String fileName) { - int finalFullStop = fileName.lastIndexOf('.'); - if (finalFullStop == -1) { - return null; - } - - final String extension = fileName.substring(finalFullStop + 1).toLowerCase(); - - return switch (extension) { - case "webm" -> "video/webm"; - case "mpeg", "mpg" -> "video/mpeg"; - case "mp3" -> "audio/mpeg"; - case "wasm" -> "application/wasm"; - case "xhtml", "xht", "xhtm" -> "application/xhtml+xml"; - case "flac" -> "audio/flac"; - case "ogg", "oga", "opus" -> "audio/ogg"; - case "wav" -> "audio/wav"; - case "m4a" -> "audio/x-m4a"; - case "gif" -> "image/gif"; - case "jpeg", "jpg", "jfif", "pjpeg", "pjp" -> "image/jpeg"; - case "png" -> "image/png"; - case "apng" -> "image/apng"; - case "svg", "svgz" -> "image/svg+xml"; - case "webp" -> "image/webp"; - case "mht", "mhtml" -> "multipart/related"; - case "css" -> "text/css"; - case "html", "htm", "shtml", "shtm", "ehtml" -> "text/html"; - case "js", "mjs" -> "application/javascript"; - case "xml" -> "text/xml"; - case "mp4", "m4v" -> "video/mp4"; - case "ogv", "ogm" -> "video/ogg"; - case "ico" -> "image/x-icon"; - case "woff" -> "application/font-woff"; - case "gz", "tgz" -> "application/gzip"; - case "json" -> "application/json"; - case "pdf" -> "application/pdf"; - case "zip" -> "application/zip"; - case "bmp" -> "image/bmp"; - case "tiff", "tif" -> "image/tiff"; - default -> null; - }; - } -} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.kt new file mode 100644 index 00000000..b9adcfa2 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/MimeUtil.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.sukisu.ultra.ui.webui + +import java.net.URLConnection + +internal object MimeUtil { + fun getMimeFromFileName(fileName: String?): String? { + if (fileName == null) { + return null + } + + val mimeType = URLConnection.guessContentTypeFromName(fileName) + if (mimeType != null) { + return mimeType + } + + return guessHardcodedMime(fileName) + } + + private fun guessHardcodedMime(fileName: String): String? { + val finalFullStop = fileName.lastIndexOf('.') + if (finalFullStop == -1) { + return null + } + + val extension = fileName.substring(finalFullStop + 1).lowercase() + + return when (extension) { + "webm" -> "video/webm" + "mpeg", "mpg" -> "video/mpeg" + "mp3" -> "audio/mpeg" + "wasm" -> "application/wasm" + "xhtml", "xht", "xhtm" -> "application/xhtml+xml" + "flac" -> "audio/flac" + "ogg", "oga", "opus" -> "audio/ogg" + "wav" -> "audio/wav" + "m4a" -> "audio/x-m4a" + "gif" -> "image/gif" + "jpeg", "jpg", "jfif", "pjpeg", "pjp" -> "image/jpeg" + "png" -> "image/png" + "apng" -> "image/apng" + "svg", "svgz" -> "image/svg+xml" + "webp" -> "image/webp" + "mht", "mhtml" -> "multipart/related" + "css" -> "text/css" + "html", "htm", "shtml", "shtm", "ehtml" -> "text/html" + "js", "mjs" -> "application/javascript" + "xml" -> "text/xml" + "mp4", "m4v" -> "video/mp4" + "ogv", "ogm" -> "video/ogg" + "ico" -> "image/x-icon" + "woff" -> "application/font-woff" + "gz", "tgz" -> "application/gzip" + "json" -> "application/json" + "pdf" -> "application/pdf" + "zip" -> "application/zip" + "bmp" -> "image/bmp" + "tiff", "tif" -> "image/tiff" + else -> null + } + } +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.java b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.java deleted file mode 100644 index 263aa953..00000000 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.sukisu.ultra.ui.webui; - -import android.content.Context; -import android.util.Log; -import android.webkit.WebResourceResponse; -import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; -import androidx.webkit.WebViewAssetLoader; -import com.topjohnwu.superuser.Shell; -import com.topjohnwu.superuser.io.SuFile; -import com.topjohnwu.superuser.io.SuFileInputStream; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -/** - * Handler class to open files from file system by root access - * For more information about android storage please refer to - * Android Developers - * Docs: Data and file storage overview. - *

- * To avoid leaking user or app data to the web, make sure to choose {@code directory} - * carefully, and assume any file under this directory could be accessed by any web page subject - * to same-origin rules. - *

- * A typical usage would be like: - *

- * File publicDir = new File(context.getFilesDir(), "public");
- * // Host "files/public/" in app's data directory under:
- * // http://appassets.androidplatform.net/public/...
- * WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
- *          .addPathHandler("/public/", new InternalStoragePathHandler(context, publicDir))
- *          .build();
- * 
- */ -public final class SuFilePathHandler implements WebViewAssetLoader.PathHandler { - private static final String TAG = "SuFilePathHandler"; - - /** - * Default value to be used as MIME type if guessing MIME type failed. - */ - public static final String DEFAULT_MIME_TYPE = "text/plain"; - - /** - * Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this - * handler. They are forbidden as they often contain sensitive information. - *

- * Note: Any future addition to this list will be considered breaking changes to the API. - */ - private static final String[] FORBIDDEN_DATA_DIRS = - new String[] {"/data/data", "/data/system"}; - - @NonNull - private final File mDirectory; - - private final Shell mShell; - - /** - * Creates PathHandler for app's internal storage. - * The directory to be exposed must be inside either the application's internal data - * directory {@link Context#getDataDir} or cache directory {@link Context#getCacheDir}. - * External storage is not supported for security reasons, as other apps with - * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} may be able to modify the - * files. - *

- * Exposing the entire data or cache directory is not permitted, to avoid accidentally - * exposing sensitive application files to the web. Certain existing subdirectories of - * {@link Context#getDataDir} are also not permitted as they are often sensitive. - * These files are ({@code "app_webview/"}, {@code "databases/"}, {@code "lib/"}, - * {@code "shared_prefs/"} and {@code "code_cache/"}). - *

- * The application should typically use a dedicated subdirectory for the files it intends to - * expose and keep them separate from other files. - * - * @param directory the absolute path of the exposed app internal storage directory from - * which files can be loaded. - * @throws IllegalArgumentException if the directory is not allowed. - */ - public SuFilePathHandler(@NonNull File directory, Shell rootShell) { - try { - mDirectory = new File(getCanonicalDirPath(directory)); - if (!isAllowedInternalStorageDir()) { - throw new IllegalArgumentException("The given directory \"" + directory - + "\" doesn't exist under an allowed app internal storage directory"); - } - mShell = rootShell; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to resolve the canonical path for the given directory: " - + directory.getPath(), e); - } - } - - private boolean isAllowedInternalStorageDir() throws IOException { - String dir = getCanonicalDirPath(mDirectory); - - for (String forbiddenPath : FORBIDDEN_DATA_DIRS) { - if (dir.startsWith(forbiddenPath)) { - return false; - } - } - return true; - } - - /** - * Opens the requested file from the exposed data directory. - *

- * The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the - * requested file cannot be found or is outside the mounted directory a - * {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be - * returned instead of {@code null}. This saves the time of falling back to network and - * trying to resolve a path that doesn't exist. A {@link WebResourceResponse} with - * {@code null} {@link InputStream} will be received as an HTTP response with status code - * {@code 404} and no body. - *

- * The MIME type for the file will be determined from the file's extension using - * {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that - * files are named using standard file extensions. If the file does not have a - * recognised extension, {@code "text/plain"} will be used by default. - * - * @param path the suffix path to be handled. - * @return {@link WebResourceResponse} for the requested file. - */ - @Override - @WorkerThread - @NonNull - public WebResourceResponse handle(@NonNull String path) { - try { - File file = getCanonicalFileIfChild(mDirectory, path); - if (file != null) { - InputStream is = openFile(file, mShell); - String mimeType = guessMimeType(path); - return new WebResourceResponse(mimeType, null, is); - } else { - Log.e(TAG, String.format( - "The requested file: %s is outside the mounted directory: %s", path, - mDirectory)); - } - } catch (IOException e) { - Log.e(TAG, "Error opening the requested path: " + path, e); - } - return new WebResourceResponse(null, null, null); - } - - public static String getCanonicalDirPath(@NonNull File file) throws IOException { - String canonicalPath = file.getCanonicalPath(); - if (!canonicalPath.endsWith("/")) canonicalPath += "/"; - return canonicalPath; - } - - public static File getCanonicalFileIfChild(@NonNull File parent, @NonNull String child) - throws IOException { - String parentCanonicalPath = getCanonicalDirPath(parent); - String childCanonicalPath = new File(parent, child).getCanonicalPath(); - if (childCanonicalPath.startsWith(parentCanonicalPath)) { - return new File(childCanonicalPath); - } - return null; - } - - @NonNull - private static InputStream handleSvgzStream(@NonNull String path, - @NonNull InputStream stream) throws IOException { - return path.endsWith(".svgz") ? new GZIPInputStream(stream) : stream; - } - - public static InputStream openFile(@NonNull File file, @NonNull Shell shell) throws IOException { - SuFile suFile = new SuFile(file.getAbsolutePath()); - suFile.setShell(shell); - InputStream fis = SuFileInputStream.open(suFile); - return handleSvgzStream(file.getPath(), fis); - } - - /** - * Use {@link MimeUtil#getMimeFromFileName} to guess MIME type or return the - * {@link #DEFAULT_MIME_TYPE} if it can't guess. - * - * @param filePath path of the file to guess its MIME type. - * @return MIME type guessed from file extension or {@link #DEFAULT_MIME_TYPE}. - */ - @NonNull - public static String guessMimeType(@NonNull String filePath) { - String mimeType = MimeUtil.getMimeFromFileName(filePath); - return mimeType == null ? DEFAULT_MIME_TYPE : mimeType; - } -} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.kt new file mode 100644 index 00000000..ab97e636 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuFilePathHandler.kt @@ -0,0 +1,108 @@ +package com.sukisu.ultra.ui.webui + +import android.util.Log +import android.webkit.WebResourceResponse +import androidx.annotation.WorkerThread +import androidx.webkit.WebViewAssetLoader +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.io.SuFile +import com.topjohnwu.superuser.io.SuFileInputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.util.zip.GZIPInputStream + +class SuFilePathHandler( + directory: File, + private val mShell: Shell +) : WebViewAssetLoader.PathHandler { + + private val mDirectory: File + + init { + try { + mDirectory = File(getCanonicalDirPath(directory)) + if (!isAllowedInternalStorageDir()) { + throw IllegalArgumentException( + "The given directory \"$directory\" doesn't exist under an allowed app internal storage directory" + ) + } + } catch (e: IOException) { + throw IllegalArgumentException( + "Failed to resolve the canonical path for the given directory: ${directory.path}", + e + ) + } + } + + private fun isAllowedInternalStorageDir(): Boolean { + return try { + val dir = getCanonicalDirPath(mDirectory) + FORBIDDEN_DATA_DIRS.none { dir.startsWith(it) } + } catch (_: IOException) { + false + } + } + + @WorkerThread + override fun handle(path: String): WebResourceResponse { + try { + val file = getCanonicalFileIfChild(mDirectory, path) + if (file != null) { + val inputStream = openFile(file, mShell) + val mimeType = guessMimeType(path) + return WebResourceResponse(mimeType, null, inputStream) + } else { + Log.e( + TAG, + "The requested file: $path is outside the mounted directory: $mDirectory" + ) + } + } catch (e: IOException) { + Log.e(TAG, "Error opening the requested path: $path", e) + } + return WebResourceResponse(null, null, null) + } + + companion object { + private const val TAG = "SuFilePathHandler" + const val DEFAULT_MIME_TYPE = "text/plain" + + private val FORBIDDEN_DATA_DIRS = arrayOf("/data/data", "/data/system") + + fun getCanonicalDirPath(file: File): String { + val canonicalPath = file.canonicalPath + return if (!canonicalPath.endsWith("/")) "$canonicalPath/" else canonicalPath + } + + fun getCanonicalFileIfChild(parent: File, child: String): File? { + return try { + val parentCanonicalPath = getCanonicalDirPath(parent) + val childCanonicalPath = File(parent, child).canonicalPath + if (childCanonicalPath.startsWith(parentCanonicalPath)) { + File(childCanonicalPath) + } else { + null + } + } catch (_: IOException) { + null + } + } + + private fun handleSvgzStream(path: String, stream: InputStream): InputStream { + return if (path.endsWith(".svgz")) GZIPInputStream(stream) else stream + } + + fun openFile(file: File, shell: Shell): InputStream { + val suFile = SuFile(file.absolutePath).apply { + setShell(shell) + } + val fis = SuFileInputStream.open(suFile) + return handleSvgzStream(file.path, fis) + } + + fun guessMimeType(filePath: String): String { + return MimeUtil.getMimeFromFileName(filePath) ?: DEFAULT_MIME_TYPE + } + } +}