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:
oxmc
2026-06-12 20:02:46 -07:00
parent 51a93970d9
commit ec3e75de5d
10 changed files with 478 additions and 104 deletions
+26 -2
View File
@@ -1,3 +1,6 @@
// PawletOS Framework API Extensions
// Installed to product partition; visible to system apps and vendor subpackages.
java_sdk_library {
name: "pawlet-device",
srcs: ["core/java/pawletos/device/PawletDevice.java"],
@@ -15,7 +18,14 @@ java_sdk_library {
java_sdk_library {
name: "pawlet-system",
srcs: ["core/java/pawletos/device/system/PawletSystem.java"],
srcs: [
"core/java/pawletos/device/system/PawletSystem.java",
"core/java/pawletos/device/system/PawletManager.java",
"core/java/pawletos/device/system/IPawletManager.aidl",
],
aidl: {
local_include_dirs: ["core/java"],
},
api_packages: ["pawletos.device.system"],
sdk_version: "system_current",
product_specific: true,
@@ -24,6 +34,20 @@ java_sdk_library {
visibility: [
"//frameworks/base:__subpackages__",
"//packages/apps:__subpackages__",
"//vendor:__subpackages__",
],
unsafe_ignore_missing_latest_api: true,
}
}
// Service-side implementation — used by the privileged app that hosts the service.
java_library {
name: "pawlet-manager-service",
srcs: ["core/java/pawletos/device/system/PawletManagerService.java"],
libs: ["pawlet-system"],
sdk_version: "system_current",
product_specific: true,
visibility: [
"//packages/apps:__subpackages__",
"//vendor:__subpackages__",
],
}
+4 -4
View File
@@ -3,12 +3,12 @@
package="pawletos.device">
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="34" />
android:minSdkVersion="26"
android:targetSdkVersion="36" />
<application
android:label="PawletOS-DeviceAPI"
android:supportsRtl="false">
</application>
</manifest>
</manifest>
+56 -25
View File
@@ -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");
}
}
}
@@ -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();
}
@@ -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; }
}
}
@@ -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;
}
}
}
+3 -2
View File
@@ -1,2 +1,3 @@
# Label the JAR file
/system/framework/pawlet-device.jar u:object_r:pawlet_device_exec:s0
# PawletOS framework JARs
/system/framework/pawlet-device\.jar u:object_r:system_file:s0
/system/framework/pawlet-system\.jar u:object_r:system_file:s0
+9 -9
View File
@@ -1,14 +1,14 @@
# Domain declaration
# PawletOS framework service domain
type pawlet_device, domain, coredomain;
type pawlet_device_exec, exec_type, file_type;
# Inherit from core domain
typeattribute pawlet_device coredomain;
# Basic file access for your domain
allow pawlet_device pawlet_device_exec:file { execute read open map };
# Binder communication
# Binder communication with system_server and servicemanager
binder_use(pawlet_device)
binder_call(pawlet_device, system_server)
binder_call(pawlet_device, servicemanager)
binder_call(pawlet_device, servicemanager)
# Allow reading PawletOS system properties
get_prop(pawlet_device, system_prop)
# Silence audit noise from system domains reading vendor props they don't need
dontaudit domain vendor_internal_prop:file { read open getattr map };
+11 -4
View File
@@ -1,4 +1,11 @@
# Custom properties for the pawlet device
ro.vendor.oxmc.* u:object_r:vendor_default_prop:s0
ro.vendor.pawlet.* u:object_r:vendor_default_prop:s0
persist.vendor.pawlet.* u:object_r:vendor_default_prop:s0
# PawletOS system properties
ro.pawlet. u:object_r:system_prop:s0
ro.oxmc. u:object_r:system_prop:s0
# Vendor-internal properties (vendor domain only)
ro.vendor.oxmc. u:object_r:vendor_internal_prop:s0
ro.vendor.pawlet. u:object_r:vendor_internal_prop:s0
persist.vendor.pawlet. u:object_r:vendor_internal_prop:s0
# Feature flags (writable by pawlet service)
persist.pawlet.feature. u:object_r:vendor_internal_prop:s0