Skip to content

Commit

Permalink
[Install] Incorporate the latest features
Browse files Browse the repository at this point in the history
- Properly handle the originating URI extra supplied by third-party apps via
  Intent.EXTRA_ORIGINATING_URI
- Set originating package in Android 7 and later. The originating package is
  automatically determined from the Intent sent from the third-party apps.
  Proper security measures have been taken to ensure no spoofing is done.
- In Android 13 onwards, package source is set to PACKAGE_SOURCE_OTHER by
  default to prevent the system from applying various accessibility restrictions
  to the app. However, PACKAGE_SOURCE_STORE is set by default if the originating
  package is one of the supported app stores. At present, the supported app
  stores are: Aurora Store, Droid-ify, F-Droid, F-Droid Basic, F-Droid Classic
  and Neo Store. It is up to the developers to ensure that they send the APK
  installation requests in a proper way, that is, by utilizing features such as
  `startActivityForResult` whenever possible.
- In Android 12 onwards, if an app is being installed in the foreground, App
  Manager will try to accelerate the installation process by delaying various
  post-installation tasks carried out by the installer.

Signed-off-by: Muntashir Al-Islam <[email protected]>
  • Loading branch information
MuntashirAkon committed Jul 12, 2024
1 parent be2b2c9 commit 5d39b8a
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 23 deletions.
7 changes: 6 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<uses-permission
android:name="android.permission.DUMP"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
<uses-permission
android:name="android.permission.FORCE_STOP_PACKAGES"
tools:ignore="ProtectedPermissions" />
Expand Down Expand Up @@ -940,10 +941,14 @@
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<action android:name="android.intent.action.UNINSTALL_PACKAGE" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="content" />
<data android:scheme="file" />
<data android:scheme="package" />
<data android:mimeType="application/vnd.android.package-archive" />
<data android:host="*" />
<data android:mimeType="application/*" />
</intent-filter>
<!-- Camera -->
<intent-filter tools:ignore="AppLinkUrlError">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerActivity.EXTRA_INSTALL_EXISTING;
import static io.github.muntashirakon.AppManager.apk.installer.PackageInstallerActivity.EXTRA_PACKAGE_NAME;
import static io.github.muntashirakon.AppManager.apk.installer.SupportedAppStores.isAppStoreSupported;

import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.net.Uri;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;

Expand All @@ -23,7 +26,8 @@

