system-extension

Maven CentralAndroid Min SDK

This is a dependency for system layer related extensions.

Configure Dependency

You can add this module to your project using the following method.

Add dependency in your project's SweetDependency configuration file.

libraries:
  com.highcapable.betterandroid:
    system-extension:
      version: +

Configure dependency in your project build.gradle.kts.

implementation(com.highcapable.betterandroid.system.extension)

Traditional Method

Configure dependency in your project build.gradle.kts.

implementation("com.highcapable.betterandroid:system-extension:<version>")

Please change <version> to the version displayed at the top of this document.

Function Introduction

You can view the KDoc click hereopen in new window.

Application Extension

BetterAndroid provides extended functions for PackageManager, PackageInfo, ApplicationInfo and other functions, so that you can use these functions more conveniently.

They are collectively classified as application extensions, meaning application-related functions.

Below are examples of how to use these extensions.

Get the ComponentName of a component class through generics.

The following example

// Assume this is your context.
val context: Context
// Get the ComponentName of MainActivity.
val componentName = context.getComponentName<MainActivity>()

Determine whether the apps has been installed.

The following example

// Assume this is your context.
val context: Context
// For example, determine whether Chrome is installed.
val hasChrome = context.packageManager.hasPackage("com.android.chrome")

Determine whether the apps has a startable Activity.

This function is mainly used to determine whether the app has an Activity that can be launched from the launcher.

The following example

// Assume this is your context.
val context: Context
// For example, determine the com.mydemo.test app.
val hasLaunchActivity = context.packageManager.hasLaunchActivity("com.mydemo.test")

Notice

Starting from Android 10, even if the app does not have an Activity that declares ACTION_MAIN and CATEGORY_LAUNCHER, its icon may still be displayed on the launcher, and clicking it will open the application information interface, but this does not mean that it has a launchable Activity.

Get apps package information.

BetterAndroid provides an overloaded method with the same name for getPackageInfo, you don’t need to consider compatibility issues, just use PackageInfoFlagsWrapper as the parameter of flags.

The reason for overloading this method is that in Android 13, the official method of Int type flags was invalidated and a new solution was enabled, however, no compatibility processing tools were provided, but it was later canceled in Android 14, which will cause great trouble to developers.

The following example

// Assume this is your context.
val context: Context
// For example, get Chrome's PackageInfo.
val packageInfo = context.packageManager.getPackageInfo("com.android.chrome")
// You can pass in one or more PackageInfoFlagsWrapper objects in the second parameter
// to set flags instead of using bit operations.
// PackageInfoFlagsWrapper is a wrapper mirror for all PackageInfo flags.
val packageInfo = context.packageManager
    .getPackageInfo("com.android.chrome", PackageInfoFlagsWrapper.GET_META_DATA)

Tips

When you are not sure whether PackageInfo can be obtained successfully, you can replace the obtaining method with getPackageInfoOrNull, so that if the acquisition fails, null will be returned instead of throwing an exception.

Get a list of installed apps package information.

BetterAndroid also provides an overloaded method with the same name for getInstalledPackages. You don't need to consider compatibility issues, just use PackageInfoFlagsWrapper as the parameter of flags.

The following example

// Assume this is your context.
val context: Context
// Get a list of all installed apps package information.
val packageInfos = context.packageManager.getInstalledPackages()
// Similarly, you can pass in one or more PackageInfoFlagsWrapper objects
// in the second parameter to set flags.
val packageInfos = context.packageManager.getInstalledPackages(PackageInfoFlagsWrapper.GET_META_DATA)

Tips

Similarly, when you are not sure whether PackageInfo can be obtained successfully, you can replace the obtaining method with getInstalledPackagesOrNull.

Query all launchable Activities of an app.

This method is implemented based on queryIntentActivities and getLaunchIntentForPackage, simplifying the acquisition process.

The following example

// Assume this is your context.
val context: Context
// For example, query all launchable activities of Chrome.
val launchActivities = context.packageManager.queryLaunchActivitiesForPackage("com.android.chrome")

Tips

Similarly, when you are not sure whether ResolveInfo can be obtained successfully, you can replace the obtaining method with queryLaunchActivitiesForPackageOrNull.

Determine whether the component declared by the app is enabled or in the default state.

