How to Install/Update/Remove APK Using "Packageinstaller" Class in Android L

Updating my app remotely without Google Play access

I followed recommendation from @keag and it worked.

1.
With no "root" on the device, I made the app "device-owner"
For this I added a device admin receiver class. SampleAdminReceiver.class:

import android.app.admin.DeviceAdminReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class SampleAdminReceiver extends DeviceAdminReceiver {
void showToast(Context context, CharSequence msg) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onEnabled(Context context, Intent intent) {
showToast(context, "Device admin enabled");
}
@Override
public void onDisabled(Context context, Intent intent) {
showToast(context, "Device admin disabled");
}
}

added receiver to the manifest:

    <receiver
android:name=".SampleAdminReceiver"
android:description="@string/app_name"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN" >
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/device_admin_receiver" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>

Then using the adb interface I run the following dpm command:

$ dpm set-device-owner com.sample.app/.SampleAdminReceiver

Added following permission to manifest :

<uses-permission android:name="android.permission.INSTALL_PACKAGES" />

The with the following function I am able to install the apk from URL:

public static boolean installPackageX(final Context context, final String url)
throws IOException {
//Use an async task to run the install package method
AsyncTask<Void,Void,Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
try {
PackageInstaller packageInstaller = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
packageInstaller = context.getPackageManager().getPackageInstaller();
}
PackageInstaller.SessionParams params = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
}

// set params
int sessionId = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sessionId = packageInstaller.createSession(params);
}
PackageInstaller.Session session = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session = packageInstaller.openSession(sessionId);
}
OutputStream out = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
out = session.openWrite("COSU", 0, -1);
}
//get the input stream from the url
HttpURLConnection apkConn = (HttpURLConnection) new URL(url).openConnection();
InputStream in = apkConn.getInputStream();
byte[] buffer = new byte[65536];
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session.fsync(out);
}
in.close();
out.close();
//you can replace this intent with whatever intent you want to be run when the applicaiton is finished installing
//I assume you have an activity called InstallComplete
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("info", "somedata"); // for extra data if needed..
Random generator = new Random();
PendingIntent i = PendingIntent.getActivity(context, generator.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session.commit(i.getIntentSender());
}
} catch (Exception ex){
ex.printStackTrace();
Log.e("AppStore","Error when installing application. Error is " + ex.getMessage());
}

return null;
}
};
task.execute(null,null);
return true;
}

After that, it is just a matter of automating the process.

Btw, following code in the app is useful for removing "device owner" property.

    DevicePolicyManager dpm = (DevicePolicyManager) getApplicationContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.clearDeviceOwnerApp(getApplicationContext().getPackageName());

Android install apk with Intent.VIEW_ACTION not working with File provider

After a lot of trying I have been able to solve this by creating different Intents for anything lower than Nougat as using the FileProvider to create an install intent with Android Versions before Nougat causes the error:

ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.INSTALL_PACKAGE dat=content://XXX.apk flg=0x1 }

While using a normal Uri on Android Nougat creates the following error:

FileUriExposedException: file:///XXX.apk exposed beyond app through Intent.getData()

My solution which is working for me with Android N on the emulator and a phone running Android M.

File toInstall = new File(appDirectory, appName + ".apk");
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", toInstall);
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
Uri apkUri = Uri.fromFile(toInstall);
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
activity.startActivity(intent);

UPDATE FOR Android Nougat 7.1:

You also need to add the permission REQUEST_INSTALL_PACKAGES in your manifest. Its available from Api Level 23 (Android 6.0 Marshmallow) and required from Level 25 (Android 7.1 Nougat).

UPDATE:

Remember to request the permissions for read and write to external storage if the file you try to install is on the external storage. And also to set up a correct FileProvider for Android Nougat and above.

First check if you have write permission by calling canReadWriteExternal() below, if not call requestPermission() before:

private static final int REQUEST_WRITE_PERMISSION = 786;

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_WRITE_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission granted", Toast.LENGTH_LONG).show();
}

private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
}

private boolean canReadWriteExternal() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}

Here is an example of a file provider for the Download folder on the external storage. AndroidManifest.xml:

<application ... >
...

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>

resources/xml/filepaths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_download" path="Download"/>
</paths>

If you get an error while installing the .apk saying something like "There is a problem parsing the package." it could be that you haven't asked for the read/write permission or the file you try to install doesn't exist or is corrupt.

UPDATE FOR Android Oreo 8.0:

You have to check if current application is allowed to install the APK on Android Oreo 8.0 or above.

You can check if your app is allowed to install APK by using canRequestPackageInstalls method of PackageManager class. If it returns false, then you can launch intent with ACTION_MANAGE_UNKNOWN_APP_SOURCES action to launch settings dialog where user can allow the app to install APKs.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 
&& !getPackageManager().canRequestPackageInstalls()) {
Intent unknownAppSourceIntent = new Intent()
.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
.setData(Uri.parse(String.format("package:%s", getPackageName())));

unknownAppSourceDialog.launch(unknownAppSourceIntent);
} else {
// App already have the permission to install so launch the APK installation.
startActivity(intent);
}

Make sure you add the following code to your activity to receive the result of intent.

ActivityResultLauncher<Intent> unknownAppSourceDialog = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// User has allowed app to install APKs
// so we can now launch APK installation.
startActivity(intent);
}
});


Related Topics



Leave a reply



Submit