Android PackageInstaller, re-open the app after it updates itself
Asked Answered
D

1

22

I'm developing an app that runs as Device Owner, and I want to build an automatic updater inside it.

To do it I use the PackageInstaller, as I have the privileges to use it due to my Device owner position.

private void installPackage(InputStream inputStream)
        throws IOException {
    notifyLog("Inizio aggiornamento...");
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
            .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    session.commit(createIntentSender(sessionId));
}

private IntentSender createIntentSender(int sessionId) {
    PendingIntent pendingIntent = PendingIntent.getBroadcast(
            context,
            sessionId,
            new Intent(LauncherReceiver.START_INTENT),
            0);
    return pendingIntent.getIntentSender();
}

The update goes right, but the problem is that it doesn't re-open the app itself after the update, even if I set an IntentSender to broadcast the action LauncherReceiver.START_INTENT to the new app instance (that will bring it on to start).

Here it is my Receiver:

public class LauncherReceiver extends BroadcastReceiver {

    public static final String START_INTENT = "com.aaa.aaa.action.START";

    @Override
    public void onReceive(Context context, Intent intent) {
        System.out.println("CALL RECEIVER!!");
        Intent startIntent = new Intent(context, StartActivity.class);
        startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
        context.startActivity(startIntent);
    }
}

And it is registered in my manifest:

    <receiver android:name=".receivers.LauncherReceiver" android:enabled="true" android:exported="true">
        <intent-filter>
            <action android:name="com.aaa.aaa.action.START"></action>
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>

If I call it by CLI it works:

am broadcast -a com.package.appname.action.START 

So the receiver works but for some reason it doesn't work from the Package Installer session commit. The app close itself due to the upgrade but does't open again.

If I change the createIntentSender to this:

private IntentSender createIntentSender(int sessionId) {
    Intent intent = new Intent(Intent.ACTION_DIAL);
    intent.setData(Uri.parse("tel:0123456789"));
    PendingIntent pendingIntent = PendingIntent.getActivity(context,sessionId,intent,0);
    return pendingIntent.getIntentSender();
}

it actually opens the phone service. So I think the problem reside in the upgrade lifecycle, as the app is not ready when the broadcast action is spawned.

Moreover, I made another try and I created a side app that does nothing but call the broadcast action to my main app, so I can call this side app and with this "double step" it can actually re-open the just updated app. The problem is that I have to install two apps =/

Can anybody help me? Is there a way to re-open the just updated app?

Dissemble answered 12/5, 2017 at 15:7 Comment(14)
What does it mean "runs as Device Owner" ?Extensity
A device owner app is an app that implements a Broadcast Receiver of class DeviceAdminReceiver, installed on the device and on it has been run the dpm command by shell or by NFC that made the app has some special privileges on the devices. Make some google search for "android device owner" and check thisCoroneted
So this is only for built in apps, and for apps that got this permission using adb (connected to PC) ? What would it grant the app in this case? I mean, I've succeeded installing an APK (yet with user confirmation) using a very similar code, as such: issuetracker.google.com/issues/63965110#comment8Extensity
Yes, but the Device Owner mode grants you many privileges, including to install apps without user confirmation. The app doesn't need to be built-in, you can just develop your own app and the set it in the device owner mode on the device you need. Of course you need to physically access the device by USB or at least NFC, and it is not supposed for Play Store Distribution. (You can read about Android Entrerprise (advanced plan) for more option about special privileges without Device Owner).Coroneted
Does it also include uninstall without user confirmation? Is it ok to publish such an app on the Play Store? Or do I have to have Google allow it? Do you know of a sample project that shows both of those features (of background install and uninstall) ?Extensity
Yes, it also allows uninstall w/out user confirmation. And yes, you can publish it on the play store as long as the Device Owner Mode is not a key feature and the app can run normally even without it. It seems to me that you also need to explain to Google why you need the DeviceOwner Mode on the app during the approval procedure. I can't find an example here in a minute but there're many foundable by Google (search for result from just last year). Good Luck!Coroneted
If you found this helpful please make a tiny upvote on my question or on a comment! ThxCoroneted
There is this one: github.com/googlesamples/android-testdpc , but I've failed to use it, and I don't think it has app-related operations examples there, and I'm not sure if it has to use NFC or adb command to work. Where and how do I " explain to Google" ? Which other app-related operations do you know of, that this feature allows? Disable&enable apps? clear-data? Force-stop? Backup/restore data? Get app size?Extensity
I found this codelab example very helpful. It's meant to create a COSU app but it also include the Device Owner setup instructions.Coroneted
Here is a list of methods that you will able to use with Device Ownerr.Coroneted
Interesting. Thank you. Hope to try it soon. I ask all this because I think it might be useful for my app, which has background install/uninstall only for rooted devices: play.google.com/store/apps/…Extensity
Were you able to do this silent update in the app itself? Or did you have to make a separate service or something?Arpent
I ask because this question never got a definitive answer: #51503758Arpent
What's the point on having that const public static final String START_INTENT = "com.aaa.aaa.action.START"; if you don't use it and you have the same string hardcoded in your manifest?Sanhedrin
M
13

Starting from about Android 5 (I don't know the exact API level), Android will send a broadcast Intent with ACTION="android.intent.action.MY_PACKAGE_REPLACED" after your app is updated. Just add

        <intent-filter>
            <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
        </intent-filter>

to your <receiver> declaration and you should be able to listen for this and restart your app in onReceive().

Moravia answered 15/5, 2017 at 15:32 Comment(5)
Thanks, this works, my only note is that there may be software installed on the phone that block this feature that we have to deal with see hereCoroneted
Just tried this on android P and it worked without any problems.Emlin
How should my PendingIntent look like that I pass to the session.commit() ? Regarding API level this method should work for me but I couldn't get it to work.. The application closes and nothing happens after the installation is done. #63051350Phloem
That block <intent-filter> must be add to manifest file or is any kind of header for the Activity?Sanhedrin
@Sanhedrin As I said in the answer, the <intent-filter> needs to be declared inside the <receiver> declaration. This is a BroadcastReceiver, not an Activity.Moravia

© 2022 - 2024 — McMap. All rights reserved.