BetterAndroid encapsulates the getComponentEnabledSetting method, you can use the following methods to determine the component status faster.

The default state is the state declared by the app itself in AndroidManifest.xml, or the enabled state if not declared.

The following example

// Assume this is your context.
val context: Context
// Get the ComponentName of MainActivity.
val mainComponent = context.getComponentName<MainActivity>()
// Determine whether MainActivity is enabled.
val isEnabled = context.packageManager.isComponentEnabled(mainComponent)

Enable, disable, or reset components declared by the app.

BetterAndroid encapsulates the setComponentEnabledSetting method, you can use the following methods to complete this operation faster.

The reset operation will reset to the default state, which is the state declared by the app itself in AndroidManifest.xml, or the enabled state if not declared.

The following example

// Assume this is your context.
val context: Context
// Get the ComponentName of MainActivity.
val mainComponent = context.getComponentName<MainActivity>()
// Enable MainActivity.
context.packageManager.enableComponent(mainComponent)
// Disable MainActivity.
context.packageManager.disableComponent(mainComponent)
// Reset MainActivity.
context.packageManager.resetComponent(mainComponent)

Pay Attention

Your app does not have permission to enable or disable components of other apps unless it is in the system user group.

Get the version code of the apps.

longVersionCode is a new feature introduced in Android 9, it is an expanded version of versionCode and is used to solve the problem of insufficient digits in versionCode.

Since versionCode has been marked as invalid, and it is too cumbersome for developers to use PackageInfoCompat.getLongVersionCode provided by androidx, this method is basically difficult to find.

The use of two version numbers at the same time will also cause problems, developers cause trouble.

For this purpose, BetterAndroid encapsulates the compatible implementation of version number, you don't need to think about versionCode and longVersionCode now.

You can directly use versionCodeCompat to get the version number of the apps, and its type will always remain is Long.

The following example

// Assume this is your context.
val context: Context
// For example, get the version number of Chrome.
val versionCode = context.packageManager.getPackageInfo("com.android.chrome").versionCodeCompat

Gets the CPU ABI name of the apps.

This is a hidden API, so BetterAndroid is obtained through reflection, and you may need to use it in some specific scenarios.

The following example

// Assume this is your context.
val context: Context
// For example, get Chrome's PackageInfo.
val packageInfo = context.packageManager.getPackageInfo("com.android.chrome")
// Get the main CPU ABI name.
val primaryCpuAbi = packageInfo.applicationInfo.primaryCpuAbi
// Get the secondary CPU ABI name.
val secondaryCpuAbi = packageInfo.applicationInfo.secondaryCpuAbi

Determines whether ApplicationInfo contains the specified flags.

BetterAndroid encapsulates the method of judging flags through bit operations, you can use the following method to complete this operation faster.

The following example

// Assume this is your context.
val context: Context
// For example, get Chrome's ApplicationInfo.
val applicationInfo = context.packageManager.getPackageInfo("com.android.chrome").applicationInfo
// Determine whether Chrome is a system app.
// You can pass in one or more ApplicationInfoFlagsWrapper objects
// to set flags instead of using bit operations.
// ApplicationInfoFlagsWrapper is a wrapper image for all ApplicationInfo flags.
// For ease of reading, all flags have the FLAG prefix removed in ApplicationInfoFlagsWrapper.
val isSystemApp = applicationInfo.hasFlags(ApplicationInfoFlagsWrapper.SYSTEM)

Notice

In all of the above features, when it comes to querying package behavior outside of your own app, Android 11 and later require the QUERY_ALL_PACKAGES permission or explicitly configure a queries property list.

Please refer to Package visibility filtering on Androidopen in new window.

Broadcast Extension

Broadcast is a very important feature in Android, which allows apps to communicate with each other.

BetterAndroid provides a dynamic registration solution at runtime for broadcast, you can send broadcasts and create BoardcastReceiver more easily.

You can use the following methods to send and receive normal broadcasts without declaring them in AndroidManifest.xml.

For example, we want to send a broadcast to com.example.app.

The following example

