Base
This commit is contained in:
15
Android.bp
Normal file
15
Android.bp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
android_app {
|
||||||
|
name: "ConfigProvisioner",
|
||||||
|
srcs: ["src/**/*.java"],
|
||||||
|
manifest: "AndroidManifest.xml",
|
||||||
|
resource_dirs: ["res"],
|
||||||
|
privileged: true,
|
||||||
|
certificate: "platform",
|
||||||
|
optimize: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
dex_preopt: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
product_specific: true,
|
||||||
|
}
|
34
AndroidManifest.xml
Normal file
34
AndroidManifest.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="dev.oxmc.configprovisioner"
|
||||||
|
android:sharedUserId="android.uid.system">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:label="Config Provisioner"
|
||||||
|
android:supportsRtl="true">
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".BootReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.SYSTEM_ALERT_WINDOW">
|
||||||
|
<intent-filter android:priority="1000">
|
||||||
|
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".ProvisioningService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
53
src/dev/oxmc/configprovisioner/BootReciever.java
Normal file
53
src/dev/oxmc/configprovisioner/BootReciever.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package dev.oxmc.configprovisioner;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class BootReceiver extends BroadcastReceiver {
|
||||||
|
private static final String TAG = "ConfigProvisioner";
|
||||||
|
private static final String PREF_NAME = "config_provisioner_prefs";
|
||||||
|
private static final String KEY_HAS_PROVISIONED = "has_provisioned";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent == null || intent.getAction() == null) {
|
||||||
|
Log.d(TAG, "Null intent or action");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Received boot event: " + intent.getAction());
|
||||||
|
|
||||||
|
// Check if we've already provisioned
|
||||||
|
if (hasProvisioned(context)) {
|
||||||
|
Log.d(TAG, "Already provisioned, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vendor config exists - if not, exit gracefully
|
||||||
|
if (!VendorConfig.hasVendorConfig()) {
|
||||||
|
Log.i(TAG, "No vendor config found, provisioning not required");
|
||||||
|
setProvisioned(context, true); // Mark as provisioned to not run again
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start provisioning service
|
||||||
|
Intent serviceIntent = new Intent(context, ProvisioningService.class);
|
||||||
|
serviceIntent.setAction(intent.getAction());
|
||||||
|
context.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasProvisioned(Context context) {
|
||||||
|
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getBoolean(KEY_HAS_PROVISIONED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setProvisioned(Context context, boolean provisioned) {
|
||||||
|
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putBoolean(KEY_HAS_PROVISIONED, provisioned)
|
||||||
|
.apply();
|
||||||
|
Log.d(TAG, "Provisioning state set to: " + provisioned);
|
||||||
|
}
|
||||||
|
}
|
205
src/dev/oxmc/configprovisioner/ProvisioningService.java
Normal file
205
src/dev/oxmc/configprovisioner/ProvisioningService.java
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package dev.oxmc.configprovisioner;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class ProvisioningService extends Service {
|
||||||
|
private static final String TAG = "ConfigProvisioner";
|
||||||
|
private static final String DOWNLOAD_PATH = "/data/local/tmp/config_provision.apk";
|
||||||
|
|
||||||
|
@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();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ProvisioningTask().execute();
|
||||||
|
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProvisioningTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
Log.i(TAG, "Starting provisioning process");
|
||||||
|
|
||||||
|
// Log all config values for debugging
|
||||||
|
VendorConfig.logConfigValues();
|
||||||
|
|
||||||
|
// Check if provisioning is enabled
|
||||||
|
if (!VendorConfig.isProvisioningEnabled()) {
|
||||||
|
Log.i(TAG, "Provisioning disabled by vendor config");
|
||||||
|
configureSetupWizard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Download failed", e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void disablePackage(String packageName) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = getPackageManager();
|
||||||
|
pm.setApplicationEnabledSetting(packageName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
|
||||||
|
Log.d(TAG, "Disabled package: " + packageName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "Failed to disable package: " + packageName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
125
src/dev/oxmc/configprovisioner/VendorConfig.java
Normal file
125
src/dev/oxmc/configprovisioner/VendorConfig.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package dev.oxmc.configprovisioner;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class VendorConfig {
|
||||||
|
private static final String TAG = "VendorConfig";
|
||||||
|
|
||||||
|
// Configuration path
|
||||||
|
public static final String VENDOR_CONFIG_PATH = "/vendor/etc/config_provisioner/vendor.cfg";
|
||||||
|
|
||||||
|
// Configuration keys
|
||||||
|
private static final String KEY_ENABLE_SETUP_WIZARD = "enable_setup_wizard";
|
||||||
|
private static final String KEY_ENABLE_PROVISIONING = "enable_provisioning";
|
||||||
|
private static final String KEY_VENDOR_ID = "vendor_id";
|
||||||
|
private static final String KEY_NETWORK_TIMEOUT = "network_timeout";
|
||||||
|
private static final String KEY_CONFIG_APK_URL = "config_apk_url";
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
public static final boolean DEFAULT_ENABLE_SETUP_WIZARD = true;
|
||||||
|
public static final boolean DEFAULT_ENABLE_PROVISIONING = true;
|
||||||
|
public static final int DEFAULT_NETWORK_TIMEOUT = 30000;
|
||||||
|
public static final String DEFAULT_CONFIG_APK_URL = "https://default.example.com/config.apk";
|
||||||
|
|
||||||
|
public static boolean hasVendorConfig() {
|
||||||
|
File configFile = new File(VENDOR_CONFIG_PATH);
|
||||||
|
boolean exists = configFile.exists();
|
||||||
|
Log.d(TAG, "Vendor config exists: " + exists + " at " + VENDOR_CONFIG_PATH);
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSetupWizardEnabled() {
|
||||||
|
return getConfigBoolean(KEY_ENABLE_SETUP_WIZARD, DEFAULT_ENABLE_SETUP_WIZARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isProvisioningEnabled() {
|
||||||
|
return getConfigBoolean(KEY_ENABLE_PROVISIONING, DEFAULT_ENABLE_PROVISIONING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getConfigApkUrl() {
|
||||||
|
return getConfigString(KEY_CONFIG_APK_URL, DEFAULT_CONFIG_APK_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVendorId() {
|
||||||
|
return getConfigString(KEY_VENDOR_ID, "default_vendor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNetworkTimeout() {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(getConfigString(KEY_NETWORK_TIMEOUT,
|
||||||
|
String.valueOf(DEFAULT_NETWORK_TIMEOUT)));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return DEFAULT_NETWORK_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getConfigBoolean(String key, boolean defaultValue) {
|
||||||
|
String value = getConfigValue(key);
|
||||||
|
if (value != null) {
|
||||||
|
return "true".equalsIgnoreCase(value) || "1".equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to system properties if config file doesn't exist
|
||||||
|
if (!hasVendorConfig()) {
|
||||||
|
String propValue = android.os.SystemProperties.get("persist.configprovisioner." + key, "");
|
||||||
|
if (!propValue.isEmpty()) {
|
||||||
|
return "true".equalsIgnoreCase(propValue) || "1".equals(propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getConfigString(String key, String defaultValue) {
|
||||||
|
String value = getConfigValue(key);
|
||||||
|
if (value != null) return value;
|
||||||
|
|
||||||
|
// Fallback to system properties if config file doesn't exist
|
||||||
|
if (!hasVendorConfig()) {
|
||||||
|
return android.os.SystemProperties.get("persist.configprovisioner." + key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getConfigValue(String key) {
|
||||||
|
if (!hasVendorConfig()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(VENDOR_CONFIG_PATH))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.startsWith("#") || line.isEmpty()) continue;
|
||||||
|
|
||||||
|
String[] parts = line.split("=", 2);
|
||||||
|
if (parts.length == 2 && parts[0].trim().equals(key)) {
|
||||||
|
return parts[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error reading vendor config", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get all config values for debugging
|
||||||
|
public static void logConfigValues() {
|
||||||
|
if (!hasVendorConfig()) {
|
||||||
|
Log.d(TAG, "No vendor config file found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Current configuration:");
|
||||||
|
Log.d(TAG, KEY_ENABLE_SETUP_WIZARD + "=" + isSetupWizardEnabled());
|
||||||
|
Log.d(TAG, KEY_ENABLE_PROVISIONING + "=" + isProvisioningEnabled());
|
||||||
|
Log.d(TAG, KEY_CONFIG_APK_URL + "=" + getConfigApkUrl());
|
||||||
|
Log.d(TAG, KEY_VENDOR_ID + "=" + getVendorId());
|
||||||
|
Log.d(TAG, KEY_NETWORK_TIMEOUT + "=" + getNetworkTimeout());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user