Android AOSP定制默认输入法为讯飞输入法
前言:
最近在公司的项目中发现默认的输入法非常不好用,而且默认输入法中英文切换非常麻烦,被用户吐槽定制的AOSP镜像体验不好,于是查找资料,研究了一番,尝试了搜狗、百度和讯飞三大著名的输入法,最后选择了适合我们的讯飞输入法.本文就讲解一下在AOSP中如何定制默认的输入法.
1.下载讯飞输入法apk:
建议去官网下载,如果官网下载的有问题去apkmirror这个网站下载,基本上都是正版可以使用的apk,我的gms服务三件套都是在此网站下载的,都是可以正常使用》
https://www.apkmirror.com/
2.把apk放到系统目录下:
具体目录如下:
vendor/common/android/gms/iFlyInPut/iFlyInPut.apk:system/priv-app/iFlyInPut/iFlyInPut.apk
3.这里有2种配置方式:
3.1 第1种方式以mk文件方式:
3.2 新建一个文件目录和mk文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := GoogleLoginService
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := APPS
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
include $(BUILD_PREBUILT)
3.3 把apk放到此文件同名的目录下:
4.第2种方式
4.1 新建一个放讯飞输入法的目录:
把下载好的讯飞输入法apk放到此文件目录下.
4.2 添加apk配置到系统目录:
在product_extra文件中添加如下配置:一般AOSP源码都有此目录,做过AOSP定制的小伙伴应该都明白,这里就不过多讲解了,后面讲解GMS定制的时候在具体讲解每个目录和文件的含义.
vendor/common/android/gms/iFlyInPut/iFlyInPut.apk:system/priv-app/iFlyInPut/iFlyInPut.apk
4.3 完整的文件配置如下:
这里有几个比较重要的应用:
- Google服务框架: GoogleServicesFramework;
- Google核心服务:PrebuiltGmsCore;
- GooglePlay:Phonesky;(google商店)
#
# Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
# Description: product extra config
#
IS_GMS_LUNCH = true
PRODUCT_PACKAGES += \
GoogleServicesFramework \
PrebuiltGmsCore \
GoogleLoginService \
Phonesky \
PRODUCT_COPY_FILES += \
vendor/common/android/gms/system/etc/permissions/extend.xml:system/etc/permissions/extend.xml \
vendor/common/android/gms/system/etc/sysconfig/google.xml:system/etc/sysconfig/google.xml \
$(call find-copy-subdir-files,*,vendor/common/android/gms/Phonesky/split,system/priv-app/Phonesky) \
$(call find-copy-subdir-files,*,vendor/common/android/gms/PrebuiltGmsCore/split,system/priv-app/PrebuiltGmsCore) \
vendor/common/android/gms/GoogleChrome/GoogleChrome.apk:system/priv-app/GoogleChrome/GoogleChrome.apk \
vendor/common/android/gms/iFlyInPut/iFlyInPut.apk:system/priv-app/iFlyInPut/iFlyInPut.apk
5.添加讯飞输入法:
在系统源码路径:/framefork/base/packages/SettingsProvider/res/values/defaults文件添加如下配置:
每个输入法的包名和服务不一样,可根据自己的业务进行对应的配置,本文只是以讯飞为例子,大家需要自行修改.
<!-- 设置默认输入法 -->
<string name="enabled_input_methods" translatable="false">com.iflytek.inputmethod/.FlyIME</string>
<string name="default_input_method" translatable="false">com.iflytek.inputmethod/.FlyIME</string>
6.设置讯飞输入法为默认输入法:
找到源码路径:/framefork/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper
在文件DatabaseHelper添加如下设置讯飞输入法为默认输入法,代码如下:
//add start
loadStringSetting(stmt, Settings.Secure.DEFAULT_INPUT_METHOD,R.string.enabled_input_methods);
loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS,R.string.default_input_method);
//add end
完整修改代码如下:
private void loadSecureSettings(SQLiteDatabase db) {
SQLiteStatement stmt = null;
try {
stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
+ " VALUES(?,?);");
// Don't do this. The SystemServer will initialize ADB_ENABLED from a
// persistent system property instead.
//loadSetting(stmt, Settings.Secure.ADB_ENABLED, 0);
// Allow mock locations default, based on build
loadSetting(stmt, Settings.Secure.ALLOW_MOCK_LOCATION,
"1".equals(SystemProperties.get("ro.allow.mock.location")) ? 1 : 0);
loadSecure35Settings(stmt);
loadBooleanSetting(stmt, Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND,
R.bool.def_mount_play_notification_snd);
loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_AUTOSTART,
R.bool.def_mount_ums_autostart);
loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_PROMPT,
R.bool.def_mount_ums_prompt);
loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
R.bool.def_mount_ums_notify_enabled);
loadIntegerSetting(stmt, Settings.Secure.LONG_PRESS_TIMEOUT,
R.integer.def_long_press_timeout_millis);
loadBooleanSetting(stmt, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
R.bool.def_touch_exploration_enabled);
loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
R.bool.def_accessibility_speak_password);
if (SystemProperties.getBoolean("ro.lockscreen.disable.default", false) == true) {
loadSetting(stmt, Settings.System.LOCKSCREEN_DISABLED, "1");
} else {
loadBooleanSetting(stmt, Settings.System.LOCKSCREEN_DISABLED,
R.bool.def_lockscreen_disabled);
}
loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ENABLED,
com.android.internal.R.bool.config_dreamsEnabledByDefault);
loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
com.android.internal.R.string.config_dreamsDefaultComponent);
loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
com.android.internal.R.string.config_dreamsDefaultComponent);
loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
R.bool.def_accessibility_display_magnification_enabled);
loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
R.fraction.def_accessibility_display_magnification_scale, 1);
loadBooleanSetting(stmt, Settings.Secure.USER_SETUP_COMPLETE,
R.bool.def_user_setup_complete);
loadStringSetting(stmt, Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
R.string.def_immersive_mode_confirmations);
loadBooleanSetting(stmt, Settings.Secure.INSTALL_NON_MARKET_APPS,
R.bool.def_install_non_market_apps);
loadBooleanSetting(stmt, Settings.Secure.WAKE_GESTURE_ENABLED,
R.bool.def_wake_gesture_enabled);
loadIntegerSetting(stmt, Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
R.integer.def_lock_screen_show_notifications);
loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
R.bool.def_lock_screen_allow_private_notifications);
loadIntegerSetting(stmt, Settings.Secure.SLEEP_TIMEOUT,
R.integer.def_sleep_timeout);
//add start
loadStringSetting(stmt, Settings.Secure.DEFAULT_INPUT_METHOD,R.string.enabled_input_methods);
loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS,R.string.default_input_method);
//add end
/*
* IMPORTANT: Do not add any more upgrade steps here as the global,
* secure, and system settings are no longer stored in a database
* but are kept in memory and persisted to XML.
*
* See: SettingsProvider.UpgradeController#onUpgradeLocked
*/
} finally {
if (stmt != null) stmt.close();
}
}
private void loadSecure35Settings(SQLiteStatement stmt) {
loadBooleanSetting(stmt, Settings.Secure.BACKUP_ENABLED,
R.bool.def_backup_enabled);
loadStringSetting(stmt, Settings.Secure.BACKUP_TRANSPORT,
R.string.def_backup_transport);
}
7.设置讯飞输入法默认权限:
7.1 找到源码路径/framefork/base/data/etc/
7.2 在privapp-permissions-platform文件中添加如下配置:
主要是解决用户打开输入法还需要授权的问题,这种每个手机和用户都要去授权很麻烦,也不符合现代化的用户体验,于是在定制的时候就要考虑好用户权限授权问题.
<privapp-permissions package="com.iflytek.inputmethod.miui">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.RECOVERY"/>
<permission name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<permission name="android.permission.READ_EXTERNAL_STORAGE"/>
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<permission name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</privapp-permissions>
8.实现的效果如下:
可以看到开机后打开浏览器或者app的输入法,默认就是调用的讯飞输入法,基本上就完成了本次的把讯飞输入法定制为默认输入法的定制需求.
9.总结:
当然这里还有一个小问题,就是用户首次打开输入法的时候会有一个隐私协议的授权弹框,既然是定制系统,这块其实也可以考虑,我们是采用首次打开app的时候调起输入法利用无障碍服务自动点击同意或者拒绝隐私协议的方式解决这个体验问题,方式有很多,这里就不详细讲解了,大家可以自己尝试解决.基本上是一个很完整的定制过程,感兴趣的小伙伴可以自行尝试,实战才是检验一切的最好老师.
今天的AOSP定制实现还是遇到很多问题的:
- 比如apk定制好之后能不能正常使用?
- 如何把讯飞输入法定制为默认输入法?而不是需要用户主动去切换选择?
- 怎么默认授权文件读写、录音等权限?
- 怎么在用户首次打开输入法时自动点击同意或者拒绝隐私政策?