provisioner: initial ConfigProvisioner implementation
Core provisioner logic for PawletOS Android 16: - BootReceiver: trigger provisioning on BOOT_COMPLETED and USER_PRESENT - ProvisioningService: run base tasks (APN, secure settings) and OTA tasks (deferred APK installs) as separate passes - VendorConfig: read vendor.cfg from partition; parse APNs, packages, settings - Android.bp: wire up AIDL, disable resource generation (no UI)
This commit is contained in:
@@ -1,205 +1,464 @@
|
||||
package dev.oxmc.configprovisioner;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Telephony;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public class ProvisioningService extends Service {
|
||||
private static final String TAG = "ConfigProvisioner";
|
||||
private static final String DOWNLOAD_PATH = "/data/local/tmp/config_provision.apk";
|
||||
|
||||
/** Runs on BOOT_COMPLETED: applies built-in config APK settings + wizard state. No network. */
|
||||
public static final String ACTION_BASE_PROVISION = "dev.oxmc.configprovisioner.ACTION_BASE_PROVISION";
|
||||
|
||||
/** Runs on USER_PRESENT: downloads updated config APK from URL and re-applies settings. */
|
||||
public static final String ACTION_OTA_UPDATE = "dev.oxmc.configprovisioner.ACTION_OTA_UPDATE";
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Service entry point
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "Starting provisioning service");
|
||||
|
||||
// Double-check that vendor config exists
|
||||
if (!VendorConfig.hasVendorConfig()) {
|
||||
Log.i(TAG, "No vendor config found, stopping service");
|
||||
BootReceiver.setProvisioned(this, true);
|
||||
stopSelf();
|
||||
String action = intent != null ? intent.getAction() : null;
|
||||
if (ACTION_BASE_PROVISION.equals(action)) {
|
||||
Log.d(TAG, "Starting base provisioning");
|
||||
new BaseProvisionTask().execute();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
new ProvisioningTask().execute();
|
||||
|
||||
return START_STICKY;
|
||||
if (ACTION_OTA_UPDATE.equals(action)) {
|
||||
Log.d(TAG, "Starting OTA config update check");
|
||||
new OtaUpdateTask().execute();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private class ProvisioningTask extends AsyncTask<Void, Void, Boolean> {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) { return null; }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Base provisioning — runs at boot, no network needed
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private class BaseProvisionTask extends AsyncTask<Void, Void, Boolean> {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
Log.i(TAG, "Starting provisioning process");
|
||||
|
||||
// Log all config values for debugging
|
||||
protected Boolean doInBackground(Void... v) {
|
||||
VendorConfig.logConfigValues();
|
||||
|
||||
// Check if provisioning is enabled
|
||||
if (!VendorConfig.isProvisioningEnabled()) {
|
||||
Log.i(TAG, "Provisioning disabled by vendor config");
|
||||
configureSetupWizard();
|
||||
return true;
|
||||
if (VendorConfig.isConfigApkServiceEnabled()) {
|
||||
applyConfigApkSettings();
|
||||
applyApns();
|
||||
}
|
||||
|
||||
// Get config URL
|
||||
String configUrl = VendorConfig.getConfigApkUrl();
|
||||
if (configUrl == null || configUrl.isEmpty() || configUrl.equals(VendorConfig.DEFAULT_CONFIG_APK_URL)) {
|
||||
Log.e(TAG, "No valid config URL configured, skipping provisioning");
|
||||
configureSetupWizard();
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
Log.d(TAG, "Downloading config from: " + configUrl);
|
||||
|
||||
if (downloadApk(configUrl, DOWNLOAD_PATH)) {
|
||||
Log.i(TAG, "Download successful, installing APK");
|
||||
|
||||
if (installApk(DOWNLOAD_PATH)) {
|
||||
Log.i(TAG, "Installation successful");
|
||||
configureSetupWizard();
|
||||
return true;
|
||||
} else {
|
||||
Log.e(TAG, "Installation failed");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Download failed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Provisioning failed with error", e);
|
||||
} finally {
|
||||
// Clean up downloaded file
|
||||
File downloadedFile = new File(DOWNLOAD_PATH);
|
||||
if (downloadedFile.exists()) {
|
||||
if (downloadedFile.delete()) {
|
||||
Log.d(TAG, "Cleaned up downloaded file");
|
||||
} else {
|
||||
Log.w(TAG, "Failed to clean up downloaded file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
configureDeviceProvisioned();
|
||||
configureSetupWizard();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean success) {
|
||||
if (success) {
|
||||
Log.i(TAG, "Provisioning completed successfully");
|
||||
BootReceiver.setProvisioned(ProvisioningService.this, true);
|
||||
} else {
|
||||
Log.e(TAG, "Provisioning failed");
|
||||
// Don't mark as provisioned on failure, so it retries on next boot
|
||||
}
|
||||
BootReceiver.setBaseProvisioned(ProvisioningService.this, true);
|
||||
Log.i(TAG, "Base provisioning complete");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private boolean downloadApk(String urlString, String outputPath) {
|
||||
HttpURLConnection connection = null;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// OTA update — runs on USER_PRESENT, deferred well after boot
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private class OtaUpdateTask extends AsyncTask<Void, Void, Boolean> {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... v) {
|
||||
if (!VendorConfig.isConfigApkServiceEnabled()) {
|
||||
return false;
|
||||
}
|
||||
String url = VendorConfig.getConfigApkUrl();
|
||||
if (url == null || url.isEmpty() || url.equals(VendorConfig.DEFAULT_CONFIG_APK_URL)) {
|
||||
Log.d(TAG, "No OTA URL configured, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long lastCheck = BootReceiver.getLastOtaCheck(ProvisioningService.this);
|
||||
long interval = VendorConfig.getOtaCheckIntervalMs();
|
||||
if (now - lastCheck < interval) {
|
||||
long minutesLeft = (interval - (now - lastCheck)) / 60_000;
|
||||
Log.d(TAG, "OTA check skipped — next check in " + minutesLeft + " min");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record attempt time before the download so a broken server doesn't
|
||||
// cause a retry on every subsequent screen unlock.
|
||||
BootReceiver.setLastOtaCheck(ProvisioningService.this, now);
|
||||
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(VendorConfig.getNetworkTimeout());
|
||||
connection.setReadTimeout(VendorConfig.getNetworkTimeout());
|
||||
connection.setRequestProperty("User-Agent", "ConfigProvisioner/1.0");
|
||||
connection.connect();
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
Log.e(TAG, "Server returned HTTP " + responseCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream();
|
||||
FileOutputStream output = new FileOutputStream(outputPath)) {
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
long totalBytes = 0;
|
||||
|
||||
while ((bytesRead = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
totalBytes += bytesRead;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Downloaded " + totalBytes + " bytes to " + outputPath);
|
||||
return true;
|
||||
}
|
||||
Log.i(TAG, "Downloading config update from: " + url);
|
||||
if (!downloadApk(url, DOWNLOAD_PATH)) return false;
|
||||
if (!installApk(DOWNLOAD_PATH)) return false;
|
||||
Log.i(TAG, "OTA config APK installed, re-applying settings");
|
||||
applyConfigApkSettings();
|
||||
applyApns();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Download failed", e);
|
||||
Log.e(TAG, "OTA update failed", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
new File(DOWNLOAD_PATH).delete();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean installApk(String apkPath) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(
|
||||
new String[]{"pm", "install", "-r", "--user", "0", apkPath}
|
||||
);
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode == 0) {
|
||||
Log.i(TAG, "APK installed successfully via pm install");
|
||||
return true;
|
||||
} else {
|
||||
Log.e(TAG, "pm install failed with exit code: " + exitCode);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Installation failed", e);
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean updated) {
|
||||
if (updated) Log.i(TAG, "OTA config update applied successfully");
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Network helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private boolean downloadApk(String urlString, String outputPath) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(VendorConfig.getNetworkTimeout());
|
||||
connection.setReadTimeout(VendorConfig.getNetworkTimeout());
|
||||
connection.setRequestProperty("User-Agent", "ConfigProvisioner/1.0");
|
||||
connection.connect();
|
||||
|
||||
int code = connection.getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK) {
|
||||
Log.e(TAG, "Server returned HTTP " + code);
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream in = connection.getInputStream();
|
||||
FileOutputStream out = new FileOutputStream(outputPath)) {
|
||||
byte[] buf = new byte[8192];
|
||||
int n;
|
||||
long total = 0;
|
||||
while ((n = in.read(buf)) != -1) { out.write(buf, 0, n); total += n; }
|
||||
Log.d(TAG, "Downloaded " + total + " bytes");
|
||||
return total > 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Download failed", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
|
||||
private void configureSetupWizard() {
|
||||
boolean enableWizard = VendorConfig.isSetupWizardEnabled();
|
||||
Log.i(TAG, "Configuring Setup Wizard: " + (enableWizard ? "ENABLED" : "DISABLED"));
|
||||
|
||||
if (!enableWizard) {
|
||||
disablePackage("com.android.setupwizard");
|
||||
disablePackage("com.google.android.setupwizard");
|
||||
disablePackage("org.lineageos.setupwizard");
|
||||
|
||||
// Mark setup as complete
|
||||
try {
|
||||
Settings.Secure.putInt(getContentResolver(),
|
||||
Settings.Secure.USER_SETUP_COMPLETE, 1);
|
||||
Settings.Global.putInt(getContentResolver(),
|
||||
Settings.Global.DEVICE_PROVISIONED, 1);
|
||||
Log.d(TAG, "Setup marked as complete");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to set setup complete flags", e);
|
||||
}
|
||||
|
||||
private boolean installApk(String apkPath) {
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec(
|
||||
new String[]{"pm", "install", "-r", "--user", "0", apkPath});
|
||||
int exit = p.waitFor();
|
||||
if (exit == 0) { Log.i(TAG, "APK installed via pm install"); return true; }
|
||||
Log.e(TAG, "pm install failed (exit " + exit + ")");
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Installation failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Settings application
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void applyConfigApkSettings() {
|
||||
String pkg = VendorConfig.getConfigApkPackage();
|
||||
if (pkg == null || pkg.isEmpty()) return;
|
||||
try {
|
||||
Context ctx = createPackageContext(pkg, Context.CONTEXT_IGNORE_SECURITY);
|
||||
Resources res = ctx.getResources();
|
||||
int xmlId = res.getIdentifier("settings", "xml", pkg);
|
||||
if (xmlId == 0) { Log.d(TAG, "No settings.xml in " + pkg); return; }
|
||||
|
||||
XmlResourceParser xml = res.getXml(xmlId);
|
||||
int event, skipDepth = 0;
|
||||
while ((event = xml.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||
if (event == XmlResourceParser.START_TAG) {
|
||||
if (skipDepth > 0) { skipDepth++; continue; }
|
||||
String tag = xml.getName();
|
||||
if ("if".equals(tag)) { if (!evaluateCondition(xml)) skipDepth = 1; continue; }
|
||||
String name = xml.getAttributeValue(null, "name");
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
if (name == null || value == null) continue;
|
||||
try {
|
||||
switch (tag) {
|
||||
case "secure": Settings.Secure.putString(getContentResolver(), name, value); break;
|
||||
case "system": Settings.System.putString(getContentResolver(), name, value); break;
|
||||
case "global": Settings.Global.putString(getContentResolver(), name, value); break;
|
||||
default: Log.w(TAG, "Unknown settings tag: " + tag);
|
||||
}
|
||||
Log.d(TAG, tag + "." + name + " = " + value);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to apply " + tag + "." + name, e);
|
||||
}
|
||||
} else if (event == XmlResourceParser.END_TAG) {
|
||||
if (skipDepth > 0) skipDepth--;
|
||||
}
|
||||
}
|
||||
xml.close();
|
||||
Log.i(TAG, "Settings from " + pkg + " applied");
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Config APK not found: " + pkg + " (not yet installed?)");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to apply config APK settings", e);
|
||||
}
|
||||
|
||||
private void disablePackage(String packageName) {
|
||||
}
|
||||
|
||||
private void applyApns() {
|
||||
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
|
||||
String pkg = VendorConfig.getConfigApkPackage();
|
||||
if (pkg == null || pkg.isEmpty()) return;
|
||||
try {
|
||||
Context ctx = createPackageContext(pkg, Context.CONTEXT_IGNORE_SECURITY);
|
||||
int[] counts = {0, 0};
|
||||
|
||||
AssetManager assets = ctx.getAssets();
|
||||
String[] topLevel = assets.list("apns");
|
||||
if (topLevel != null && topLevel.length > 0) {
|
||||
walkApnAssets(assets, "apns", counts);
|
||||
Log.i(TAG, "APNs (assets/apns/): " + counts[0] + " inserted, " + counts[1] + " skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
Resources res = ctx.getResources();
|
||||
int xmlId = res.getIdentifier("apns", "xml", pkg);
|
||||
if (xmlId == 0) { Log.d(TAG, "No APN config in " + pkg); return; }
|
||||
XmlResourceParser xml = res.getXml(xmlId);
|
||||
parseAndInsertApns(xml, "res/xml/apns.xml", counts);
|
||||
xml.close();
|
||||
Log.i(TAG, "APNs (res/xml/apns.xml): " + counts[0] + " inserted, " + counts[1] + " skipped");
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Config APK not found for APN provisioning: " + pkg);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "APN provisioning failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void walkApnAssets(AssetManager assets, String path, int[] counts) {
|
||||
try {
|
||||
String[] entries = assets.list(path);
|
||||
if (entries == null) return;
|
||||
for (String entry : entries) {
|
||||
String full = path + "/" + entry;
|
||||
String[] children = assets.list(full);
|
||||
if (children != null && children.length > 0) {
|
||||
walkApnAssets(assets, full, counts);
|
||||
} else if (entry.endsWith(".xml")) {
|
||||
try (InputStream is = assets.open(full)) {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(is, "UTF-8");
|
||||
parseAndInsertApns(parser, full, counts);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to parse APN file: " + full, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to enumerate assets/" + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAndInsertApns(XmlPullParser parser, String source, int[] counts)
|
||||
throws Exception {
|
||||
int event;
|
||||
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (event != XmlPullParser.START_TAG || !"apn".equals(parser.getName())) continue;
|
||||
|
||||
String name = parser.getAttributeValue(null, "name");
|
||||
if (name == null) name = parser.getAttributeValue(null, "carrier");
|
||||
String mcc = parser.getAttributeValue(null, "mcc");
|
||||
String mnc = parser.getAttributeValue(null, "mnc");
|
||||
String apn = parser.getAttributeValue(null, "apn");
|
||||
String numeric = parser.getAttributeValue(null, "numeric");
|
||||
String mvnoType = parser.getAttributeValue(null, "mvno_type");
|
||||
String mvnoData = parser.getAttributeValue(null, "mvno_match_data");
|
||||
if (numeric == null && mcc != null && mnc != null) numeric = mcc + mnc;
|
||||
if (mvnoType == null) mvnoType = "";
|
||||
if (mvnoData == null) mvnoData = "";
|
||||
|
||||
if (name == null || numeric == null || mcc == null || mnc == null || apn == null) {
|
||||
Log.w(TAG, "Skipping APN with missing fields in " + source);
|
||||
continue;
|
||||
}
|
||||
|
||||
Cursor c = getContentResolver().query(Telephony.Carriers.CONTENT_URI,
|
||||
new String[]{"_id"},
|
||||
"numeric=? AND apn=? AND mvno_type=? AND mvno_match_data=?",
|
||||
new String[]{numeric, apn, mvnoType, mvnoData}, null);
|
||||
boolean exists = c != null && c.getCount() > 0;
|
||||
if (c != null) c.close();
|
||||
if (exists) { counts[1]++; continue; }
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Telephony.Carriers.NAME, name);
|
||||
cv.put(Telephony.Carriers.NUMERIC, numeric);
|
||||
cv.put(Telephony.Carriers.MCC, mcc);
|
||||
cv.put(Telephony.Carriers.MNC, mnc);
|
||||
cv.put(Telephony.Carriers.APN, apn);
|
||||
if (!mvnoType.isEmpty()) cv.put(Telephony.Carriers.MVNO_TYPE, mvnoType);
|
||||
if (!mvnoData.isEmpty()) cv.put(Telephony.Carriers.MVNO_MATCH_DATA, mvnoData);
|
||||
|
||||
String type = parser.getAttributeValue(null, "type");
|
||||
cv.put(Telephony.Carriers.TYPE, type != null ? type : "default,supl");
|
||||
String proto = parser.getAttributeValue(null, "protocol");
|
||||
cv.put(Telephony.Carriers.PROTOCOL, proto != null ? proto : "IPV4V6");
|
||||
String roamProto = parser.getAttributeValue(null, "roaming_protocol");
|
||||
cv.put(Telephony.Carriers.ROAMING_PROTOCOL, roamProto != null ? roamProto : "IPV4V6");
|
||||
|
||||
apnPutStr(cv, Telephony.Carriers.SERVER, parser, "server");
|
||||
apnPutStr(cv, Telephony.Carriers.PROXY, parser, "proxy");
|
||||
apnPutStr(cv, Telephony.Carriers.PORT, parser, "port");
|
||||
apnPutStr(cv, Telephony.Carriers.MMSC, parser, "mmsc");
|
||||
apnPutStr(cv, Telephony.Carriers.MMSPROXY, parser, "mmsproxy");
|
||||
apnPutStr(cv, Telephony.Carriers.MMSPORT, parser, "mmsport");
|
||||
apnPutStr(cv, Telephony.Carriers.USER, parser, "user");
|
||||
apnPutStr(cv, Telephony.Carriers.PASSWORD, parser, "password");
|
||||
apnPutStr(cv, "bearer_bitmask", parser, "bearer_bitmask");
|
||||
apnPutInt(cv, Telephony.Carriers.AUTH_TYPE, parser, "authtype", -1);
|
||||
apnPutInt(cv, "profile_id", parser, "profile_id", 0);
|
||||
apnPutInt(cv, "max_conns", parser, "max_conns", 0);
|
||||
apnPutInt(cv, "wait_time", parser, "wait_time", 0);
|
||||
apnPutInt(cv, "max_conns_time", parser, "max_conns_time", 0);
|
||||
apnPutInt(cv, Telephony.Carriers.MTU, parser, "mtu", 0);
|
||||
|
||||
String modemCog = parser.getAttributeValue(null, "modem_cognitive");
|
||||
if (modemCog != null)
|
||||
cv.put("modem_cognitive",
|
||||
"true".equalsIgnoreCase(modemCog) || "1".equals(modemCog) ? 1 : 0);
|
||||
String enabled = parser.getAttributeValue(null, "carrier_enabled");
|
||||
cv.put(Telephony.Carriers.CARRIER_ENABLED,
|
||||
"0".equals(enabled) || "false".equalsIgnoreCase(enabled) ? 0 : 1);
|
||||
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
pm.setApplicationEnabledSetting(packageName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
|
||||
Log.d(TAG, "Disabled package: " + packageName);
|
||||
getContentResolver().insert(Telephony.Carriers.CONTENT_URI, cv);
|
||||
counts[0]++;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to disable package: " + packageName, e);
|
||||
Log.w(TAG, "Failed to insert APN: " + name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
private static void apnPutStr(ContentValues cv, String col, XmlPullParser p, String attr) {
|
||||
String v = p.getAttributeValue(null, attr);
|
||||
if (v != null && !v.isEmpty()) cv.put(col, v);
|
||||
}
|
||||
}
|
||||
|
||||
private static void apnPutInt(ContentValues cv, String col, XmlPullParser p,
|
||||
String attr, int def) {
|
||||
String v = p.getAttributeValue(null, attr);
|
||||
if (v == null || v.isEmpty()) return;
|
||||
try { cv.put(col, Integer.parseInt(v)); }
|
||||
catch (NumberFormatException e) { if (def >= 0) cv.put(col, def); }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup wizard configuration
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void configureDeviceProvisioned() {
|
||||
boolean provisioned = VendorConfig.isDeviceProvisioned();
|
||||
Log.i(TAG, "Device provisioned: " + provisioned);
|
||||
try {
|
||||
int v = provisioned ? 1 : 0;
|
||||
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, v);
|
||||
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, v);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to set device provisioned state", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSetupWizard() {
|
||||
boolean enable = VendorConfig.isSetupWizardEnabled();
|
||||
Log.i(TAG, "Setup wizard: " + (enable ? "ENABLED" : "DISABLED"));
|
||||
if (!enable) {
|
||||
disablePackage("com.android.setupwizard");
|
||||
disablePackage("com.google.android.setupwizard");
|
||||
disablePackage("org.lineageos.setupwizard");
|
||||
}
|
||||
}
|
||||
|
||||
private void disablePackage(String pkg) {
|
||||
try {
|
||||
getPackageManager().setApplicationEnabledSetting(
|
||||
pkg, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
|
||||
Log.d(TAG, "Disabled: " + pkg);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Could not disable " + pkg + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// <if> condition evaluation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private boolean evaluateCondition(XmlResourceParser xml) {
|
||||
String manufacturer = xml.getAttributeValue(null, "manufacturer");
|
||||
String brand = xml.getAttributeValue(null, "brand");
|
||||
String model = xml.getAttributeValue(null, "model");
|
||||
String sdkStr = xml.getAttributeValue(null, "sdk");
|
||||
String sdkMinStr = xml.getAttributeValue(null, "sdk_min");
|
||||
String sdkMaxStr = xml.getAttributeValue(null, "sdk_max");
|
||||
String formFactor = xml.getAttributeValue(null, "form_factor");
|
||||
String feature = xml.getAttributeValue(null, "feature");
|
||||
|
||||
if (manufacturer != null && !Build.MANUFACTURER.equalsIgnoreCase(manufacturer)) return false;
|
||||
if (brand != null && !Build.BRAND.equalsIgnoreCase(brand)) return false;
|
||||
if (model != null && !Build.MODEL.toLowerCase().contains(model.toLowerCase())) return false;
|
||||
|
||||
if (sdkStr != null) { try { if (Build.VERSION.SDK_INT != Integer.parseInt(sdkStr)) return false; } catch (NumberFormatException ignored) {} }
|
||||
if (sdkMinStr != null) { try { if (Build.VERSION.SDK_INT < Integer.parseInt(sdkMinStr)) return false; } catch (NumberFormatException ignored) {} }
|
||||
if (sdkMaxStr != null) { try { if (Build.VERSION.SDK_INT > Integer.parseInt(sdkMaxStr)) return false; } catch (NumberFormatException ignored) {} }
|
||||
|
||||
if (formFactor != null) {
|
||||
switch (formFactor) {
|
||||
case "tablet": if (!isTablet()) return false; break;
|
||||
case "phone": if (isTablet()) return false; break;
|
||||
case "flip":
|
||||
if (!getPackageManager().hasSystemFeature("android.hardware.sensor.hinge_angle")) return false;
|
||||
break;
|
||||
case "tv":
|
||||
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) return false;
|
||||
break;
|
||||
default: Log.w(TAG, "Unknown form_factor: " + formFactor);
|
||||
}
|
||||
}
|
||||
if (feature != null && !getPackageManager().hasSystemFeature(feature)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isTablet() {
|
||||
int layout = getResources().getConfiguration().screenLayout
|
||||
& Configuration.SCREENLAYOUT_SIZE_MASK;
|
||||
return layout >= Configuration.SCREENLAYOUT_SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user