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