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

Commit 18d73a38 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[ADI][49/N] New UI dialog layout" into main

parents 62e5f877 14000870
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
<!--
    Copyright (C) 2025 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:height="32dp"
        android:viewportHeight="48"
        android:viewportWidth="48"
        android:width="20dp">
    <path
        android:fillAlpha="1.0"
        android:fillType="nonZero"
        android:fillColor="?android:attr/colorAccent"
        android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"/>
</vector>
+94 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
  Copyright (C) 2025 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.
  -->

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/verification_confirmation_fragment_layout"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:paddingHorizontal="?android:attr/dialogPreferredPadding"
    android:paddingBottom="@dimen/alert_dialog_inner_padding">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <include layout="@layout/app_snippet_layout" />

        <TextView
            style="?attr/textAppearanceInstallerCustomMessage"
            android:id="@+id/custom_message"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:layout_marginTop="@dimen/dialog_inter_element_margin"/>

        <!-- TODO(b/360129657): set touch area as current height does not meet a11y requirement -->
        <LinearLayout
            android:id="@+id/more_details_clickable_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:visibility="gone">

            <TextView
                android:id="@+id/more_details_button"
                android:text="@string/more_details"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:selectable="false"
                android:gravity="center_vertical"
                style="?attr/textAppearanceInstallerCustomMessage"/>

            <ImageView
                android:src="@drawable/ic_keyboard_arrow_down"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:selectable="false" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/more_details_expanded_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="gone">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="start"
                android:text="@string/install_without_verifying_summary"
                style="?attr/textAppearanceInstallerCustomMessage"/>

            <TextView
                android:id="@+id/install_without_verifying_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="start"
                android:text="@string/install_without_verifying"
                android:paddingTop="@dimen/install_without_verifying_text_padding"
                android:textColor="@color/primaryColor"
                style="?attr/textAppearanceInstallerCustomMessage"/>

        </LinearLayout>

    </LinearLayout>

</ScrollView>
+5 −0
Original line number Diff line number Diff line
@@ -60,4 +60,9 @@
    <!-- 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>

    <dimen name="install_without_verifying_text_padding">16dp</dimen>
    <dimen name="more_details_clickable_layout_vertical_margin">16dp</dimen>
    <dimen name="keyboard_arrow_down_icon_width">20dp</dimen>

</resources>
+23 −5
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@
    <!-- [CHAR LIMIT=15] -->
    <string name="cancel">Cancel</string>
    <!-- [CHAR LIMIT=15] -->
    <string name="install_anyway">Install anyway</string>
    <string name="install_without_verifying">Install without verifying</string>
    <!-- [CHAR LIMIT=15] -->
    <string name="more_details">More details</string>
    <!-- [CHAR LIMIT=15] -->
    <string name="dont_install">Don\'t install</string>
    <!-- [CHAR LIMIT=50] -->
@@ -87,13 +89,29 @@
    <!-- Message presented in a dialog box when the user restriction set by the system restricts the installation of apps. [CHAR LIMIT=none] -->
    <string name="install_apps_user_restriction_dlg_text">This user is not allowed to install apps</string>
    <!-- Message presented in a dialog box when verification failed due to problems with the network. [CHAR LIMIT=none]-->
    <string name="verification_incomplete_summary">App is not yet installed because verification could not take place. Please try again.</string>
    <string name="cannot_install_verification_no_internet_summary">&lt;p>This app was not installed.
        Apps from unverified developers may be unsafe.&lt;/p>
        &lt;p>An internet connection is required to verify the app developer.
            Make sure your device is connected to the internet, then try installing the app again.
        &lt;/p>
        &lt;p>&lt;a href="">Learn more about developer verification&lt;/a>&lt;p></string>
    <!-- Message presented in a dialog box when verification failed due to internal failures. [CHAR LIMIT=none]-->
    <string name="cannot_install_verification_unavailable_summary"><p>Warning: App verification could not take place. </p><p><a href="">Learn more</a> about package verification</p></string>
    <string name="cannot_install_verification_unavailable_summary">&lt;p>There was a problem verifying
        this app\'s developer. Apps from unverified developers may be unsafe,
        so the app was not installed.&lt;/p>
        &lt;p>&lt;a href="">Learn more about developer verification&lt;/a>&lt;/p></string>
    <!-- Message presented in a dialog box when the verifier blocked an app from being installed. [CHAR LIMIT=none]-->
    <string name="cannot_install_package_summary"><p>This app can’t be installed because it has been blocked. </p><p><a href="">Learn more</a> about package verification</p></string>
    <string name="cannot_install_app_blocked_summary">&lt;p>This app was blocked. To help keep your
        device and data safe, only apps from verified developers can be installed.&lt;/p>
        &lt;p>&lt;a href="">Learn more about developer verification&lt;/a>&lt;/p></string>
    <!-- Message presented in a dialog box when only the lite verification could be performed on the installation. [CHAR LIMIT=none]-->
    <string name="lite_verification_summary"><p>This app is owned by ADI Lite Developer and bypasses the standard verification process. Do you want to proceed with installation?</p></string>
    <string name="lite_verification_summary">&lt;p>This app is owned by ADI Lite Developer and bypasses
        the standard verification process. Do you want to proceed with installation?&lt;/p></string>
    <string name="install_without_verifying_summary">If you install without verifying, keep in
        mind apps from unverified developers may put your device and data at risk.</string>
    <string name="cannot_install_app_blocked_title">App developer unverified</string>
    <string name="cannot_install_verification_no_internet_title">No internet, can\'t verify app developer</string>
    <string name="cannot_install_verification_unavailable_title">Can\'t verify app developer</string>

    <!-- [CHAR LIMIT=15] -->
    <string name="ok">OK</string>
