Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc975c2d authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Support app install from a content URI" into nyc-dev

parents c5d544ef bd6ad3a2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="content" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter>
@@ -47,6 +48,7 @@
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
                <data android:scheme="content" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
+28 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
    Copyright (C) 2016 The Android Open Source Project

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">

    <path
        android:fillColor="#000000"
        android:pathData="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
    <path
        android:pathData="M0 0h24v24H0z" />
</vector>
+6 −0
Original line number Diff line number Diff line
@@ -293,4 +293,10 @@
    <!-- Title for the category listing the current permissions used by an app. -->
    <string name="current_permissions_category">Current permissions</string>

    <!-- Message that the app to be installed is being staged -->
    <string name="message_staging">Staging app&#8230;</string>

    <!-- Placeholder for an app name when it is unknown -->
    <string name="app_name_unknown">Unknown</string>

</resources>
+203 −67
Original line number Diff line number Diff line
@@ -33,7 +33,9 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
import android.content.pm.VerificationParams;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.UserManager;
@@ -46,10 +48,16 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AppSecurityPermissions;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;
import com.android.packageinstaller.util.Utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/*
 * This activity is launched when a new application is installed via side loading
@@ -65,12 +73,20 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
    private static final String TAG = "PackageInstaller";

    private static final int REQUEST_ENABLE_UNKNOWN_SOURCES = 1;
    private static final int REQUEST_INSTALL_PACKAGE = 2;

    private static final String SCHEME_FILE = "file";
    private static final String SCHEME_CONTENT = "content";
    private static final String SCHEME_PACKAGE = "package";

    private int mSessionId = -1;
    private Uri mPackageURI;
    private Uri mOriginatingURI;
    private Uri mReferrerURI;
    private int mOriginatingUid = VerificationParams.NO_UID;
    private File mContentUriApkStagingFile;

    private AsyncTask<Uri, Void, File> mStagingAsynTask;

    private boolean localLOGV = false;
    PackageManager mPm;
@@ -108,6 +124,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
    private void startInstallConfirm() {
        TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
        tabHost.setup();
        tabHost.setVisibility(View.VISIBLE);
        ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
        TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
        // If the app supports runtime permissions the new permissions will
@@ -183,10 +200,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
            ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
        }
        mInstallConfirm.setVisibility(View.VISIBLE);
        mOk = (Button)findViewById(R.id.ok_button);
        mCancel = (Button)findViewById(R.id.cancel_button);
        mOk.setOnClickListener(this);
        mCancel.setOnClickListener(this);
        mOk.setEnabled(true);
        if (mScrollView == null) {
            // There is nothing to scroll view, so the ok button is immediately
            // set to install.
@@ -320,7 +334,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            setResult(RESULT_OK);
                            finish();
                            clearCachedApkIfNeededAndFinish();
                        }
                    })
                    .setOnCancelListener(this)
@@ -341,9 +355,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
        // implement a "allow untrusted source once" feature.
        if (request == REQUEST_ENABLE_UNKNOWN_SOURCES && result == RESULT_OK) {
            initiateInstall();
        } else {
            finish();
        }
        clearCachedApkIfNeededAndFinish();
    }

    private boolean isInstallRequestFromUnknownSource(Intent intent) {
@@ -430,6 +443,10 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        final Intent intent = getIntent();
        mOriginatingUid = getOriginatingUid(intent);

        final Uri packageUri;

        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
@@ -440,41 +457,78 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
            }

            mSessionId = sessionId;
            mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            mPackageURI = intent.getData();
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
        final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();

        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            return;
        }

        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            Log.w(TAG, "Unsupported scheme " + scheme);
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            finish();
        //set view
        setContentView(R.layout.install_start);
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        mOk = (Button)findViewById(R.id.ok_button);
        mCancel = (Button)findViewById(R.id.cancel_button);
        mOk.setOnClickListener(this);
        mCancel.setOnClickListener(this);

        // Block the install attempt on the Unknown Sources setting if necessary.
        final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        if (!requestFromUnknownSource) {
            initiateInstall();
            return;
        }

        // If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (!isUnknownSourcesAllowedByAdmin()) {
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            clearCachedApkIfNeededAndFinish();
        } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
        } else if (!isUnknownSourcesEnabled()) {
            // Ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_SOURCES);
        }

        processPackageUri(packageUri);
    }

    @Override
    protected void onDestroy() {
        if (mStagingAsynTask != null) {
            mStagingAsynTask.cancel(true);
            mStagingAsynTask = null;
        }
        super.onDestroy();
    }

    private void processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;

        final String scheme = packageUri.getScheme();
        final PackageUtil.AppSnippet as;
        if ("package".equals(mPackageURI.getScheme())) {

        switch (scheme) {
            case SCHEME_PACKAGE: {
                try {
                mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
                    mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                            PackageManager.GET_PERMISSIONS
                                    | PackageManager.GET_UNINSTALLED_PACKAGES);
                } catch (NameNotFoundException e) {
                }
                if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + mPackageURI.getScheme()
                    Log.w(TAG, "Requested package " + packageUri.getScheme()
                            + " not available. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
@@ -482,8 +536,10 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
                }
                as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } else {
            final File sourceFile = new File(mPackageURI.getPath());
            } break;

            case SCHEME_FILE: {
                File sourceFile = new File(packageUri.getPath());
                PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

                // Check for parse errors
@@ -497,38 +553,26 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
                        PackageManager.GET_PERMISSIONS, 0, 0, null,
                        new PackageUserState());
                as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        }

        //set view
        setContentView(R.layout.install_start);
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
            } break;

        mOriginatingUid = getOriginatingUid(intent);
            case SCHEME_CONTENT: {
                mStagingAsynTask = new StagingAsyncTask();
                mStagingAsynTask.execute(packageUri);
                return;
            }

        // Block the install attempt on the Unknown Sources setting if necessary.
        final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        if (!requestFromUnknownSource) {
            initiateInstall();
            default: {
                Log.w(TAG, "Unsupported scheme " + scheme);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
                clearCachedApkIfNeededAndFinish();
                return;
            }
        }

        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);

        // If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (!unknownSourcesAllowedByAdmin) {
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
        } else if (!unknownSourcesAllowedByUser && isManagedProfile) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
        } else if (!unknownSourcesAllowedByUser) {
            // Ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_SOURCES);
        } else {
        initiateInstall();
    }
    }

    /** Get the ApplicationInfo for the calling package, if available */
    private ApplicationInfo getSourceInfo() {
@@ -612,7 +656,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen

    // Generic handling when pressing back key
    public void onCancel(DialogInterface dialog) {
        finish();
        clearCachedApkIfNeededAndFinish();
    }

    public void onClick(View v) {
@@ -620,7 +664,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
            if (mOkCanInstall || mScrollView == null) {
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, true);
                    finish();
                    clearCachedApkIfNeededAndFinish();
                } else {
                    startInstall();
                }
@@ -633,7 +677,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            finish();
            clearCachedApkIfNeededAndFinish();
        }
    }

