1 /*
2  * Copyright (C) 2021 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.settings.applications.intentpicker;
18 
19 import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE;
20 
21 import android.app.Dialog;
22 import android.app.settings.SettingsEnums;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.pm.verify.domain.DomainOwner;
26 import android.content.pm.verify.domain.DomainVerificationManager;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.SystemClock;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.widget.ProgressBar;
35 
36 import androidx.annotation.NonNull;
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.fragment.app.FragmentManager;
39 import androidx.lifecycle.ViewModelProvider;
40 
41 import com.android.settings.R;
42 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
43 import com.android.settingslib.utils.ThreadUtils;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.SortedSet;
49 
50 /** A customized {@link InstrumentedDialogFragment} with a progress bar. */
51 public class ProgressDialogFragment extends InstrumentedDialogFragment {
52     private static final String TAG = "ProgressDialogFragment";
53     private static final String DLG_ID = "ProgressDialog";
54     private static final int PROGRESS_BAR_STEPPING_TIME = 20;
55 
56     private ProgressAlertDialog mProgressAlertDialog;
57     private DomainVerificationManager mDomainVerificationManager;
58     private List<SupportedLinkWrapper> mSupportedLinkWrapperList;
59     private SupportedLinkViewModel mViewModel;
60     private Handler mHandle;
61     private String mPackage;
62 
63     @Override
onCreate(Bundle savedInstanceState)64     public void onCreate(Bundle savedInstanceState) {
65         super.onCreate(savedInstanceState);
66         mViewModel = new ViewModelProvider(this.getActivity()).get(SupportedLinkViewModel.class);
67     }
68 
69     @Override
onCreateDialog(Bundle savedInstanceState)70     public Dialog onCreateDialog(Bundle savedInstanceState) {
71         mPackage = getArguments().getString(AppLaunchSettings.APP_PACKAGE_KEY);
72         mDomainVerificationManager = getActivity().getSystemService(
73                 DomainVerificationManager.class);
74         mHandle = new Handler(Looper.getMainLooper());
75         mProgressAlertDialog = createProgressAlertDialog();
76         return mProgressAlertDialog;
77     }
78 
createProgressAlertDialog()79     private ProgressAlertDialog createProgressAlertDialog() {
80         final Context context = getActivity();
81         final ProgressAlertDialog progressDialog = new ProgressAlertDialog(context);
82         final String title = context.getResources().getString(
83                 R.string.app_launch_checking_links_title);
84         progressDialog.setTitle(IntentPickerUtils.getCentralizedDialogTitle(title));
85         progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
86                 context.getText(R.string.app_launch_dialog_cancel),
87                 (dialog, which) -> {
88                     if (which == DialogInterface.BUTTON_NEGATIVE) {
89                         dialog.cancel();
90                     }
91                 });
92         progressDialog.setCanceledOnTouchOutside(true);
93         return progressDialog;
94     }
95 
96     /** Display the {@link ProgressAlertDialog}. */
showDialog(FragmentManager manager)97     public void showDialog(FragmentManager manager) {
98         show(manager, DLG_ID);
99     }
100 
101     @Override
onResume()102     public void onResume() {
103         super.onResume();
104         generateProgressAlertDialog();
105     }
106 
107     @Override
onDestroy()108     public void onDestroy() {
109         super.onDestroy();
110         if (mProgressAlertDialog != null && mProgressAlertDialog.isShowing()) {
111             mProgressAlertDialog.cancel();
112         }
113     }
114 
115     @Override
getMetricsCategory()116     public int getMetricsCategory() {
117         return SettingsEnums.PROGRESS_DIALOG;
118     }
119 
120     /**
121      * To generate a progress alter dialog and invoke the supported links dialog.
122      */
generateProgressAlertDialog()123     private void generateProgressAlertDialog() {
124         ThreadUtils.postOnBackgroundThread(() -> {
125             final long start = SystemClock.elapsedRealtime();
126             queryLinksInBackground();
127             IntentPickerUtils.logd(
128                     "queryLinksInBackground take time: " + (SystemClock.elapsedRealtime() - start));
129             if (mProgressAlertDialog.isShowing()) {
130                 mHandle.post(() -> {
131                     synchronized (mHandle) {
132                         if (mProgressAlertDialog.isShowing()) {
133                             mProgressAlertDialog.dismiss();
134                             IntentPickerUtils.logd("mProgressAlertDialog.dismiss() and isShowing: "
135                                     + mProgressAlertDialog.isShowing());
136                             launchSupportedLinksDialogFragment();
137                         }
138                     }
139                 });
140             }
141         });
142     }
143 
queryLinksInBackground()144     private void queryLinksInBackground() {
145         final List<String> links = IntentPickerUtils.getLinksList(mDomainVerificationManager,
146                 mPackage, DOMAIN_STATE_NONE);
147         final int linksNo = links.size();
148         int index = 0;
149         mSupportedLinkWrapperList = new ArrayList<>();
150         for (String host : links) {
151             final SortedSet<DomainOwner> ownerSet =
152                     mDomainVerificationManager.getOwnersForDomain(host);
153             mSupportedLinkWrapperList.add(new SupportedLinkWrapper(getActivity(), host, ownerSet));
154             index++;
155             // The cancel was clicked while progressing to collect data.
156             if (!mProgressAlertDialog.isShowing()) {
157                 Log.w(TAG, "Exit the background thread!!!");
158                 // clear buffer
159                 mSupportedLinkWrapperList.clear();
160                 break;
161             }
162             int progress = (int) (index * 100) / linksNo;
163             mHandle.post(() -> {
164                 synchronized (mHandle) {
165                     if (!mProgressAlertDialog.isShowing()) {
166                         Log.w(TAG, "Exit the UI thread");
167                         return;
168                     }
169                     mProgressAlertDialog.getProgressBar().setProgress(progress);
170                 }
171             });
172             if (ownerSet.size() == 0) {
173                 SystemClock.sleep(PROGRESS_BAR_STEPPING_TIME);
174             }
175         }
176         IntentPickerUtils.logd("queryLinksInBackground : SupportedLinkWrapperList size="
177                 + mSupportedLinkWrapperList.size());
178         Collections.sort(mSupportedLinkWrapperList);
179     }
180 
launchSupportedLinksDialogFragment()181     private void launchSupportedLinksDialogFragment() {
182         if (mSupportedLinkWrapperList.size() > 0) {
183             mViewModel.setSupportedLinkWrapperList(mSupportedLinkWrapperList);
184             final Bundle args = new Bundle();
185             args.putString(AppLaunchSettings.APP_PACKAGE_KEY, mPackage);
186             final SupportedLinksDialogFragment dialogFragment = new SupportedLinksDialogFragment();
187             dialogFragment.setArguments(args);
188             dialogFragment.showDialog(getActivity().getSupportFragmentManager());
189         }
190     }
191 
192     /** Create a custom {@link AlertDialog} with a {@link ProgressBar}. */
193     static class ProgressAlertDialog extends AlertDialog {
194         private ProgressBar mProgressBar;
195 
ProgressAlertDialog(@onNull Context context)196         protected ProgressAlertDialog(@NonNull Context context) {
197             this(context, 0);
198         }
199 
ProgressAlertDialog(@onNull Context context, int themeResId)200         protected ProgressAlertDialog(@NonNull Context context, int themeResId) {
201             super(context, themeResId);
202             init(context);
203         }
204 
init(Context context)205         private void init(Context context) {
206             final View view = LayoutInflater.from(context).inflate(
207                     R.layout.app_launch_progress, /* root= */ null);
208             mProgressBar = view.findViewById(R.id.scan_links_progressbar);
209             mProgressBar.setProgress(0);
210             mProgressBar.setMax(100);
211             setView(view);
212         }
213 
getProgressBar()214         ProgressBar getProgressBar() {
215             return mProgressBar;
216         }
217     }
218 }
219