public class ApkQueueItem implements Parcelable {
@NonNull
static List<ApkQueueItem> fromIntent(@NonNull Intent intent) {
static List<ApkQueueItem> fromIntent(@NonNull Intent intent,
@Nullable String originatingPackage) {
List<ApkQueueItem> apkQueueItems = new ArrayList<>();
boolean installExisting = intent.getBooleanExtra(EXTRA_INSTALL_EXISTING, false);
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
Expand All @@ -35,8 +39,12 @@ static List<ApkQueueItem> fromIntent(@NonNull Intent intent) {
return apkQueueItems;
}
String mimeType = intent.getType();
Uri originatingUri = IntentCompat.getParcelableExtra(intent, Intent.EXTRA_ORIGINATING_URI, Uri.class);
for (Uri uri : uris) {
apkQueueItems.add(new ApkQueueItem(ApkSource.getCachedApkSource(uri, mimeType)));
ApkQueueItem item = new ApkQueueItem(ApkSource.getCachedApkSource(uri, mimeType));
item.mOriginatingUri = originatingUri;
item.mOriginatingPackage = originatingPackage;
apkQueueItems.add(item);
}
return apkQueueItems;
}
Expand All @@ -50,7 +58,11 @@ public static ApkQueueItem fromApkSource(@NonNull ApkSource apkSource) {
private String mPackageName;
@Nullable
private String mAppLabel;
private boolean mInstallExisting;
private final boolean mInstallExisting;
@Nullable
private String mOriginatingPackage;
@Nullable
private Uri mOriginatingUri;
@Nullable
private ApkSource mApkSource;
@Nullable
Expand All @@ -66,12 +78,15 @@ private ApkQueueItem(@NonNull String packageName, boolean installExisting) {

private ApkQueueItem(@NonNull ApkSource apkSource) {
mApkSource = Objects.requireNonNull(apkSource);
mInstallExisting = false;
}

protected ApkQueueItem(@NonNull Parcel in) {
mPackageName = in.readString();
mAppLabel = in.readString();
mInstallExisting = in.readByte() != 0;
mOriginatingPackage = in.readString();
mOriginatingUri = ParcelCompat.readParcelable(in, Uri.class.getClassLoader(), Uri.class);
mApkSource = ParcelCompat.readParcelable(in, ApkSource.class.getClassLoader(), ApkSource.class);
mInstallerOptions = ParcelCompat.readParcelable(in, InstallerOptions.class.getClassLoader(), InstallerOptions.class);
mSelectedSplits = new ArrayList<>();
Expand All @@ -87,10 +102,6 @@ public void setPackageName(@Nullable String packageName) {
mPackageName = packageName;
}

public void setInstallExisting(boolean installExisting) {
mInstallExisting = installExisting;
}

public boolean isInstallExisting() {
return mInstallExisting;
}
Expand All @@ -109,7 +120,17 @@ public InstallerOptions getInstallerOptions() {
return mInstallerOptions;
}

public void setInstallerOptions(InstallerOptions installerOptions) {
public void setInstallerOptions(@Nullable InstallerOptions installerOptions) {
if (installerOptions != null) {
installerOptions.setOriginatingPackage(mOriginatingPackage);
installerOptions.setOriginatingUri(mOriginatingUri);
// Set package source to PACKAGE_SOURCE_STORE if it's supported
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& mOriginatingPackage != null
&& isAppStoreSupported(mOriginatingPackage)) {
installerOptions.setPackageSource(PackageInstaller.PACKAGE_SOURCE_STORE);
}
}
mInstallerOptions = installerOptions;
}

Expand Down Expand Up @@ -141,6 +162,8 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mPackageName);
dest.writeString(mAppLabel);
dest.writeByte((byte) (mInstallExisting ? 1 : 0));
dest.writeString(mOriginatingPackage);
dest.writeParcelable(mOriginatingUri, flags);
dest.writeParcelable(mApkSource, flags);
dest.writeParcelable(mInstallerOptions, flags);
dest.writeStringList(mSelectedSplits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,72 @@
package io.github.muntashirakon.AppManager.apk.installer;

import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandleHidden;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.ParcelCompat;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.settings.Prefs;

public class InstallerOptions implements Parcelable {
@NonNull
public static InstallerOptions getDefault() {
return new InstallerOptions();
}

@UserIdInt
private int mUserId;
private int mInstallLocation;
@Nullable
private String mInstallerName;
@Nullable
private String mOriginatingPackage;
@Nullable
private Uri mOriginatingUri;
private int mPackageSource;
private int mInstallScenario;
private boolean mRequestUpdateOwnership;
private boolean mSignApkFiles;
private boolean mForceDexOpt;
private boolean mBlockTrackers;

public InstallerOptions() {
private InstallerOptions() {
mUserId = UserHandleHidden.myUserId();
mInstallLocation = Prefs.Installer.getInstallLocation();
mInstallerName = Prefs.Installer.getInstallerPackageName();
mOriginatingPackage = Prefs.Installer.getOriginatingPackage();
mOriginatingUri = null;
mPackageSource = Prefs.Installer.getPackageSource();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// If the user is always installing apps in the background, we expect that the user does
// want to install an app quite fast.
mInstallScenario = Prefs.Installer.installInBackground()
? PackageManager.INSTALL_SCENARIO_BULK
: PackageManager.INSTALL_SCENARIO_FAST;
}
mRequestUpdateOwnership = Prefs.Installer.requestUpdateOwnership();
mSignApkFiles = Prefs.Installer.canSignApk();
mForceDexOpt = Prefs.Installer.forceDexOpt();
mBlockTrackers = Prefs.Installer.blockTrackers();
}

protected InstallerOptions(Parcel in) {
protected InstallerOptions(@NonNull Parcel in) {
mUserId = in.readInt();
mInstallLocation = in.readInt();
mInstallerName = in.readString();
mOriginatingPackage = in.readString();
mOriginatingUri = ParcelCompat.readParcelable(in, Uri.class.getClassLoader(), Uri.class);
mPackageSource = in.readInt();
mInstallScenario = in.readInt();
mRequestUpdateOwnership = in.readByte() != 0;
mSignApkFiles = in.readByte() != 0;
mForceDexOpt = in.readByte() != 0;
mBlockTrackers = in.readByte() != 0;
Expand All @@ -46,16 +78,26 @@ public void copy(@NonNull InstallerOptions options) {
mUserId = options.mUserId;
mInstallLocation = options.mInstallLocation;
mInstallerName = options.mInstallerName;
mOriginatingPackage = options.mOriginatingPackage;
mOriginatingUri = options.mOriginatingUri;
mPackageSource = options.mPackageSource;
mInstallScenario = options.mInstallScenario;
mRequestUpdateOwnership = options.mRequestUpdateOwnership;
mSignApkFiles = options.mSignApkFiles;
mForceDexOpt = options.mForceDexOpt;
mBlockTrackers = options.mBlockTrackers;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mUserId);
dest.writeInt(mInstallLocation);
dest.writeString(mInstallerName);
dest.writeString(mOriginatingPackage);
dest.writeParcelable(mOriginatingUri, flags);
dest.writeInt(mPackageSource);
dest.writeInt(mInstallScenario);
dest.writeByte((byte) (mRequestUpdateOwnership ? 1 : 0));
dest.writeByte((byte) (mSignApkFiles ? 1 : 0));
dest.writeByte((byte) (mForceDexOpt ? 1 : 0));
dest.writeByte((byte) (mBlockTrackers ? 1 : 0));
Expand All @@ -68,11 +110,12 @@ public int describeContents() {

public static final Creator<InstallerOptions> CREATOR = new Creator<InstallerOptions>() {
@Override
public InstallerOptions createFromParcel(Parcel in) {
public InstallerOptions createFromParcel(@NonNull Parcel in) {
return new InstallerOptions(in);
}

@Override
@NonNull
public InstallerOptions[] newArray(int size) {
return new InstallerOptions[size];
}
Expand Down Expand Up @@ -104,6 +147,48 @@ public void setInstallerName(@Nullable String installerName) {
mInstallerName = installerName;
}

@Nullable
public String getOriginatingPackage() {
return mOriginatingPackage;
}

public void setOriginatingPackage(@Nullable String originatingPackage) {
mOriginatingPackage = originatingPackage;
}

@Nullable
public Uri getOriginatingUri() {
return mOriginatingUri;
}

public void setOriginatingUri(@Nullable Uri originatingUri) {
mOriginatingUri = originatingUri;
}

public int getPackageSource() {
return mPackageSource;
}

public void setPackageSource(int packageSource) {
mPackageSource = packageSource;
}

public int getInstallScenario() {
return mInstallScenario;
}

public void setInstallScenario(int installScenario) {
mInstallScenario = installScenario;
}

public boolean requestUpdateOwnership() {
return mRequestUpdateOwnership;
}

public void requestUpdateOwnership(boolean update) {
mRequestUpdateOwnership = update;
}

public boolean isSignApkFiles() {
return mSignApkFiles;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public static Intent getLaunchableInstance(@NonNull Context context, @NonNull St
goToNext();
}
};
private final InstallerOptions mInstallerOptions = new InstallerOptions();
private final InstallerOptions mInstallerOptions = InstallerOptions.getDefault();
private final Queue<ApkQueueItem> mApkQueue = new LinkedList<>();
private final ActivityResultLauncher<Intent> mConfirmIntentLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
Expand Down Expand Up @@ -203,7 +203,7 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
throw new RuntimeException("Unable to bind PackageInstallerService");
}
synchronized (mApkQueue) {
mApkQueue.addAll(ApkQueueItem.fromIntent(intent));
mApkQueue.addAll(ApkQueueItem.fromIntent(intent, Utils.getRealReferrer(this)));
}
ApkSource apkSource = IntentCompat.getParcelableExtra(intent, EXTRA_APK_FILE_LINK, ApkSource.class);
if (apkSource != null) {
Expand Down Expand Up @@ -381,6 +381,7 @@ private void launchInstallerService() {
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "New intent called: %s", intent);
setIntent(intent);
// Check for action first
if (ACTION_PACKAGE_INSTALLED.equals(intent.getAction())) {
mSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
Expand All @@ -402,7 +403,7 @@ protected void onNewIntent(Intent intent) {
}
// New APK files added
synchronized (mApkQueue) {
mApkQueue.addAll(ApkQueueItem.fromIntent(intent));
mApkQueue.addAll(ApkQueueItem.fromIntent(intent, Utils.getRealReferrer(this)));
}
UIUtils.displayShortToast(R.string.added_to_queue);
}
Expand Down
Loading

0 comments on commit 5d39b8a

Please sign in to comment.