提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。
一、需求背景
当前项目中有一个名为X的系统上层应用,该上层应用的功能用于对设备的音效进行算法处理,通过不同的场景切换来实现不同的播放效果。X应用在系统Settings——提示音和振动,这个二级菜单中添加了一个自定义的Preference,该Preference能够动态显示当前的播放模式,并且可以通过点击该Preference跳转到X应用。
客户要求,当用户长按X应用——应用信息——停用该应用后,Settings中的X应用的Preference能够自动隐藏,当用户启用X应用后,Settings中的X应用的Preference能够自动显示出来。
二、需求实现
2.1 文件说明
Settings—"提示音和振动"二级菜单的layout布局文件:res/xml/sound_settings.xml
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/sound_settings"
android:key="sound_settings"
settings:keywords="@string/keywords_sounds">
· · ·
<!-- MODIFY START-->
<Preference
android:key="x_dap_settings"
android:icon="@drawable/ic_x_logo"
android:title="@string/sound_x_apps"
android:order="-185"
settings:controller="com.android.settings.notification.SoundXParentPreferenceController">
<intent android:targetPackage="com.x.daxappui2"
android:targetClass="com.x.daxappui2.MainActivity" />
</Preference>
<!-- MODIFY END -->
· · ·
</PreferenceScreen>
可以看到该Preference是由一个名为SoundXParentPreferenceController.java的Controller来控制的,并且我们给这个Preference配置了一个< intent> 标签。< intent> 标签中的android:targetPackage和android:targetClass属性指定了当这个设置项被点击时启动的应用包名和类名。当我们点击这个Preference时,系统将会启动com.x.daxappui2这个应用,并直接跳转到com.x.daxappui2.MainActivity界面,也就是X应用的主界面。
2.2 控制Preference的显示与隐藏
src/com/android/settings/notification/SoundXParentPreferenceController.java在这个Controller中,有一个getAvailabilityStatus()方法,该方法的返回值会影响目标Preference的显示与消失。当getAvailabilityStatus()方法返回AVAILABLE时,该Controller控制的Preference就会显示出来。当getAvailabilityStatus()方法返回UNSUPPORTED_ON_DEVICE时,该Controller控制的Preference就会被隐藏。
@Override
public int getAvailabilityStatus() {
if (shouldShow) {
return AVAILABLE; // 显示Preference
} else {
return UNSUPPORTED_ON_DEVICE; // 隐藏Preference
}
}
2.3 获取应用被停用的状态
public boolean isFrozen(Context context) {
final String packageName = "com.x.daxappui2";
final String LOG_TAG = "AppStatusChecker"; // 确保定义了日志标签
try {
PackageManager pm = context.getPackageManager();
int xEnabledState = pm.getApplicationEnabledSetting(packageName);
if (xEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
xEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
Log.d(LOG_TAG, "X App is Disabled!");
return true;
} else {
Log.d(LOG_TAG, "X App is Enabled.");
return false;
}
} catch (IllegalArgumentException e) {
// 异常捕获(如果应用程序包名不存在,调用getApplicationEnabledSetting会抛出异常)
Log.e(LOG_TAG, "Application package not found: " + packageName, e);
return false;
}
}
2.4 相关源码
public class SoundXParentPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnResume, OnPause {
private final Context context;
private static final String X_PACKAGE_NAME = "com.x.daxappui2";
private static final String LOG_TAG = "SoundXParentPreferenceController";
· · ·
public SoundXParentPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
this.context = context;
}
· · ·
@Override
public int getAvailabilityStatus() {
try {
int xEnabledState = context.getPackageManager().getApplicationEnabledSetting(X_PACKAGE_NAME);
Log.d(LOG_TAG, "xEnabledSetting is :" + xEnabledState);
if (xEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
xEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
Log.d(LOG_TAG, "XApp is Disabled!");
return UNSUPPORTED_ON_DEVICE;
} else {
Log.d(LOG_TAG, "XApp State Changed, but not disabled.");
return AVAILABLE;
}
} catch (IllegalArgumentException e) {
// 异常捕获(如果应用程序包名不存在,调用getApplicationEnabledSetting会抛出异常)
Log.e(LOG_TAG, "Application package not found: " + X_PACKAGE_NAME, e);
return UNSUPPORTED_ON_DEVICE;
}
}
}
2.5 2024-03-26缺陷修复
2024年3月26日更新
已知缺陷:当用户已经在Settings·X应用Preference界面所处的二级菜单时,将Settings切回后台,然后在桌面禁用X应用后再通过后台返回Settings·X应用Preference界面所处的二级菜单。这种场景下X应用Preference所处的二级菜单无法及时更新,X应用的Preference就没有及时隐藏,若此时点击X应用的Preference时会导致Settings发生Crash并报错ActivityNotFoundException。为了修复这个问题,暂时需要先将X应用的Preference点击事件进行异常处理。
我们可以重写SoundXParentPreferenceController 中的handlePreferenceTreeClick()方法实现对Preference的点击事件进行异常捕获和处理:
public class SoundXParentPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnResume, OnPause {
private final Context context;
private static final String X_PACKAGE_NAME = "com.x.daxappui2";
++private static final String X_PREFERENCE_KEY = "x_dap_settings";
private static final String LOG_TAG = "SoundXParentPreferenceController";
· · ·
@Override
public int getAvailabilityStatus() {
· · ·
}
@Override
public boolean handlePreferenceTreeClick(Preference mPreference) {
if (mPreference.getKey().equals(X_PREFERENCE_KEY)) {
try {
Intent xIntent = new Intent();
xIntent.setComponent(new ComponentName("com.x.daxappui2", "com.x.daxappui2.MainActivity"));
context.startActivity(xIntent);
} catch (ActivityNotFoundException acException) {
Toast.makeText(context, "X App not found.", Toast.LENGTH_SHORT).show();
}
return true;
}
return false;
}