frameworks_pawlet: add PawletManager service + clean up APIs
Add IPawletManager AIDL interface, PawletManager client wrapper, and PawletManagerService implementation. The service is hosted by a privileged system app which calls: ServiceManager.addService(PawletManager.SERVICE_NAME, new PawletManagerService(this)); API surface: - Build info: getPawletVersion, getDisplayVersion, getReleaseType, getBuildDate - Device info: getDeviceCodename, getDeviceModel, getHardwarePlatform, getSerialNumber - Provisioning: isDeviceProvisioned, isUserSetupComplete - Feature flags: isFeatureEnabled / setFeatureEnabled (persist.pawlet.feature.*) - SDK version: getPawletSdkVersion PawletDevice: remove broken resource lookup, use system props directly; add isPawletDevice, getBuildVersion, getDisplayVersion, getReleaseType, getHardwarePlatform, getSerialNumber, getBuildDate. PawletSystem: remove dead SDK<24 branches; improve isRooted to check su binary paths and Magisk; keep isEmulator with same logic. sepolicy: vendor_internal_prop for persist.* and vendor.* props, dontaudit for system domains; fix file_contexts JAR labels. Android.bp: add aidl block, pawlet-manager-service java_library. AndroidManifest: minSdk 26, targetSdk 36.
This commit is contained in:
@@ -1,48 +1,79 @@
|
||||
package pawletos.device;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
/**
|
||||
* PawletOS Device API for apps running on PawletOS builds.
|
||||
* Public PawletOS device information API.
|
||||
* Available to all apps via the pawlet-device SDK library.
|
||||
*/
|
||||
public class PawletDevice {
|
||||
public final class PawletDevice {
|
||||
|
||||
/**
|
||||
* Get the brand name.
|
||||
*/
|
||||
public static String getBrandName(Context ctx) {
|
||||
int resId = ctx.getResources().getIdentifier("oxmc_brand_name", "string", ctx.getPackageName());
|
||||
if (resId != 0) {
|
||||
return ctx.getString(resId);
|
||||
}
|
||||
return "PawletOS";
|
||||
private PawletDevice() {}
|
||||
|
||||
/** Returns true if this device is running PawletOS. */
|
||||
public static boolean isPawletDevice() {
|
||||
return !SystemProperties.get("ro.pawlet.build.version", "").isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the codename.
|
||||
*/
|
||||
public static String getCodename(Context ctx) {
|
||||
int resId = ctx.getResources().getIdentifier("oxmc_codename", "string", ctx.getPackageName());
|
||||
if (resId != 0) {
|
||||
return ctx.getString(resId);
|
||||
}
|
||||
return "generic-pawlet-device";
|
||||
/** PawletOS major.minor version string, e.g. "1.0". */
|
||||
public static String getBuildVersion() {
|
||||
return SystemProperties.get("ro.pawlet.build.version", "unknown");
|
||||
}
|
||||
|
||||
// ─────────────────────────────
|
||||
// Custom build property access
|
||||
// ─────────────────────────────
|
||||
/** Full display version including date and type, e.g. "1-20260101-RELEASE-pawlet_rpi4". */
|
||||
public static String getDisplayVersion() {
|
||||
return SystemProperties.get("ro.pawlet.display.version", getBuildVersion());
|
||||
}
|
||||
|
||||
/** Release type: RELEASE, NIGHTLY, SNAPSHOT, EXPERIMENTAL, or UNOFFICIAL. */
|
||||
public static String getReleaseType() {
|
||||
return SystemProperties.get("ro.pawlet.releasetype", "UNOFFICIAL");
|
||||
}
|
||||
|
||||
/** Device codename as used in builds, e.g. "pawlet_rpi4". */
|
||||
public static String getDeviceCodename() {
|
||||
return SystemProperties.get("ro.product.device", Build.DEVICE);
|
||||
}
|
||||
|
||||
/** Human-readable device model string. */
|
||||
public static String getDeviceModel() {
|
||||
return SystemProperties.get("ro.product.model", Build.MODEL);
|
||||
}
|
||||
|
||||
/** Hardware platform, e.g. "bcm2711" (RPi 4) or "bcm2712" (RPi 5). */
|
||||
public static String getHardwarePlatform() {
|
||||
return SystemProperties.get("ro.hardware", Build.HARDWARE);
|
||||
}
|
||||
|
||||
/** Device serial number. */
|
||||
public static String getSerialNumber() {
|
||||
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
|
||||
}
|
||||
|
||||
/** Build date as a Unix timestamp string from ro.build.date.utc. */
|
||||
public static long getBuildDate() {
|
||||
try {
|
||||
return Long.parseLong(SystemProperties.get("ro.build.date.utc", "0"));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Legacy vendor properties ──────────────────────────────────────────────
|
||||
|
||||
/** Manufacture date from vendor property, or "unknown". */
|
||||
public static String getManufactureDate() {
|
||||
return SystemProperties.get("ro.oxmc.build.manufacture_date", "unknown");
|
||||
}
|
||||
|
||||
/** Warranty exclusion string from vendor property, or "none". */
|
||||
public static String getWarrantyExclusion() {
|
||||
return SystemProperties.get("ro.oxmc.build.warranty_exclusion", "none");
|
||||
}
|
||||
|
||||
/** Product series from vendor property, or "generic". */
|
||||
public static String getSeries() {
|
||||
return SystemProperties.get("ro.oxmc.build.series", "generic");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
core/java/pawletos/device/system/IPawletManager.aidl
Normal file
61
core/java/pawletos/device/system/IPawletManager.aidl
Normal file
@@ -0,0 +1,61 @@
|
||||
package pawletos.device.system;
|
||||
|
||||
/**
|
||||
* System service interface for PawletOS platform APIs.
|
||||
* Obtain via PawletManager.get(context).
|
||||
* @hide
|
||||
*/
|
||||
interface IPawletManager {
|
||||
|
||||
// ── Build info ────────────────────────────────────────────────────────────
|
||||
|
||||
/** PawletOS major.minor version string, e.g. "1.0". */
|
||||
String getPawletVersion();
|
||||
|
||||
/** Full display version, e.g. "1-20260101-RELEASE-pawlet_rpi4". */
|
||||
String getDisplayVersion();
|
||||
|
||||
/** Release type: RELEASE, NIGHTLY, SNAPSHOT, EXPERIMENTAL, or UNOFFICIAL. */
|
||||
String getReleaseType();
|
||||
|
||||
/** Build date as Unix timestamp. */
|
||||
long getBuildDate();
|
||||
|
||||
// ── Device info ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Device codename, e.g. "pawlet_rpi4". */
|
||||
String getDeviceCodename();
|
||||
|
||||
/** Human-readable device model string. */
|
||||
String getDeviceModel();
|
||||
|
||||
/** Hardware platform, e.g. "bcm2711". */
|
||||
String getHardwarePlatform();
|
||||
|
||||
/** Device serial number. */
|
||||
String getSerialNumber();
|
||||
|
||||
// ── Provisioning ─────────────────────────────────────────────────────────
|
||||
|
||||
/** True if DEVICE_PROVISIONED=1. */
|
||||
boolean isDeviceProvisioned();
|
||||
|
||||
/** True if USER_SETUP_COMPLETE=1. */
|
||||
boolean isUserSetupComplete();
|
||||
|
||||
// ── Feature flags ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Check if a named feature flag is enabled.
|
||||
* Backed by persist.pawlet.feature.<name>=1.
|
||||
*/
|
||||
boolean isFeatureEnabled(in String feature);
|
||||
|
||||
/** Enable or disable a named feature flag (requires MANAGE_PAWLET_FEATURES permission). */
|
||||
void setFeatureEnabled(in String feature, boolean enabled);
|
||||
|
||||
// ── SDK ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** PawletOS SDK/API version integer. */
|
||||
int getPawletSdkVersion();
|
||||
}
|
||||
118
core/java/pawletos/device/system/PawletManager.java
Normal file
118
core/java/pawletos/device/system/PawletManager.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package pawletos.device.system;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Client-side manager for the PawletOS system service.
|
||||
*
|
||||
* Usage:
|
||||
* PawletManager pm = PawletManager.get(context);
|
||||
* if (pm != null) {
|
||||
* String version = pm.getPawletVersion();
|
||||
* }
|
||||
*
|
||||
* The service is registered by a privileged system app at boot via:
|
||||
* ServiceManager.addService(PawletManager.SERVICE_NAME, new PawletManagerService(context));
|
||||
*/
|
||||
public final class PawletManager {
|
||||
|
||||
public static final String SERVICE_NAME = "pawlet";
|
||||
private static final String TAG = "PawletManager";
|
||||
|
||||
private final IPawletManager mService;
|
||||
|
||||
private PawletManager(IPawletManager service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PawletManager instance, or null if the service is not running.
|
||||
*/
|
||||
public static PawletManager get(Context context) {
|
||||
IBinder binder = ServiceManager.getService(SERVICE_NAME);
|
||||
if (binder == null) {
|
||||
Log.w(TAG, "pawlet service not available");
|
||||
return null;
|
||||
}
|
||||
return new PawletManager(IPawletManager.Stub.asInterface(binder));
|
||||
}
|
||||
|
||||
// ── Build info ────────────────────────────────────────────────────────────
|
||||
|
||||
public String getPawletVersion() {
|
||||
try { return mService.getPawletVersion(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public String getDisplayVersion() {
|
||||
try { return mService.getDisplayVersion(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public String getReleaseType() {
|
||||
try { return mService.getReleaseType(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public long getBuildDate() {
|
||||
try { return mService.getBuildDate(); }
|
||||
catch (RemoteException e) { return 0L; }
|
||||
}
|
||||
|
||||
// ── Device info ───────────────────────────────────────────────────────────
|
||||
|
||||
public String getDeviceCodename() {
|
||||
try { return mService.getDeviceCodename(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public String getDeviceModel() {
|
||||
try { return mService.getDeviceModel(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public String getHardwarePlatform() {
|
||||
try { return mService.getHardwarePlatform(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
try { return mService.getSerialNumber(); }
|
||||
catch (RemoteException e) { return null; }
|
||||
}
|
||||
|
||||
// ── Provisioning ─────────────────────────────────────────────────────────
|
||||
|
||||
public boolean isDeviceProvisioned() {
|
||||
try { return mService.isDeviceProvisioned(); }
|
||||
catch (RemoteException e) { return false; }
|
||||
}
|
||||
|
||||
public boolean isUserSetupComplete() {
|
||||
try { return mService.isUserSetupComplete(); }
|
||||
catch (RemoteException e) { return false; }
|
||||
}
|
||||
|
||||
// ── Feature flags ─────────────────────────────────────────────────────────
|
||||
|
||||
public boolean isFeatureEnabled(String feature) {
|
||||
try { return mService.isFeatureEnabled(feature); }
|
||||
catch (RemoteException e) { return false; }
|
||||
}
|
||||
|
||||
public void setFeatureEnabled(String feature, boolean enabled) {
|
||||
try { mService.setFeatureEnabled(feature, enabled); }
|
||||
catch (RemoteException e) { Log.e(TAG, "setFeatureEnabled failed", e); }
|
||||
}
|
||||
|
||||
// ── SDK ───────────────────────────────────────────────────────────────────
|
||||
|
||||
public int getPawletSdkVersion() {
|
||||
try { return mService.getPawletSdkVersion(); }
|
||||
catch (RemoteException e) { return 0; }
|
||||
}
|
||||
}
|
||||
135
core/java/pawletos/device/system/PawletManagerService.java
Normal file
135
core/java/pawletos/device/system/PawletManagerService.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package pawletos.device.system;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Implementation of IPawletManager, hosted by a privileged system app.
|
||||
*
|
||||
* The hosting app registers this on boot:
|
||||
* ServiceManager.addService(PawletManager.SERVICE_NAME, new PawletManagerService(this));
|
||||
*
|
||||
* Requires the hosting app to hold INTERACT_ACROSS_USERS_FULL for Settings access.
|
||||
*/
|
||||
public class PawletManagerService extends IPawletManager.Stub {
|
||||
|
||||
public static final int PAWLET_SDK_VERSION = 1;
|
||||
|
||||
private static final String TAG = "PawletManagerService";
|
||||
private static final String FEATURE_PROP_PREFIX = "persist.pawlet.feature.";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public PawletManagerService(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
// ── Build info ────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String getPawletVersion() {
|
||||
return SystemProperties.get("ro.pawlet.build.version", "unknown");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayVersion() {
|
||||
return SystemProperties.get("ro.pawlet.display.version", getPawletVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReleaseType() {
|
||||
return SystemProperties.get("ro.pawlet.releasetype", "UNOFFICIAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBuildDate() {
|
||||
try {
|
||||
return Long.parseLong(SystemProperties.get("ro.build.date.utc", "0"));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Device info ───────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String getDeviceCodename() {
|
||||
return SystemProperties.get("ro.product.device", Build.DEVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceModel() {
|
||||
return SystemProperties.get("ro.product.model", Build.MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHardwarePlatform() {
|
||||
return SystemProperties.get("ro.hardware", Build.HARDWARE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
|
||||
}
|
||||
|
||||
// ── Provisioning ─────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public boolean isDeviceProvisioned() {
|
||||
try {
|
||||
return Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.DEVICE_PROVISIONED, 0) == 1;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "isDeviceProvisioned: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupComplete() {
|
||||
try {
|
||||
return Settings.Secure.getInt(mContext.getContentResolver(),
|
||||
Settings.Secure.USER_SETUP_COMPLETE, 0) == 1;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "isUserSetupComplete: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Feature flags ─────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public boolean isFeatureEnabled(String feature) {
|
||||
if (feature == null || feature.isEmpty()) return false;
|
||||
return "1".equals(SystemProperties.get(FEATURE_PROP_PREFIX + feature, "0"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeatureEnabled(String feature, boolean enabled) {
|
||||
if (feature == null || feature.isEmpty()) return;
|
||||
enforceFeaturePermission();
|
||||
SystemProperties.set(FEATURE_PROP_PREFIX + feature, enabled ? "1" : "0");
|
||||
Log.i(TAG, "Feature " + feature + " " + (enabled ? "enabled" : "disabled")
|
||||
+ " by uid=" + Binder.getCallingUid());
|
||||
}
|
||||
|
||||
// ── SDK ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public int getPawletSdkVersion() {
|
||||
return PAWLET_SDK_VERSION;
|
||||
}
|
||||
|
||||
// ── Internal ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void enforceFeaturePermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
"pawletos.permission.MANAGE_PAWLET_FEATURES",
|
||||
"Requires MANAGE_PAWLET_FEATURES permission");
|
||||
}
|
||||
}
|
||||
@@ -5,75 +5,72 @@ import android.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
/**
|
||||
* PawletOS System API for system apps (OTA, device management, etc.)
|
||||
* Static system utilities for PawletOS system apps.
|
||||
* For richer runtime data, use PawletManager.get(context) instead.
|
||||
*/
|
||||
public class PawletSystem {
|
||||
private static final double PAWLET_SDK_VERSION = 1.0;
|
||||
public final class PawletSystem {
|
||||
|
||||
/**
|
||||
* Check if running on an emulator.
|
||||
*/
|
||||
public static final int PAWLET_SDK_VERSION = 1;
|
||||
|
||||
private PawletSystem() {}
|
||||
|
||||
/** True if running on a PawletOS build. */
|
||||
public static boolean isPawletOS() {
|
||||
return !SystemProperties.get("ro.pawlet.build.version", "").isEmpty();
|
||||
}
|
||||
|
||||
/** True if this is a debug/userdebug build. */
|
||||
public static boolean isDebugBuild() {
|
||||
return "1".equals(SystemProperties.get("ro.debuggable", "0"));
|
||||
}
|
||||
|
||||
/** True if this is a release-signed build (not test-keys). */
|
||||
public static boolean isReleaseBuild() {
|
||||
String tags = Build.TAGS;
|
||||
return tags != null && tags.contains("release-keys");
|
||||
}
|
||||
|
||||
/** Current locale tag, e.g. "en_US". */
|
||||
public static String getCurrentLocale(Context ctx) {
|
||||
return ctx.getResources().getConfiguration().getLocales().get(0).toString();
|
||||
}
|
||||
|
||||
/** True if the device appears to be an emulator. */
|
||||
public static boolean isEmulator() {
|
||||
String fingerprint = Build.FINGERPRINT;
|
||||
String model = Build.MODEL;
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
String brand = Build.BRAND;
|
||||
String device = Build.DEVICE;
|
||||
String product = Build.PRODUCT;
|
||||
return fingerprint != null && (fingerprint.startsWith("generic") || fingerprint.startsWith("unknown"))
|
||||
|| model != null && model.contains("google_sdk")
|
||||
|| model != null && model.contains("Emulator")
|
||||
|| model != null && model.contains("Android SDK built for x86")
|
||||
|| manufacturer != null && manufacturer.contains("Genymotion")
|
||||
|| brand != null && brand.startsWith("generic") && device != null && device.startsWith("generic")
|
||||
|| product != null && product.equals("google_sdk");
|
||||
String fp = Build.FINGERPRINT;
|
||||
return (fp != null && (fp.startsWith("generic") || fp.startsWith("unknown")))
|
||||
|| Build.MODEL.contains("google_sdk")
|
||||
|| Build.MODEL.contains("Emulator")
|
||||
|| Build.MODEL.contains("Android SDK built for x86")
|
||||
|| Build.MANUFACTURER.contains("Genymotion")
|
||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||
|| "google_sdk".equals(Build.PRODUCT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic check if device is rooted.
|
||||
* Heuristic check for whether the device has root access.
|
||||
* Checks for su binaries in common locations and the ro.debuggable property.
|
||||
* Not foolproof — a determined user can hide root.
|
||||
*/
|
||||
public static boolean isRooted() {
|
||||
String buildTags = Build.TAGS;
|
||||
if (buildTags != null && buildTags.contains("test-keys")) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
java.io.File file = new java.io.File("/system/app/Superuser.apk");
|
||||
if (file.exists()) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
// Common su locations
|
||||
String[] suPaths = {
|
||||
"/system/bin/su",
|
||||
"/system/xbin/su",
|
||||
"/sbin/su",
|
||||
"/vendor/bin/su",
|
||||
"/system/su",
|
||||
};
|
||||
for (String path : suPaths) {
|
||||
if (new java.io.File(path).exists()) return true;
|
||||
}
|
||||
// Magisk manager package presence
|
||||
if (new java.io.File("/data/adb/magisk").exists()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current language/locale.
|
||||
*/
|
||||
public static String getCurrentLocale(Context ctx) {
|
||||
java.util.Locale locale;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 24) {
|
||||
locale = ctx.getResources().getConfiguration().getLocales().get(0);
|
||||
} else {
|
||||
locale = ctx.getResources().getConfiguration().locale;
|
||||
}
|
||||
return locale.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device is running PawletOS.
|
||||
* Returns true if running on PawletOS, false otherwise.
|
||||
*/
|
||||
public static boolean isPawletOS() {
|
||||
return "Pawlet".equalsIgnoreCase(Build.MANUFACTURER)
|
||||
|| "PawletOS".equalsIgnoreCase(SystemProperties.get("ro.oxmc.os_name", ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PawletSDK version.
|
||||
*/
|
||||
public static double getPawletSDKVersion() {
|
||||
/** PawletOS SDK version integer. Matches PawletManagerService.PAWLET_SDK_VERSION. */
|
||||
public static int getPawletSdkVersion() {
|
||||
return PAWLET_SDK_VERSION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user