Article Directory
background
Developing applications based on a custom tablet system can obtain the support of the Android source code layer.
Android8.1 system.
Target
- By default, all required permissions are obtained, and no pop-up window is asked for authorization.
- A silent upgrade installs a new version.
accomplish
Obtain system application permissions
Through sharedUserId
this configuration, the current application process is configured as a system application process, and all permissions can be obtained.
principle
Through Shared User id, multiple APKs with the same UserId can be configured to run in the same process, so by default, they can access any data from each other, and at the same time, they can access databases and files in the data directory of other APKs, just like accessing this program data is the same.
In order to prevent other applications from obtaining the sharedUserId and abusing it, the Android system restricts that the same userId must use the same signature to be installed on the same device. Therefore, if you want to set it as a system shared process, it must be consistent with the system application signature.
The logic for the system to sign the application:
build/target/product/security
There are four sets of default signatures in the directory of the system source code Android.mk
for use in compiling the APK, which LOCAL_CERTIFICATE
are implemented by specifying fields, which are:
testkey
: Normal APK, used by default.platform
: The APK completes some core functions of the system. After the access test to the folders existing in the system, the UID of the process where the APK compiled in this way is system.shared
: The APK needs to share data with the home/contacts process.media
: The APK is a part of the media/download system
It is used by ordinary applications testkey
. If it is a system application, it can be specified as platform
.
The principle and introduction refer to this article : Android's permission mechanism - "sandbox" mechanism sharedUserId and signature
accomplish
Refer to this article to implement : Android apk system signature
1. Prerequisite:
The application signature cannot be a custom signature, but must be a system signature. The signature file needs to be provided by the system provider, including several files such as platform.pk8
, platform.x509.pem
, signapk.jar
and so on. If this step cannot be provided, it sharedUserId
is hopeless through configuration.
2. ConfiguresharedUserId
the node increase
in :AndroidManifest.xml
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.moore.systemapk"
android:sharedUserId="android.uid.system">
</manifest>
3. Signature
Method 1:
In the system source code layer, configure the application as Android.mk
the system application type among the four default types in the corresponding principleLOCAL_CERTIFICATE
platform
Method 2:
Build the unsigned apk package, and sign the application through the command method.
java -jar signapk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apk
Replace your apk name and path unsigned.apk
with signed.apk
the output path.
Method 3:
According to the signature file provided by the system, generate a jks or keystone file, which can be directly packaged and signed in the AS without additional signature.
- generate
shared.priv.pem
file
openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt
- generate
shared.pk12
file
openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name moore
- generate
jks
orkeystone
file
keytool -importkeystore -deststorepass android -destkeypass android -destkeystore moore.jks -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias moore
After generation, it can be configured in gradle, or choose the path where the signature is located when packaging
4. Problems
In the actual operation process, if you use the command method to sign, there is a high probability no conscrypt_openjdk_jni in java.library.path:
of errors. Get the one provided by the manufacturer libconscrypt_openjdk_jni.so
, re-sign, and report another error: FATAL ERROR in native method: RegisterNatives failed for 'org/conscrypt/NativeCrypto'
, try to change the computer or system (windows, mac, linux) There is no solution. It is normal for the computer on the manufacturer's side to execute the command, so I gave up and executed it by generating a jks signature.
5. Installation
Since the signature has been changed, if the app is installed on the device where the app was originally installed, it must be uninstalled in advance. The same package name has a different signature, and the installation is obviously unsuccessful.
silent installation
The application realizes the automatic update function, and there are several installation methods:
- Call the system installer and pass in the apk path (authorization is required to allow the installation of applications)
- Execute the shell command to silently install apk (requires root authority)
- Use
PackageInstaller
code to install, simulate the system installation process (need to be a system application)
Normal upgrade installation
First of all, the installation page will appear in this way, which does not meet our needs, but some problems were found during the installation process, so let’s list them for further reference.
1. Method
After downloading the upgrade package apk, directly use Intent to invoke the system installation intent. It should be noted that customization is required for versions above 8.0 FileProvider
, otherwise the path cannot be obtained and an exception will be thrown FileUriExposedException
.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//自定义的FileProvider,需要在清单文件中声明
String authority = XUpdate.getContext().getPackageName() + ".updateFileProvider";
fileUri = FileProvider.getUriForFile(XUpdate.getContext(), authority, file);
} else {
fileUri = Uri.fromFile(file);
}
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
context.startActivity(intent)
2. Problem
Here comes the problem, according to the normal application process, this method is no problem, everything is normal, but our application has been set ** android:sharedUserId="android.uid.system"
**, it is a bit troublesome.
When starting the installation, an error is reported: There was a problem parsing the package
I have confirmed that there is no problem with the downloaded installation package. The installation can be initiated through file management and other applications, and it can be installed normally after removing the systemUid. The problem is the sharedUserId.
Reason:
By tracking the source program of the system installation process, the following code was found:
final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
// Exempted authority for cropping user photos in Settings app
} else {
Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
+ " grant to " + grantUri + "; use startActivityAsCaller() instead");
return -1;
}
}
ok, to solve the case, because we are an application set to SystemUid, but our provider is not com.android.settings.files
, so it is directly blocked return -1
, and com.android.settings.files
this provider is defined in the Settings system application, that is to say, only the SystemUid application of Settings can Invokes the normal installation process.
solution:
- We can't change our own to the one
android:authorities
available abovecom.android.settings.files
, because this is the only value in the entire system, and it cannot be installed on the device if it is changed to the same value. pawn. - Remove systemUid, it can be installed normally, if you can remove it, you won't bother to study it. pawn.
- Modify the source code! In the above judgment, add an or condition, add our own
authorities
, but if you want to modify the source code, you can use it conditionally
For detailed analysis of this problem, please refer to the article : The application call of Android android.uid.system fails to install apk
shell command installation
As the name suggests, it is installed directly by entering commands to execute. We all know that the Android kernel is linux, so the principle is to use the root account to directly command the installation.
You need to determine whether you have root privileges first:
public static boolean isDeviceRooted() {
String su = "su";
String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/",
"/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/"};
for (String location : locations) {
if (new File(location + su).exists()) {
return true;
}
}
return false;
}
If you have permission, the splicing command filePath
is the apk path
String command = "pm install -i " + packageName + " --user 0 " + filePath;
Then execute the command through the method of ShellUtils .execCommand
If you have Root permission, this method is feasible and there will be no interface, but the devices we release to the outside world will not open root permission, so this method can only be used during development and testing, and it cannot be operated online.
Simulate System Installer Flow
Before the introduction, you can refer to these two articles to understand the system installation application process and code logic
Android package management mechanism (1) PackageInstaller initialization
Android package management mechanism (2) PackageInstaller install APK
What we have to do is to analyze and call up the system installation page, what operations the logic layer does after clicking install, and then implement this process directly in our application.
general flow
- Define a listener callback to receive notifications of installation start, progress, and end
- Get the object from
PackageManager
GetPackageInstaller
, get the apk file File object - Create an installation session
Session
and get the session id - Read the apk File and write the file as a stream
session
- Create one
PendingIntent
to invoke the PMS installation logic - Commit this session and enter the PMS installation
Introduction to PMS installation:
- After receiving the session commit, it will be assembled into an
PackageInstallObserverAdapter
object,Handler
which will be processed by PMS by sending the messagePackageManagerService
The install logicPMSHandler
isIMediaContainerService
related to several classesPMSHandler
Process the logic of binding messages, new installation messages, etc. Only when the binding is successful can the next installation operation be performed- Bind the service through the binder, parse and copy the apk (time-consuming operation, open a new service process in the Android system)
InstallParams
AndInstallArgs
specific operations such as pre-installation inspection, installation location confirmation, copy and move, etc.- After the installation is successful, a message is distributed through the PMS to notify the user of the application, update the Settings, and prepare data for the newly installed application. If the installation fails, the apk will be deleted.
- disconnect binder
Reference article: : Android package management mechanism (3) PMS handles APK installation
key code
int count;
int sessionId;
byte[] buffer = new byte[65536];
InputStream inputStream;
OutputStream outputStream;
//拿到apkFile,拿到packageInstaller对象
File apkFile = new File(apkFilePath);
long apkFileLength = apkFile.length();
PackageManager pm = context.getPackageManager();
PackageInstaller packageInstaller = pm.getPackageInstaller();
packageInstaller.registerSessionCallback(忽略回调实现);
//创建会话
PackageInstaller.Session session = null;
PackageInstaller.SessionParams sessionParams;
try {
sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionId = packageInstaller.createSession(sessionParams);
session = packageInstaller.openSession(sessionId);
inputStream = new FileInputStream(apkFile);
outputStream = session.openWrite(apkName, 0, apkFileLength);
//写入
while ((count = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, count);
float progress = ((float) count / (float) apkFileLength);
session.setStagingProgress(progress);
}
session.fsync(outputStream);
inputStream.close();
outputStream.flush();
outputStream.close();
//创建PendingIntent
Intent intent = new Intent();
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
//绑定并提交
session.commit(pendingIntent.getIntentSender());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (session != null) {
session.abandon();
}
}
At this point, the silent installation can be realized, but there is still a problem. Normally, our system upgrade needs to restart the application after the upgrade is completed. However, because our application is killed during the installation process, Therefore, the callback set when creating the session cannot receive the callback for the completion of the installation! !
The installation is complete and restart
Idea:
At the beginning of the installation, create an AlarmManager task, and reopen the application after 10s (depending on the size of the apk).
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
PendingIntent restartIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
mgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, restartIntent);
}
In this way, a silent installation can be achieved, and the application will be restarted after the installation is complete.