1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.packageinstaller;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.app.DialogFragment;
25 import android.app.PendingIntent;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.IntentSender;
29 import android.content.pm.PackageInstaller;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.provider.Settings;
33 import android.text.format.Formatter;
34 import android.util.Log;
35 
36 import androidx.annotation.Nullable;
37 
38 public class UnarchiveErrorFragment extends DialogFragment implements
39         DialogInterface.OnClickListener {
40 
41     private static final String TAG = "UnarchiveErrorFragment";
42 
43     private int mStatus;
44 
45     @Nullable
46     private PendingIntent mExtraIntent;
47 
48     @Nullable
49     private String mInstallerPackageName;
50 
51     @Override
onCreateDialog(Bundle savedInstanceState)52     public Dialog onCreateDialog(Bundle savedInstanceState) {
53         Bundle args = getArguments();
54         mStatus = args.getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS, -1);
55         mExtraIntent = args.getParcelable(Intent.EXTRA_INTENT, PendingIntent.class);
56         long requiredBytes = args.getLong(UnarchiveErrorActivity.EXTRA_REQUIRED_BYTES);
57         mInstallerPackageName = args.getString(
58                 UnarchiveErrorActivity.EXTRA_INSTALLER_PACKAGE_NAME);
59         String installerAppTitle = args.getString(UnarchiveErrorActivity.EXTRA_INSTALLER_TITLE);
60 
61         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
62 
63         dialogBuilder.setTitle(getDialogTitle(mStatus, installerAppTitle));
64         dialogBuilder.setMessage(getBodyText(mStatus, installerAppTitle, requiredBytes));
65 
66         addButtons(dialogBuilder, mStatus);
67 
68         return dialogBuilder.create();
69     }
70 
addButtons(AlertDialog.Builder dialogBuilder, int status)71     private void addButtons(AlertDialog.Builder dialogBuilder, int status) {
72         switch (status) {
73             case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
74                 dialogBuilder.setPositiveButton(R.string.unarchive_action_required_continue, this);
75                 dialogBuilder.setNegativeButton(R.string.close, this);
76                 break;
77             case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
78                 dialogBuilder.setPositiveButton(R.string.unarchive_clear_storage_button, this);
79                 dialogBuilder.setNegativeButton(R.string.close, this);
80                 break;
81             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
82                 dialogBuilder.setPositiveButton(R.string.external_sources_settings, this);
83                 dialogBuilder.setNegativeButton(R.string.close, this);
84                 break;
85             case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
86             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
87             case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
88                 dialogBuilder.setPositiveButton(android.R.string.ok, this);
89                 break;
90             default:
91                 // This should never happen through normal API usage.
92                 throw new IllegalArgumentException("Invalid unarchive status " + status);
93         }
94     }
95 
getBodyText(int status, String installerAppTitle, long requiredBytes)96     private String getBodyText(int status, String installerAppTitle, long requiredBytes) {
97         switch (status) {
98             case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
99                 return getString(R.string.unarchive_action_required_body);
100             case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
101                 return String.format(getString(R.string.unarchive_error_storage_body),
102                         Formatter.formatShortFileSize(getActivity(), requiredBytes));
103             case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
104                 return getString(R.string.unarchive_error_offline_body);
105             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
106                 return String.format(getString(R.string.unarchive_error_installer_disabled_body),
107                         installerAppTitle);
108             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
109                 return String.format(
110                         getString(R.string.unarchive_error_installer_uninstalled_body),
111                         installerAppTitle);
112             case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
113                 return getString(R.string.unarchive_error_generic_body);
114             default:
115                 // This should never happen through normal API usage.
116                 throw new IllegalArgumentException("Invalid unarchive status " + status);
117         }
118     }
119 
getDialogTitle(int status, String installerAppTitle)120     private String getDialogTitle(int status, String installerAppTitle) {
121         switch (status) {
122             case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
123                 return getString(R.string.unarchive_action_required_title);
124             case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
125                 return getString(R.string.unarchive_error_storage_title);
126             case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
127                 return getString(R.string.unarchive_error_offline_title);
128             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
129                 return String.format(getString(R.string.unarchive_error_installer_disabled_title),
130                         installerAppTitle);
131             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
132                 return String.format(
133                         getString(R.string.unarchive_error_installer_uninstalled_title),
134                         installerAppTitle);
135             case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
136                 return getString(R.string.unarchive_error_generic_title);
137             default:
138                 // This should never happen through normal API usage.
139                 throw new IllegalArgumentException("Invalid unarchive status " + status);
140         }
141     }
142 
143     @Override
onClick(DialogInterface dialog, int which)144     public void onClick(DialogInterface dialog, int which) {
145         if (which != Dialog.BUTTON_POSITIVE) {
146             return;
147         }
148 
149         try {
150             onClickInternal();
151         } catch (IntentSender.SendIntentException e) {
152             Log.e(TAG, "Failed to start intent after onClick.", e);
153         }
154     }
155 
onClickInternal()156     private void onClickInternal() throws IntentSender.SendIntentException {
157         Activity activity = getActivity();
158         if (activity == null) {
159             // This probably shouldn't happen in practice.
160             Log.i(TAG, "Lost reference to activity, cannot act onClick.");
161             return;
162         }
163 
164         switch (mStatus) {
165             case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
166                 activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
167                         null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
168                 break;
169             case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
170                 if (mExtraIntent != null) {
171                     activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
172                             null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
173                 } else {
174                     Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
175                     startActivity(intent);
176                 }
177                 break;
178             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
179                 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
180                 Uri uri = Uri.fromParts("package", mInstallerPackageName, null);
181                 intent.setData(uri);
182                 startActivity(intent);
183                 break;
184             default:
185                 // Do nothing. The rest of the dialogs are purely informational.
186         }
187     }
188 
189     @Override
onDismiss(DialogInterface dialog)190     public void onDismiss(DialogInterface dialog) {
191         super.onDismiss(dialog);
192         if (isAdded()) {
193             getActivity().finish();
194         }
195     }
196 }
197