manger: fmt code

This commit is contained in:
ShirkNeko
2025-11-17 12:20:45 +08:00
parent d45676f059
commit f48d2e6cac
10 changed files with 756 additions and 897 deletions

View File

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

View File

@@ -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<Token> {
val tokens = ArrayList<Token>()
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<Token>, 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
}
}
}

View File

@@ -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
}

View File

@@ -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 ||

View File

@@ -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<String, Bitmap> 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;
}
}

View File

@@ -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<String?, Bitmap?>(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
}
}

View File

@@ -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;
};
}
}

View File

@@ -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
}
}
}

View File

@@ -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
* <a href="https://developer.android.com/guide/topics/data/data-storage">Android Developers
* Docs: Data and file storage overview</a>.
* <p class="note">
* 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.
* <p>
* A typical usage would be like:
* <pre class="prettyprint">
* 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();
* </pre>
*/
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.
* <p class="note">
* 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.
* <p>
* 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/"}).
* <p>
* 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.
* <p>
* 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.
* <p class="note">
* 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;
}
}

View File

@@ -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
}
}
}