blob: af05078b4f3ad5ecc334ae1e68fe25130e881e2b [file] [log] [blame]
/*
* 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.
*/
package com.android.packageinstaller;
import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
import android.Manifest;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserManager;
import android.util.Log;
import java.util.Arrays;
/**
* Select which activity is the first visible activity of the installation and forward the intent to
* it.
*/
public class InstallStart extends Activity {
private static final String LOG_TAG = InstallStart.class.getSimpleName();
private static final String DOWNLOADS_AUTHORITY = "downloads";
private PackageManager mPackageManager;
private UserManager mUserManager;
private boolean mAbortInstall = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPackageManager = getPackageManager();
mUserManager = getSystemService(UserManager.class);
Intent intent = getIntent();
String callingPackage = getCallingPackage();
String callingAttributionTag = null;
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
final int sessionId = (isSessionInstall
? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
: -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
callingAttributionTag =
(sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG,
callingAttributionTag);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
private boolean isUidRequestingPermission(int uid, String permission) {
final String[] packageNames = mPackageManager.getPackagesForUid(uid);
if (packageNames == null) {
return false;
}
for (final String packageName : packageNames) {
final PackageInfo packageInfo;
try {
packageInfo = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
// Ignore and try the next package
continue;
}
if (packageInfo.requestedPermissions != null
&& Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
return true;
}
}
return false;
}
/**
* @return the ApplicationInfo for the installation source (the calling package), if available
*/
private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
if (callingPackage != null) {
try {
return getPackageManager().getApplicationInfo(callingPackage, 0);
} catch (PackageManager.NameNotFoundException ex) {
// ignore
}
}
return null;
}
/**
* Get the originating uid if possible, or
* {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
*
* @param sourceInfo The source of this installation
* @return The UID of the installation source or UID_UNKNOWN
*/
private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
// The originating uid from the intent. We only trust/use this if it comes from either
// the document manager app or the downloads provider
final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
final int callingUid;
if (sourceInfo != null) {
callingUid = sourceInfo.uid;
} else {
try {
callingUid = ActivityManager.getService()
.getLaunchedFromUid(getActivityToken());
} catch (RemoteException ex) {
// Cannot reach ActivityManager. Aborting install.
Log.e(LOG_TAG, "Could not determine the launching uid.");
mAbortInstall = true;
return PackageInstaller.SessionParams.UID_UNKNOWN;
}
}
if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
== PackageManager.PERMISSION_GRANTED) {
return uidFromIntent;
}
if (isSystemDownloadsProvider(callingUid)) {
return uidFromIntent;
}
// We don't trust uid from the intent. Use the calling uid instead.
return callingUid;
}
private boolean isSystemDownloadsProvider(int uid) {
final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
DOWNLOADS_AUTHORITY, 0);
if (downloadProviderPackage == null) {
// There seems to be no currently enabled downloads provider on the system.
return false;
}
final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
return (appInfo.isSystemApp() && uid == appInfo.uid);
}
}