fix: DummyActivity: implement installation progress dialog

* This fixes the apparent stalling when installing / cloning
  applications.
* Also moves apk reading to another thread.
This commit is contained in:
Peter Cai 2020-06-22 10:55:47 +08:00
parent 97f4b918b7
commit 7774395819
No known key found for this signature in database
GPG key ID: 71F5FB4E4F3FD54F
6 changed files with 133 additions and 12 deletions

View file

@ -41,7 +41,7 @@
<activity android:name=".ui.DummyActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
android:theme="@style/Theme.AppCompat.Translucent.NoTitleBar">
<intent-filter>
<action android:name="net.typeblog.shelter.action.FINALIZE_PROVISION" />
<action android:name="net.typeblog.shelter.action.START_SERVICE" />

View file

@ -3,6 +3,7 @@ package net.typeblog.shelter.ui;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Intent;
@ -15,10 +16,14 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.StrictMode;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import net.typeblog.shelter.R;
import net.typeblog.shelter.ShelterApplication;
@ -29,6 +34,7 @@ import net.typeblog.shelter.services.IFileShuttleService;
import net.typeblog.shelter.services.IFileShuttleServiceCallback;
import net.typeblog.shelter.util.AuthenticationUtility;
import net.typeblog.shelter.util.FileProviderProxy;
import net.typeblog.shelter.util.InstallationProgressListener;
import net.typeblog.shelter.util.LocalStorageManager;
import net.typeblog.shelter.util.SettingsManager;
import net.typeblog.shelter.util.Utility;
@ -314,20 +320,40 @@ public class DummyActivity extends Activity {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = pi.createSession(params);
// Show the progress dialog first
pi.registerSessionCallback(new InstallationProgressListener(this, pi, sessionId));
PackageInstaller.Session session = pi.openSession(sessionId);
doInstallPackageQ(uri, session, () -> {
// We have finished piping the streams, show the progress as 10%
session.setStagingProgress(0.1f);
InputStream is = getContentResolver().openInputStream(uri);
OutputStream os = session.openWrite(UUID.randomUUID().toString(), 0, is.available());
Utility.pipe(is, os);
session.fsync(os);
os.close();
is.close();
// Commit the session
Intent intent = new Intent(this, DummyActivity.class);
intent.setAction(PACKAGEINSTALLER_CALLBACK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
});
}
Intent intent = new Intent(this, DummyActivity.class);
intent.setAction(PACKAGEINSTALLER_CALLBACK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
// The background part of the installation process on Q (reading APKs etc)
// that must be executed on another thread
// Put them in background to avoid stalling the UI thread
private void doInstallPackageQ(Uri uri, PackageInstaller.Session session, Runnable callback) {
new Thread(() -> {
try (InputStream is = getContentResolver().openInputStream(uri);
OutputStream os = session.openWrite(UUID.randomUUID().toString(), 0, is.available())
) {
Utility.pipe(is, os);
session.fsync(os);
} catch (IOException e) {
}
runOnUiThread(callback);
}).start();
}
private void actionUninstallPackage() {

View file

@ -0,0 +1,67 @@
package net.typeblog.shelter.util;
import android.app.Activity;
import android.content.pm.PackageInstaller;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import androidx.appcompat.app.AlertDialog;
import net.typeblog.shelter.R;
public class InstallationProgressListener extends PackageInstaller.SessionCallback {
private AlertDialog mDialog;
private ProgressBar mProgress;
private int mSessionId;
private PackageInstaller mPi;
// Create a listener from an activity, and show a progress dialog for the sessionId
// Only cares about the one sessionId provided here.
// The caller is responsible for registering the callback;
// however, this class will remove itself once the session has been finished.
public InstallationProgressListener(Activity activity, PackageInstaller pi, int sessionId) {
mPi = pi;
ViewGroup layout = (ViewGroup) LayoutInflater.from(activity)
.inflate(R.layout.progress_dialog, (ViewGroup) activity.getWindow().getDecorView(), false);
mProgress = layout.findViewById(R.id.progress);
mDialog = new AlertDialog.Builder(activity)
.setCancelable(false)
.setTitle(R.string.app_installing)
.setView(layout)
.create();
mDialog.show();
}
@Override
public void onCreated(int sessionId) {
}
@Override
public void onBadgingChanged(int sessionId) {
}
@Override
public void onActiveChanged(int sessionId, boolean active) {
}
@Override
public void onProgressChanged(int sessionId, float progress) {
mProgress.setProgress((int) (progress * 100));
}
@Override
public void onFinished(int sessionId, boolean success) {
if (sessionId != mSessionId) {
return;
}
mDialog.hide();
mPi.unregisterSessionCallback(this);
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<ProgressBar
android:id="@+id/progress"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:indeterminate="false"
android:max="100"/>
</FrameLayout>

View file

@ -17,6 +17,7 @@
<string name="service_auto_freeze_title">Auto-freeze pending</string>
<string name="service_auto_freeze_desc">Shelter will auto-freeze apps launched from \"Unfreeze &amp; Launch\" on the next screen lock event.</string>
<string name="service_auto_freeze_now">Freeze Now</string>
<string name="app_installing">Installing...</string>
<!-- Main UI -->
<string name="fragment_profile_main">Main</string>

View file

@ -15,4 +15,13 @@
<item name="android:textColorPrimary">@color/colorAccent</item>
</style>
<!-- For DummyActivity -->
<style name="Theme.AppCompat.Translucent.NoTitleBar" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
</resources>