| /* |
| * 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.app.AppGlobals; |
| import android.content.ContentResolver; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageInstaller; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ProviderInfo; |
| import android.content.pm.UserInfo; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.os.UserManager; |
| import android.permission.IPermissionManager; |
| import android.util.Log; |
| |
| import java.util.List; |
| |
| /** |
| * 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 IPackageManager mIPackageManager; |
| private IPermissionManager mIPermissionManager; |
| private UserManager mUserManager; |
| private boolean mAbortInstall = false; |
| |
| @Override |
| protected void onCreate(@Nullable Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| mIPackageManager = AppGlobals.getPackageManager(); |
| mIPermissionManager = AppGlobals.getPermissionManager(); |
| mUserManager = getSystemService(UserManager.class); |
| Intent intent = getIntent(); |
| String callingPackage = getCallingPackage(); |
| |
| 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; |
| } |
| |
| 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 && !declaresAppOpPermission( |
| 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); |
| |
| // 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_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 declaresAppOpPermission(int uid, String permission) { |
| try { |
| final String[] packages = mIPermissionManager.getAppOpPermissionPackages(permission); |
| if (packages == null) { |
| return false; |
| } |
| final List<UserInfo> users = mUserManager.getUsers(); |
| for (String packageName : packages) { |
| for (UserInfo user : users) { |
| try { |
| if (uid == getPackageManager().getPackageUidAsUser(packageName, user.id)) { |
| return true; |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| // Ignore and try the next package |
| } |
| } |
| } |
| } catch (RemoteException rexc) { |
| // If remote package manager cannot be reached, install will likely fail anyway. |
| } |
| 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; |
| } |
| } |
| try { |
| if (mIPackageManager.checkUidPermission(Manifest.permission.MANAGE_DOCUMENTS, |
| callingUid) == PackageManager.PERMISSION_GRANTED) { |
| return uidFromIntent; |
| } |
| } catch (RemoteException rexc) { |
| // Ignore. Should not happen. |
| } |
| 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); |
| } |
| } |