// Assume this is your context.
val context: Context
// Send a normal broadcast to com.example.app.
context.sendBroadcast("com.example.app") {
    // Set action.
    action = "com.example.app.action.KNOCK"
    // Pass a string parameter.
    putExtra("greetings", "Hey you!")
}
// Specifying the receiver's package name parameter is optional,
// if you not fill in a package name, this will be received by all receivers
// added with the following action.
context.sendBroadcast {
    // Set action.
    action = "com.example.app.action.KNOCK"
    // Pass a string parameter.
    putExtra("greetings", "Hey there!")
}

In com.example.app, we can receive this broadcast like this.

The following example

// Assume this is your context.
val context: Context
// Create intent filter.
val filter = IntentFilter().apply {
    // Specify the sender's action.
    addAction("com.example.app.action.KNOCK")
}
// Register broadcast receiver.
// The callback here is the onReceive method, which is synchronous (main thread).
context.registerReceiver(filter) { context, intent ->
    // Get the passed string parameters.
    val greetings = intent.getStringExtra("greetings")
    // Use toast to show the received parameters.
    context.toast(greetings)
}

Tips

You can set the exported parameter (default is true) in the registerReceiver method to determine whether the current broadcast receiver needs to add Context.RECEIVER_EXPORTED.

If your broadcast is not open to the outside apps, you can set it to false.

Notice

In Android 14 or higher, a runtime-registered broadcast receiver must specify an exported behavior to receive broadcasts from another apps, if the current targets Android version 14 and above, you must ensure that the exported parameter is true to receive broadcasts from another apps, otherwise an exception will be thrown directly.

Please refer to Runtime-registered broadcasts receivers must specify export behavioropen in new window.

Clipboard Extension

The clipboard is a very important function that is often used in app development, but its use is not very user-friendly.

You need to use getSystemService to get ClipboardManager, and then use setPrimaryClip to set the clipboard content.

Of course, you can also use getPrimaryClip to read the clipboard content.

Sometimes we only need to set or read a string, but these operations require writing a lot of code, which is very unfriendly to developers.

For this reason, BetterAndroid provides a simpler solution for the clipboard, you can directly use the following methods to set or read the clipboard content.

Read the contents of the clipboard.

Now, you can directly replace ClipData.getItemAt and ClipData.getItemCount with ClipData.listOfItems.

This method will return a List<ClipData.Item>, you can use firstOrNull to get the first ClipData.Item, or use isEmpty to directly determine whether there is content in the clipboard.

Its benefits it means that you no longer need to consider whether the array will out of bounds.

The following example

// Assume this is your context.
val context: Context
// Get the clipboard manager.
val manager = context.clipboardManager
// Get the first ClipData.Item in the clipboard.
// Normally, you only need to get the first object.
val clipItem = manager.primaryClip?.listOfItems()?.firstOrNull()
// Get the copied text.
val copiedText = clipItem?.text

Copy a text to the clipboard.

You don't need to write setPrimaryClip(ClipData.newPlainText("Lable", "Text")) anymore, copying a text should be simple.

The following example

// Assume this is your context.
val context: Context
// Copy a text to the clipboard.
context.clipboardManager.copy("Hello World!")
// Add a label to this text.
context.clipboardManager.copy("Hello World!", "MyText")

Copy HTML type text, Uri, Intent to the clipboard.

No matter what content you copy, you can do it using the copy method.

The following example

// Assume this is your context.
val context: Context
// Copy HTML type text to the clipboard.
context.clipboardManager.copy("Hello World!", "<b>Hello World!</b>")
// Copy uri to clipboard.
context.clipboardManager.copy(Uri.parse("some://uri"), context.contentResolver)
// Copy intent to the clipboard.
context.clipboardManager.copy(Intent(Intent.ACTION_VIEW, Uri.parse("some://uri")))

Copies the contents of a custom ClipData to the clipboard.

You can use the ClipData method to create a new ClipData object and then copy it to the clipboard.

The following example

// Assume this is your context.
val context: Context
// Create ClipData object.
val clipData = ClipData {
    addText("Hello World!")
    addHtmlText("Hello World!", "<b>Hello World!</b>")
    addUri(Uri.parse("some://uri"), context.contentResolver)
    addIntent(Intent(Intent.ACTION_VIEW, Uri.parse("some://uri")))
}
// Copy to clipboard.
context.clipboardManager.copy(clipData)

Notice

