Trong hệ điều hành Android, các nhà sản xuất thiết bị (OEM) thường cần tùy chỉnh danh sách trắng (whitelist) cho chế độ tiết kiệm pin (Doze / DeviceIdle) đối với các ứng dụng bên thứ ba. Quá trình này bao gồm việc cấu hình tệp XML, biên dịch, phân tích cú pháp ở tầng Framework và kích hoạt ở tầng hệ thống lõi.
Cấu hình và sao chép tệp XML
Tệp cấu hình danh sách trắng thường được đặt trong thư mục vendor dành riêng cho các tùy chỉnh của nhà sản xuất. Ví dụ, đường dẫn có thể là /vendor/<project>/product/common_req/etc/deviceidle.xml.
Trong quá trình biên dịch mã nguồn, hệ thống sử dụng biến PRODUCT_COPY_FILES trong script Makefile để sao chép tệp cấu hình này vào thư mục /system/etc/ của thiết bị, chuẩn bị cho việc phân tích ở tầng Framework.
Phân tích cú pháp trong DeviceIdleController
Lớp DeviceIdleController (nằm trong /frameworks/base/apex/jobscheduler/service/java/com/android/server/) chịu trách nhiệm đọc và phân tích tệp XML. Khi hệ thống phát hiện một ứng dụng mới được cài đặt thông qua broadcast ACTION_PACKAGE_ADDED, bộ điều khiển sẽ kích hoạt quá trình tải lại cấu hình.
private final BroadcastReceiver packageUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent broadcastIntent) {
String action = broadcastIntent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
boolean isReplacing = broadcastIntent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (!isReplacing && CustomFeatureConfig.IDLE_WHITELIST_ENABLED) {
reloadWhitelistConfigurations();
notifyWhitelistStateChange();
}
}
}
};
private void reloadWhitelistConfigurations() {
File standardConfig = new File(getEtcDirectory(), "deviceidle.xml");
File customConfig = new File(getEtcDirectory(), "deviceidle_custom.xml");
parseAndLoadConfig(new AtomicFile(standardConfig));
parseAndLoadConfig(new AtomicFile(customConfig));
refreshWhitelistAppIds();
}
Quá trình đọc và phân tích XML sử dụng XmlPullParser để duyệt qua các thẻ. Thẻ <wl> đại diện cho các ứng dụng cần thêm vào danh sách trắng, trong khi <un-wl> dùng để loại bỏ.
private void parseAndLoadConfig(AtomicFile configFile) {
try (FileInputStream inputStream = configFile.openRead()) {
XmlPullParser xmlParser = Xml.newPullParser();
xmlParser.setInput(inputStream, StandardCharsets.UTF_8.name());
processXmlNodes(xmlParser);
} catch (Exception e) {
Log.e(TAG, "Error parsing whitelist config", e);
}
}
private void processXmlNodes(XmlPullParser parser) throws Exception {
PackageManager pm = getContext().getPackageManager();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String tagName = parser.getName();
if ("wl".equals(tagName)) {
String packageName = parser.getAttributeValue(null, "n");
if (packageName != null) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ANY_USER);
whitelistUserApps.put(appInfo.packageName, UserHandle.getAppId(appInfo.uid));
} catch (PackageManager.NameNotFoundException ignored) {
}
}
} else if ("un-wl".equals(tagName)) {
String pkgToRemove = parser.getAttributeValue(null, "n");
systemWhitelistApps.remove(pkgToRemove);
}
}
eventType = parser.next();
}
}
Truyền dữ liệu sang PowerManagerService
Sau khi phân tích xong, DeviceIdleController tập hợp các UID của ứng dụng vào một mảng và sử dụng PowerManagerInternal để đồng bộ dữ liệu này sang PowerManagerService.
// Trong DeviceIdleController
private void syncWhitelistsToServices() {
int[] allWhitelistIds = buildUidArray(systemApps, userApps);
if (localPowerManager != null) {
localPowerManager.applyDeviceIdleWhitelist(allWhitelistIds);
}
}
// Trong PowerManagerService
public void applyDeviceIdleWhitelist(int[] allowedAppIds) {
synchronized (stateLock) {
permanentIdleWhitelist = allowedAppIds;
if (isDeviceIdleModeActive) {
reevaluateWakeLockStates();
}
}
}
Kích hoạt danh sách trắng ở tầng Kernel
Trong PowerManagerService, danh sách trắng được lưu trữ và có thể được xuất ra thông qua cơ chế dump của Binder. Dữ liệu được ghi dưới định dạng Protobuf vào một FileDescriptor. FileDescriptor này đóng vai trò là cầu nối để giao tiếp với tầng kernel Linux, nơi thực sự quản lý các wake locks và trạng thái tiết kiệm pin của hệ thống.
private void exportStateToProto(FileDescriptor fileDesc) {
ProtoOutputStream protoStream = new ProtoOutputStream(fileDesc);
for (int uid : permanentIdleWhitelist) {
protoStream.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, uid);
}
for (int uid : temporaryIdleWhitelist) {
protoStream.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, uid);
}
protoStream.flush();
}