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

Commit eb773963 authored by Ivan Chiang's avatar Ivan Chiang
Browse files

[PM] Support better transition in PIA V2 (2/N)

- Migrate installation fragments to one new InstallationFragment
  except the install restriction fragment
- Adjust the min width of the dialog to the percentage

Flag: android.content.pm.use_pia_v2
Test: atest CtsPackageInstallerCUJInstallationTestCases
Bug: 274120822
Bug: 402448072
Change-Id: I66cc9e5d51d8c5b2e1208f18b18807f254f454f8
parent 09d0c763
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -53,8 +53,8 @@
    <dimen name="dialog_inter_element_vertical_margin">12dp</dimen>
    <dimen name="alert_dialog_radius">28dp</dimen>
    <dimen name="alert_dialog_dim_amount">0.6</dimen>
    <!-- 24dp + 364dp + 24dp for the portrait mode -->
    <dimen name="alert_dialog_min_width_minor">412dp</dimen>
    <!-- 364dp for the portrait mode -->
    <dimen name="alert_dialog_min_width_minor">96.5%</dimen>
    <!-- Override the values for the buttonbar paddings in M3 MaterialAlertDialog -->
    <dimen name="m3_alert_dialog_action_top_padding">4dp</dimen>
    <dimen name="m3_alert_dialog_action_bottom_padding">20dp</dimen>