@@ -664,7 +708,99 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
            newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        }
        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
        startActivity(newIntent);
        startActivityForResult(newIntent, REQUEST_INSTALL_PACKAGE);
    }

    private void clearCachedApkIfNeededAndFinish() {
        if (mContentUriApkStagingFile != null) {
            mContentUriApkStagingFile.delete();
            mContentUriApkStagingFile = null;
        }
        finish();
    }

    private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> {
        private static final long SHOW_EMPTY_STATE_DELAY_MILLIS = 300;

        private final Runnable mEmptyStateRunnable = new Runnable() {
            @Override
            public void run() {
                ((TextView) findViewById(R.id.app_name)).setText(R.string.app_name_unknown);
                ((TextView) findViewById(R.id.install_confirm_question))
                        .setText(R.string.message_staging);
                mInstallConfirm.setVisibility(View.VISIBLE);
                findViewById(android.R.id.tabhost).setVisibility(View.INVISIBLE);
                findViewById(R.id.ok_button).setEnabled(false);
                Drawable icon = getDrawable(R.drawable.ic_file_download);
                Utils.applyTint(PackageInstallerActivity.this,
                        icon, android.R.attr.colorControlNormal);
                ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(icon);
            }
        };

        @Override
        protected void onPreExecute() {
            getWindow().getDecorView().postDelayed(mEmptyStateRunnable,
                    SHOW_EMPTY_STATE_DELAY_MILLIS);
        }

        @Override
        protected File doInBackground(Uri... params) {
            if (params == null || params.length <= 0) {
                return null;
            }
            Uri packageUri = params[0];
            File sourceFile = null;
            try {
                sourceFile = File.createTempFile("package", ".apk", getCacheDir());
                try (
                    InputStream in = getContentResolver().openInputStream(packageUri);
                    OutputStream out = (in != null) ? new FileOutputStream(
                            sourceFile) : null;
                ) {
                    // Despite the comments in ContentResolver#openInputStream
                    // the returned stream can be null.
                    if (in == null) {
                        return null;
                    }
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) >= 0) {
                        // Be nice and respond to a cancellation
                        if (isCancelled()) {
                            return null;
                        }
                        out.write(buffer, 0, bytesRead);
                    }
                }
            } catch (IOException ioe) {
                Log.w(TAG, "Error staging apk from content URI", ioe);
                if (sourceFile != null) {
                    sourceFile.delete();
                }
            }
            return sourceFile;
        }

        @Override
        protected void onPostExecute(File file) {
            getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
            if (isFinishing() || isDestroyed()) {
                return;
            }
            if (file == null) {
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return;
            }
            mContentUriApkStagingFile = file;
            Uri fileUri = Uri.fromFile(file);
            processPackageUri(fileUri);
        }

        @Override
        protected void onCancelled(File file) {
            getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
        }
    };
}
+1 −1
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import android.util.Log;
import android.util.SparseArray;

import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.packageinstaller.util.Utils;

import java.util.ArrayList;
import java.util.Collections;
Loading