+94 −48
Original line number Diff line number Diff line
@@ -24,15 +24,20 @@ import static android.content.pm.PackageInstaller.DeveloperVerificationUserConfi
import static android.content.pm.PackageInstaller.DeveloperVerificationUserConfirmationInfo.DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_NETWORK_UNAVAILABLE;
import static android.content.pm.PackageInstaller.DeveloperVerificationUserConfirmationInfo.DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.DeveloperVerificationUserConfirmationInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -41,6 +46,7 @@ import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
import com.android.packageinstaller.v2.ui.UiUtil;

public class DeveloperVerificationConfirmationFragment extends DialogFragment {

@@ -50,10 +56,18 @@ public class DeveloperVerificationConfirmationFragment extends DialogFragment {
    @NonNull
    private InstallActionListener mInstallActionListener;
    @NonNull
    private AlertDialog mDialog;
    private Button mPositiveBtn;
    private Button mNegativeBtn;
    private Button mNeutralBtn;
    private Dialog mDialog;
    private boolean mIsBypassAllowed;
    private ImageView mAppIcon;
    private TextView mAppLabelTextView;
    private View mAppSnippet;
    private View mMoreDetailsClickableLayout;
    private View mMoreDetailsExpandedLayout;
    private TextView mInstallWithoutVerifyingTextView;
    private String mCustomMessage;
    private TextView mCustomMessageTextView;

    private boolean mIsExpanded;

    public DeveloperVerificationConfirmationFragment(
            @NonNull InstallUserActionRequired dialogData) {
@@ -70,75 +84,95 @@ public class DeveloperVerificationConfirmationFragment extends DialogFragment {
    @NonNull
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        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(
                R.layout.verification_confirmation_fragment_layout, null);
        mAppSnippet = dialogView.requireViewById(R.id.app_snippet);
        mAppIcon = dialogView.requireViewById(R.id.app_icon);
        mAppLabelTextView = dialogView.requireViewById(R.id.app_label);
        mMoreDetailsClickableLayout = dialogView.requireViewById(
                R.id.more_details_clickable_layout);
        mMoreDetailsExpandedLayout = dialogView.requireViewById(
                R.id.more_details_expanded_layout);
        mInstallWithoutVerifyingTextView = dialogView.requireViewById(
                R.id.install_without_verifying_text);
        mCustomMessageTextView = dialogView.requireViewById(R.id.custom_message);

        DeveloperVerificationUserConfirmationInfo verificationInfo =
                mDialogData.getVerificationInfo();
        assert verificationInfo != null;
        boolean isBypassAllowed = isBypassAllowed(verificationInfo);
        mIsBypassAllowed = isBypassAllowed(verificationInfo);
        int userActionNeededReasonReason = verificationInfo.getUserActionNeededReason();
        int titleResId = getDialogTitleResourceId(userActionNeededReasonReason);
        int msgResId = getDialogMessageResourceId(userActionNeededReasonReason);
        mCustomMessage = getString(msgResId);
        mDialog = UiUtil.getAlertDialog(requireContext(), getString(titleResId),
                dialogView, R.string.ok, Resources.ID_NULL,
                (dialog, which) -> mInstallActionListener.setVerificationUserResponse(
                        DEVELOPER_VERIFICATION_USER_RESPONSE_ABORT),
                null);

        AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
                .setIcon(mDialogData.getAppIcon())
                .setTitle(mDialogData.getAppLabel())
                .setMessage(msgResId);
        return mDialog;
    }

        if (isBypassAllowed) {
            // only allow bypass
            builder.setPositiveButton(R.string.dont_install,
                            (dialog, which) -> mInstallActionListener.setVerificationUserResponse(
                                    DEVELOPER_VERIFICATION_USER_RESPONSE_ABORT))
                    .setNegativeButton(R.string.install_anyway,
                            (dialog, which) -> mInstallActionListener.setVerificationUserResponse(
                                    DEVELOPER_VERIFICATION_USER_RESPONSE_INSTALL_ANYWAY));
        } else {
            // allow only acknowledging the error
            builder.setPositiveButton(
                    (userActionNeededReasonReason
                            == DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_DEVELOPER_BLOCKED)
                            ? R.string.close : R.string.ok,
                    (dialog, which) -> mInstallActionListener.setVerificationUserResponse(
                            DEVELOPER_VERIFICATION_USER_RESPONSE_ABORT));
    private void updateUI() {
        if (!isAdded()) {
            return;
        }
        builder.setOnCancelListener(dialog ->
                mInstallActionListener.setVerificationUserResponse(
                        DEVELOPER_VERIFICATION_USER_RESPONSE_ABORT));

        mDialog = builder.create();
        return mDialog;
        mCustomMessageTextView.setText(Html.fromHtml(mCustomMessage, Html.FROM_HTML_MODE_LEGACY));

        mAppSnippet.setVisibility(View.VISIBLE);
        mAppIcon.setImageDrawable(mDialogData.getAppIcon());
        mAppLabelTextView.setText(mDialogData.getAppLabel());

        if (mIsBypassAllowed) {
            if (!mIsExpanded) {
                mMoreDetailsClickableLayout.setVisibility(View.VISIBLE);
                mMoreDetailsExpandedLayout.setVisibility(View.GONE);
            }

            mMoreDetailsClickableLayout.setOnClickListener(v -> {
                mIsExpanded = true;
                mMoreDetailsClickableLayout.setVisibility(View.GONE);
                mMoreDetailsExpandedLayout.setVisibility(View.VISIBLE);
                mInstallWithoutVerifyingTextView.setTypeface(
                        mInstallWithoutVerifyingTextView.getTypeface(), Typeface.BOLD);
                mInstallWithoutVerifyingTextView.setOnClickListener(v1 -> {
                    mInstallActionListener.setVerificationUserResponse(
                            DEVELOPER_VERIFICATION_USER_RESPONSE_INSTALL_ANYWAY);
                    mDialog.dismiss();
                });
            });
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        mPositiveBtn = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
        mNegativeBtn = mDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
        mNeutralBtn = mDialog.getButton(DialogInterface.BUTTON_NEUTRAL);
        updateUI();
    }

    @Override
    public void onPause() {
        super.onPause();
        // Don't allow the buttons to be clicked as there might be overlays
        Button[] buttons = {mPositiveBtn, mNegativeBtn, mNeutralBtn};
        for (Button button : buttons) {
        Button button = UiUtil.getAlertDialogPositiveButton(mDialog);
        if (button != null) {
            button.setEnabled(false);
        }
    }
    }

    @Override
    public void onResume() {
        super.onResume();
        // Re-enable the buttons since they were disabled when activity was paused
        Button[] buttons = {mPositiveBtn, mNegativeBtn, mNeutralBtn};
        for (Button button : buttons) {
        Button button = UiUtil.getAlertDialogPositiveButton(mDialog);
        if (button != null) {
            button.setEnabled(true);
        }
    }
    }

    /**
     * Returns whether the user can choose to bypass the verification result and force installation,
@@ -165,18 +199,30 @@ public class DeveloperVerificationConfirmationFragment extends DialogFragment {
        };
    }

    private int getDialogTitleResourceId(int userActionNeededReason) {
        return switch (userActionNeededReason) {
            case DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN ->
                    R.string.cannot_install_verification_unavailable_title;

            case DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_NETWORK_UNAVAILABLE ->
                    R.string.cannot_install_verification_no_internet_title;

            default -> R.string.cannot_install_app_blocked_title;
        };
    }

    private int getDialogMessageResourceId(int userActionNeededReason) {
        return switch (userActionNeededReason) {
            case DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_UNKNOWN ->
                    R.string.cannot_install_verification_unavailable_summary;

            case DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_NETWORK_UNAVAILABLE ->
                    R.string.verification_incomplete_summary;
                    R.string.cannot_install_verification_no_internet_summary;

            case DEVELOPER_VERIFICATION_USER_ACTION_NEEDED_REASON_LITE_VERIFICATION ->
                    R.string.lite_verification_summary;

            default -> R.string.cannot_install_package_summary;
            default -> R.string.cannot_install_app_blocked_summary;
        };
    }
}