+6 −19
Original line number Diff line number Diff line
@@ -54,28 +54,14 @@ object PackageUtil {
    const val localLogv = false

    const val ARGS_ABORT_REASON: String = "abort_reason"
    const val ARGS_ACTION_REASON: String = "action_reason"
    const val ARGS_ACTIVITY_RESULT_CODE: String = "activity_result_code"
    const val ARGS_APP_DATA_SIZE: String = "app_data_size"
    const val ARGS_APP_LABEL: String = "app_label"
    const val ARGS_APP_SNIPPET: String = "app_snippet"
    const val ARGS_BUTTON_TEXT: String = "button_text"
    const val ARGS_ERROR_DIALOG_TYPE: String = "error_dialog_type"
    const val ARGS_EXISTING_OWNER: String = "existing_owner"
    const val ARGS_INSTALLER_LABEL: String = "installer_label"
    const val ARGS_INSTALLER_PACKAGE: String = "installer_pkg"
    const val ARGS_IS_ARCHIVE: String = "is_archive"
    const val ARGS_IS_CLONE_USER: String = "clone_user"
    const val ARGS_IS_UPDATING: String = "is_updating"
    const val ARGS_LEGACY_CODE: String = "legacy_code"
    const val ARGS_MESSAGE: String = "message"
    const val ARGS_NEW_OWNER: String = "new_owner"
    const val ARGS_PENDING_INTENT: String = "pending_intent"
    const val ARGS_REQUIRED_BYTES: String = "required_bytes"
    const val ARGS_RESULT_INTENT: String = "result_intent"
    const val ARGS_SHOULD_RETURN_RESULT: String = "should_return_result"
    const val ARGS_SOURCE_PKG: String = "source_pkg"
    const val ARGS_STATUS_CODE: String = "status_code"
    const val ARGS_TITLE: String = "title"
    const val ARGS_UNARCHIVAL_STATUS: String = "unarchival_status"

@@ -519,7 +505,8 @@ object PackageUtil {
    fun isMaterialDesignEnabled(context: Context): Boolean {
        return android.content.pm.Flags.usePiaV2()
                && context.resources.getBoolean(
            android.R.bool.config_enableMaterialDesignInPackageInstaller)
            android.R.bool.config_enableMaterialDesignInPackageInstaller
        )
    }

    /**
+2 −6
Original line number Diff line number Diff line
@@ -44,10 +44,8 @@ import com.android.packageinstaller.v2.model.InstallUserActionRequired
import com.android.packageinstaller.v2.model.PackageUtil
import com.android.packageinstaller.v2.model.PackageUtil.localLogv
import com.android.packageinstaller.v2.ui.fragments.DeveloperVerificationConfirmationFragment
import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
import com.android.packageinstaller.v2.ui.fragments.InstallRestrictionFragment
import com.android.packageinstaller.v2.ui.fragments.InstallationFragment
import com.android.packageinstaller.v2.ui.fragments.ParseErrorFragment
import com.android.packageinstaller.v2.viewmodel.InstallViewModel
import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory

@@ -140,8 +138,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
                    InstallAborted.ABORT_REASON_INTERNAL_ERROR,
                        -> {
                        if (aborted.errorDialogType == InstallAborted.DLG_PACKAGE_ERROR) {
                            val parseErrorDialog = ParseErrorFragment.newInstance(aborted)
                            showDialogInner(parseErrorDialog)
                            showInstallationDialog()
                        } else {
                            setResult(aborted.activityResultCode, aborted.resultIntent, true)
                        }
@@ -188,8 +185,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
                    val failureIntent = failed.resultIntent
                    setResult(RESULT_FIRST_USER, failureIntent, true)
                } else {
                    val failureDialog = InstallFailedFragment.newInstance(failed)
                    showDialogInner(failureDialog)
                    showInstallationDialog()
                }
            }

+0 −174
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.v2.ui.fragments;

import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_APP_SNIPPET;
import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_LEGACY_CODE;
import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_MESSAGE;
import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_RESULT_INTENT;
import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_SHOULD_RETURN_RESULT;
import static com.android.packageinstaller.v2.model.PackageUtil.ARGS_STATUS_CODE;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;

import com.android.packageinstaller.R;
import com.android.packageinstaller.v2.model.InstallFailed;
import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
import com.android.packageinstaller.v2.ui.InstallActionListener;
import com.android.packageinstaller.v2.ui.UiUtil;

/**
 * Dialog to show when the installation failed. Depending on the failure code, an appropriate
 * message would be shown to the user. This dialog is shown only when the caller does not want the
 * install result back.
 */
public class InstallFailedFragment extends DialogFragment {

    private static final String LOG_TAG = InstallFailedFragment.class.getSimpleName();
    private InstallFailed mDialogData;
    private InstallActionListener mInstallActionListener;

    public InstallFailedFragment() {
        // Required for DialogFragment
    }

    /**
     * Creates a new instance of this fragment with necessary data set as fragment arguments
     *
     * @param dialogData {@link InstallFailed} object containing data to display in the
     *                   dialog
     * @return an instance of the fragment
     */
    public static InstallFailedFragment newInstance(@NonNull InstallFailed dialogData) {
        Bundle args = new Bundle();
        args.putParcelable(ARGS_APP_SNIPPET, dialogData.getAppSnippet());
        args.putInt(ARGS_LEGACY_CODE, dialogData.getLegacyCode());
        args.putInt(ARGS_STATUS_CODE, dialogData.getStatusCode());
        args.putString(ARGS_MESSAGE, dialogData.getMessage());
        args.putBoolean(ARGS_SHOULD_RETURN_RESULT, dialogData.getShouldReturnResult());
        args.putParcelable(ARGS_RESULT_INTENT, dialogData.getResultIntent());

        InstallFailedFragment fragment = new InstallFailedFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        mInstallActionListener = (InstallActionListener) context;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        setDialogData(requireArguments());

        Log.i(LOG_TAG, "Creating " + LOG_TAG + "\n" + mDialogData);

        // There is no root view here. Ok to pass null view root
        @SuppressWarnings("InflateParams")
        View dialogView = getLayoutInflater().inflate(
                UiUtil.getInstallationLayoutResId(requireContext()), null);
        dialogView.requireViewById(R.id.app_snippet).setVisibility(View.VISIBLE);
        ((ImageView) dialogView.requireViewById(R.id.app_icon))
            .setImageDrawable(mDialogData.getAppIcon());
        ((TextView) dialogView.requireViewById(R.id.app_label)).setText(mDialogData.getAppLabel());

        return getDialog(mDialogData.getLegacyCode(), dialogView);
    }

    /**
     * Get the dialog with corresponding dialog title and message for the failure statusCode.
     *
     * @param statusCode The status code from the package installer.
     */
    private Dialog getDialog(int statusCode, View dialogView) {
        Log.i(LOG_TAG, "Installation status code: " + statusCode);

        final TextView customMessage = dialogView.requireViewById(R.id.custom_message);
        customMessage.setVisibility(View.VISIBLE);
        int titleResId = R.string.title_install_failed_not_installed;
        int positiveButtonResId = Resources.ID_NULL;
        DialogInterface.OnClickListener positiveButtonListener = null;
        switch (statusCode) {
            case PackageInstaller.STATUS_FAILURE_BLOCKED -> {
                customMessage.setText(R.string.message_install_failed_blocked);
                titleResId = R.string.title_install_failed_blocked;
            }
            case PackageInstaller.STATUS_FAILURE_CONFLICT -> {
                customMessage.setText(R.string.message_install_failed_conflict);
                titleResId = R.string.title_cant_install_app;
            }
            case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> {
                customMessage.setText(R.string.message_install_failed_incompatible);
                titleResId = R.string.title_install_failed_incompatible;
            }
            case PackageInstaller.STATUS_FAILURE_INVALID -> {
                customMessage.setText(R.string.message_install_failed_invalid);
                titleResId = R.string.title_cant_install_app;
            }
            case PackageInstaller.STATUS_FAILURE_STORAGE -> {
                customMessage.setText(R.string.message_install_failed_less_storage);
                titleResId = R.string.title_install_failed_less_storage;
                positiveButtonResId = R.string.button_manage_apps;
                positiveButtonListener = (dialog, which) ->
                            mInstallActionListener.sendManageAppsIntent();
            }
            default -> {
                customMessage.setVisibility(View.GONE);
            }
        }

        return UiUtil.getAlertDialog(requireContext(), getString(titleResId), dialogView,
                positiveButtonResId, R.string.button_close, positiveButtonListener,
                (dialogInt, which) -> mInstallActionListener.onNegativeResponse(
                        mDialogData.getStageCode()));
    }

    @Override
    public void onCancel(@NonNull DialogInterface dialog) {
        super.onCancel(dialog);
        mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
    }

    private void setDialogData(Bundle args) {
        AppSnippet appSnippet = args.getParcelable(ARGS_APP_SNIPPET, AppSnippet.class);
        int legacyCode = args.getInt(ARGS_LEGACY_CODE);
        int statusCode = args.getInt(ARGS_STATUS_CODE);
        String message = args.getString(ARGS_MESSAGE);
        boolean shouldReturnResult = args.getBoolean(ARGS_SHOULD_RETURN_RESULT);
        Intent resultIntent = args.getParcelable(ARGS_RESULT_INTENT, Intent.class);

        mDialogData = new InstallFailed(appSnippet, legacyCode, statusCode, message,
            shouldReturnResult, resultIntent);
    }
}
+118 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.text.Html;
@@ -37,6 +38,8 @@ import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;

import com.android.packageinstaller.R;
import com.android.packageinstaller.v2.model.InstallAborted;
import com.android.packageinstaller.v2.model.InstallFailed;
import com.android.packageinstaller.v2.model.InstallInstalling;
import com.android.packageinstaller.v2.model.InstallStage;
import com.android.packageinstaller.v2.model.InstallSuccess;
@@ -147,12 +150,19 @@ public class InstallationFragment extends DialogFragment {
        // Get the current install stage
        final InstallStage installStage = getCurrentInstallStage();

        // show the title and adjust the paddings of the custom message textview
        // show the title and reset the paddings of the custom message textview
        if (mTitleTemplate != null) {
            mTitleTemplate.setVisibility(View.VISIBLE);
            mCustomMessageTextView.setPadding(0, 0, 0, 0);
        }

        switch (installStage.getStageCode()) {
            case InstallStage.STAGE_ABORTED -> {
                updateInstallAbortedUI(mDialog, (InstallAborted) installStage);
            }
            case InstallStage.STAGE_FAILED -> {
                updateInstallFailedUI(mDialog, (InstallFailed) installStage);
            }
            case InstallStage.STAGE_INSTALLING -> {
                updateInstallInstallingUI(mDialog, (InstallInstalling) installStage);
            }
@@ -168,6 +178,113 @@ public class InstallationFragment extends DialogFragment {
        }
    }

    private void updateInstallAbortedUI(Dialog dialog, InstallAborted installStage) {
        mProgressBar.setVisibility(View.GONE);
        mAppSnippet.setVisibility(View.GONE);
        mCustomMessageTextView.setVisibility(View.VISIBLE);

        // Set the message
        mCustomMessageTextView.setText(R.string.message_parse_failed);

        // Set the title
        dialog.setTitle(R.string.title_cant_install_app);

        // Hide the positive button
        Button positiveButton = UiUtil.getAlertDialogPositiveButton(dialog);
        if (positiveButton != null) {
            positiveButton.setVisibility(View.GONE);
        }

        // Set the negative button and the listener
        Button negativeButton = UiUtil.getAlertDialogNegativeButton(dialog);
        if (negativeButton != null) {
            negativeButton.setVisibility(View.VISIBLE);
            UiUtil.applyOutlinedButtonStyle(requireContext(), negativeButton);
            negativeButton.setText(R.string.button_close);
            negativeButton.setOnClickListener(view -> {
                mInstallActionListener.onNegativeResponse(
                        installStage.getActivityResultCode(), installStage.getResultIntent());
            });
        }

        // Cancelable is false
        this.setCancelable(false);
    }

    private void updateInstallFailedUI(Dialog dialog, InstallFailed installStage) {
        mAppSnippet.setVisibility(View.VISIBLE);
        mCustomMessageTextView.setVisibility(View.VISIBLE);
        mProgressBar.setVisibility(View.GONE);

        Log.i(LOG_TAG, "Installation status code: " + installStage.getLegacyCode());

        // Set the app icon and label
        mAppIcon.setImageDrawable(installStage.getAppIcon());
        mAppLabelTextView.setText(installStage.getAppLabel());

        int titleResId = R.string.title_install_failed_not_installed;
        String positiveButtonText = null;
        View.OnClickListener positiveButtonListener = null;

        switch (installStage.getLegacyCode()) {
            case PackageInstaller.STATUS_FAILURE_BLOCKED -> {
                mCustomMessageTextView.setText(R.string.message_install_failed_blocked);
                titleResId = R.string.title_install_failed_blocked;
            }
            case PackageInstaller.STATUS_FAILURE_CONFLICT -> {
                mCustomMessageTextView.setText(R.string.message_install_failed_conflict);
                titleResId = R.string.title_cant_install_app;
            }
            case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> {
                mCustomMessageTextView.setText(R.string.message_install_failed_incompatible);
                titleResId = R.string.title_install_failed_incompatible;
            }
            case PackageInstaller.STATUS_FAILURE_INVALID -> {
                mCustomMessageTextView.setText(R.string.message_install_failed_invalid);
                titleResId = R.string.title_cant_install_app;
            }
            case PackageInstaller.STATUS_FAILURE_STORAGE -> {
                mCustomMessageTextView.setText(R.string.message_install_failed_less_storage);
                titleResId = R.string.title_install_failed_less_storage;
                positiveButtonText = getString(R.string.button_manage_apps);
                positiveButtonListener = (view) -> mInstallActionListener.sendManageAppsIntent();
            }
            default -> {
                mCustomMessageTextView.setVisibility(View.GONE);
            }
        }

        // Set the title
        dialog.setTitle(titleResId);

        // Set the positive button and set the listener if needed
        Button positiveButton = UiUtil.getAlertDialogPositiveButton(dialog);
        if (positiveButton != null) {
            if (positiveButtonText == null) {
                positiveButton.setVisibility(View.GONE);
            } else {
                positiveButton.setVisibility(View.VISIBLE);
                UiUtil.applyFilledButtonStyle(requireContext(), positiveButton);
                positiveButton.setText(positiveButtonText);
                positiveButton.setFilterTouchesWhenObscured(true);
                positiveButton.setOnClickListener(positiveButtonListener);
            }
        }

        // Set the negative button and set the listener
        Button negativeButton = UiUtil.getAlertDialogNegativeButton(dialog);
        if (negativeButton != null) {
            negativeButton.setVisibility(View.VISIBLE);
            UiUtil.applyOutlinedButtonStyle(requireContext(), negativeButton);
            negativeButton.setText(R.string.button_close);
            negativeButton.setOnClickListener(view -> {
                mInstallActionListener.onNegativeResponse(installStage.getStageCode());
            });
        }

        this.setCancelable(true);
    }

    private void updateInstallInstallingUI(Dialog dialog, InstallInstalling installStage) {
        mProgressBar.setVisibility(View.VISIBLE);
        mAppSnippet.setVisibility(View.VISIBLE);
Loading