In Android 10 or later, the contents of the clipboard cannot be read while your app is in the background unless your app is an input method (IME).

Please refer to Limited access to clipboard dataopen in new window.

Intent Extension

Currently, the extension methods in Intent are only used to handle the acquisition methods of Serializable and Parcelable types.

They are marked as deprecated in Android 13 and the official does not provide any effective compatible handling method.

You can use the compatibility handling methods provided by BetterAndroid to obtain data of Serializable and Parcelable types.

The following example

// Assume this is your intent.
val intent: Intent
// Get data of serializable type.
val myData = intent.getSerializableExtraCompat<MyData>("my_key_name")
val myData = intent.extras?.getSerializableCompat<MyData>("my_key_name")
// Get data of parcelable type.
val myData = intent.getParcelableExtraCompat<MyData>("my_key_name")
val myData = intent.extras?.getParcelableCompat<MyData>("my_key_name")

The following is a comparison table of the original method and the compatibility method.

Original MethodCompatibility Method
Intent.getSerializableExtraIntent.getSerializableExtraCompat
Bundle.getSerializableBundle.getSerializableCompat
Intent.getParcelableExtraIntent.getParcelableExtraCompat
Bundle.getParcelableBundle.getParcelableCompat

Service Extension

Similar to how to start Activity, when we need to start a Service, we need to use Intent to create an Intent(this, MyService::class.java), and then call startService(intent) to start it.

This may not be very friendly to write, so BetterAndroid provides an extension for Service, now you can directly use the following method to start a Service.

The following example

// Assume this is your context.
val context: Context
// Assume MyService is your target service.
context.startService<MyService>()
// You can create an intent using the following method.
context.startService<MyService> {
    // Add some extra parameters here.
    putExtra("key", "value")
}

If you need to start a Service of an external app, you can use the following method.

The following example

// Assume this is your context.
val context: Context
// Assume that the app package you need to start is named com.example.app.
// Assume that the service class you need to start is named com.example.app.MyService.
context.startService("com.example.app", "com.example.app.MyService")
// You can still create an intent using the following method.
context.startService("com.example.app", "com.example.app.MyService") {
    // Add some extra parameters here.
    putExtra("key", "value")
}

Tips

You can use startServiceOrElse and startForegroundServiceOrElse instead of startService and startForegroundService to determine whether the Service can be started successfully.

If the startup fails, this method will not throw an exception but return false.

Notice

In Android 8 or higher, in some cases, you may need to use startForegroundService to start a foreground service.

Please refer to Background Execution Limitsopen in new window.

System Infomation

Content of This Section

SystemVersionopen in new window

System version tool.

SystemKindopen in new window

System kind tool.

SystemPropertiesopen in new window

Android's SystemProperties tool.

Maybe you are tired of Build.VERSION.SDK_INT and Build.VERSION_CODES floating around in your code, so from now on, you no longer need to use them.

BetterAndroid has prepared simpler writing methods for you to replace them.

Previously, we needed to determine the Android API level of the current system, which was basically done in the following ways.

The following example

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    // Execute relevant code.
}
// Or use hard-coded API version code.
if (Build.VERSION.SDK_INT >= 29) {
    // Execute relevant code.
}

Now, you can do this very easily in the following way.

The following example

SystemVersion.require(SystemVersion.Q) {
    // Execute relevant code.
}
// Or use hard-coded API version code.
SystemVersion.require(29) {
    // Execute relevant code.
}
// result will get "target" when API is greater than or equal to 29, otherwise it will be "legacy".
val result = SystemVersion.require(SystemVersion.Q, "legacy") { "target" }
// If it is a nullable result, you can use the following method.
val myData: MyData?
val result = SystemVersion.requireOrNull(SystemVersion.Q, MyData()) { myData }

You can also use the following methods to judge.

The following example

// Determine whether API is less than 29.
if (SystemVersion.isLowTo(SystemVersion.Q)) {
    // Execute relevant code.
}
// Determine whether API is greater than 29.
if (SystemVersion.isHighTo(SystemVersion.Q)) {
    // Execute relevant code.
}
// Determine whether API is less than or equal to 29.
if (SystemVersion.isLowOrEqualsTo(SystemVersion.Q)) {
    // Execute relevant code.
}
// Determine whether the API is greater than or equal to 29.
if (SystemVersion.isHighOrEqualsTo(SystemVersion.Q)) {
    // Execute relevant code.
}
// Determine whether the API is between 26 and 29.
if (SystemVersion.isBetween(SystemVersion.O..SystemVersion.Q)) {
    // Execute relevant code.
}

