robolectric icon indicating copy to clipboard operation
robolectric copied to clipboard

OutputStream still open Exception in ShadowPackageInstaller when using try-with-resources

Open vhamon opened this issue 1 year ago • 0 comments

Description

It raises an inappropriate SecurityException when using try-with-resources with a ShadowPackageInstallerSession. It happens because it expects that the close() is done before the session.commit(), but when we call session.commit() in the try-with-resources, the close() is not yet called (it's too early). A workaround is possible by just calling session.close() in in the try-with-resources before the session.commit() This workaround raises lint issues because using try-with-resources is a recommended way with SonarLint (https://rules.sonarsource.com/java/RSPEC-2093/).

The implied code in ShadowPackageInstaller.java :

      if (outputStreamOpen) {
        throw new SecurityException("OutputStream still open");
      }

https://github.com/robolectric/robolectric/blob/0643c19fe8a32a4569b01a1950cf21613cb28497/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageInstaller.java#L265C64-L265C64

Steps to Reproduce

  1. Use try-with-resources with a PackageInstallerSession and an OutputStream (see code example below) :
    public static final String ACTION_INSTALL_COMMIT =
            "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
    private static final int PACKAGE_WRITE_BUFFER_SIZE = 65536;

    mPackageManager = RuntimeEnvironment.getApplication().getPackageManager();

    public void installPackage(File packageFile, String packageName) throws IOException {
        final PackageInstaller packageInstaller = mPackageManager.getPackageInstaller();
        final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);

        final int sessionId = packageInstaller.createSession(params);
        try (final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
                final OutputStream out = session.openWrite(mContext.getPackageName(), 0, -1);
                        final InputStream in = new FileInputStream(packageFile)) {
            byte[] buffer = new byte[PACKAGE_WRITE_BUFFER_SIZE];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            session.fsync(out);

            final Intent intent = new Intent(ACTION_INSTALL_COMMIT);
            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    mContext, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender());
        }
    }

Workaround : add out.close(); right after session.fsync(out);

Robolectric & Android Version

Robolectric 4.10.3 minSdkVersion 23 targetSdkVersion 29

vhamon avatar Sep 20 '23 15:09 vhamon