The following is the constant mapping comparison table for each API, after Android version update, BetterAndroid will update these constants synchronously.

API LevelSystemVersion NameBuild.VERSION_CODES NameCorresponding System Version
19KKITKAT4.4.3, 4.4.4
20K_WKITKAT_WATCH4.4W
21LLOLLIPOP5.0, 5.0.2
22L_MR1LOLLIPOP_MR15.1, 5.1.1
23MM6.0, 6.0.1
24NN7.0
25N_MR1N_MR17.1, 7.1.1, 7.1.2
26OO8.0
27O_MR1O_MR18.1
28PP9
29QQ10
30RR11
31SS12
32S_V2S_V212.1, 12L
33TTIRAMISU13
34UUPSIDE_DOWN_CAKE14

In addition to judging the API level, you can also use the following method to get the current Android version name.

The following example

// Get the current Android version name.
// It is equivalent to Build.VERSION.RELEASE.
// For example, the version name of Android 10 is the string "10".
val versionName = SystemVersion.name

As various manufacturers have successively released more and more deeply customized Android systems for their own brand Android mobile phones, sometimes it is very necessary for us to make targeted adaptations for the different functions of each customized version of the system, but how to judge the type of these systems is a big question.

Usually, everyone’s solution is to determine the model of the device to determine what kind of customized system it is, however, if the current device is not running the customized system you judged, such as the case where the user flashes the phone by himself, then this solution is will fail.

BetterAndroid provides you with a simple, fast and efficient solution by collecting corresponding features of various common custom systems.

The following is a simple example to determine the type of current system.

The following example

// Determine whether the current system kind is MIUI.
if (SystemKind.equals(SystemKind.MIUI)) {
    // Execute relevant code.
}

Yes, it's that simple, if you need to judge multiple system types at the same time, you can also use the following method.

The following example

// Get the current system kind.
val kind = SystemKind.current
// Determine the current system kind in batches.
when (kind) {
    SystemKind.MIUI -> {
        // Execute relevant code.
    }
    SystemKind.COLOROS -> {
        // Execute relevant code.
    }
    SystemKind.ORIGINOS -> {
        // Execute relevant code.
    }
}

The following is a comparison table of constants for currently collected system kinds, if you have features for more system kinds, you are welcome to PR or go to GitHub Issuesopen in new window to make suggestions to us.

SystemKind NameSystem Kind
DEFAULTDefault, uncategorized. (Stock Android or AOSP-based Android system and system categories not currently collected)
HARMONYOSHarmonyOSopen in new window (Based on AOSP)
EMUIEMUIopen in new window
MIUIMIUIopen in new window
HYPEROSHyperOSopen in new window
COLOROSColorOSopen in new window
FUNTOUCHOSFuntouchOSopen in new window
ORIGINOSOriginOSopen in new window
FLYMEFlymeopen in new window
ONEUIOneUIopen in new window
ZUIZUIopen in new window
REDMAGICOSRedMagicOSopen in new window
NUBIAUINubiaUIopen in new window
ROGUIRogUIopen in new window
VISIONOSVisionOSopen in new window

SystemProperties is a tool provided by Android that can read the contents of build.prop during runtime, but this function is not open to developers.

So in order to avoid using reflection to access SystemProperties every time, BetterAndroid mirrors all methods of SystemProperties.

Now, you can directly access SystemProperties using non-reflective means.

The following example

// For example, get the build ID of the current system.
val buildId = SystemProperties.get("ro.build.id")
// Get the build type of the current system.
val buildTags = SystemProperties.get("ro.system.build.tags")
// Get the CPU ABI list supported by the current device.
val abis = SystemProperties.get("ro.system.product.cpu.abilist")

BetterAndroid also provides an extension usage for it.

The following example

// Determine whether the properties key exists.
// For example, some unique key values in ROM.
val isExists = SystemProperties.contains("ro.miui.ui.version.name")