• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.providers.media;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.content.DialogInterface;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.graphics.Typeface;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.text.BidiFormatter;
30 import android.text.SpannableString;
31 import android.text.TextPaint;
32 import android.text.TextUtils;
33 import android.text.style.StyleSpan;
34 import android.util.Log;
35 import android.view.View;
36 import android.view.Window;
37 import android.view.WindowManager;
38 import android.widget.ProgressBar;
39 import android.widget.TextView;
40 
41 import com.android.providers.media.util.FileUtils;
42 
43 public class CacheClearingActivity extends Activity implements DialogInterface.OnClickListener {
44     private static final String TAG = "CacheClearingActivity";
45     private static final float MAX_APP_NAME_SIZE_PX = 500f;
46     private static final float TEXT_SIZE = 42f;
47     private static final Long LEAST_SHOW_PROGRESS_TIME_MS = 300L;
48 
49     private AlertDialog mActionDialog;
50     private Dialog mLoadingDialog;
51 
52     @Override
onCreate(Bundle savedInstanceState)53     public void onCreate(Bundle savedInstanceState) {
54         super.onCreate(savedInstanceState);
55         final String packageName = getCallingPackage();
56         setResult(RESULT_CANCELED);
57 
58         if (packageName == null) {
59             finish();
60             return;
61         }
62 
63         final PackageManager packageManager = getPackageManager();
64         final ApplicationInfo aInfo;
65         try {
66             aInfo = packageManager.getApplicationInfo(packageName, 0);
67         } catch (PackageManager.NameNotFoundException e) {
68             Log.e(TAG, "unable to look up package name", e);
69             finish();
70             return;
71         }
72 
73         if (!MediaProvider.hasPermissionToClearCaches(this, aInfo)) {
74             Log.i(TAG, "Calling package " + packageName + " has no permission clear app caches");
75             finish();
76             return;
77         }
78 
79         // If the label contains new line characters it may push the security
80         // message below the fold of the dialog. Labels shouldn't have new line
81         // characters anyways, so we just delete all of the newlines (if there are any).
82         final String label = aInfo.loadSafeLabel(packageManager, MAX_APP_NAME_SIZE_PX,
83                 TextUtils.SAFE_STRING_FLAG_SINGLE_LINE).toString();
84 
85         createActionDialog(label);
86         mActionDialog.show();
87     }
88 
89     @Override
onDestroy()90     protected void onDestroy() {
91         super.onDestroy();
92         dismissDialogs(mActionDialog, mLoadingDialog);
93     }
94 
95     @Override
onClick(DialogInterface dialog, int which)96     public void onClick(DialogInterface dialog, int which) {
97         dismissDialogs(mActionDialog);
98 
99         if (which == AlertDialog.BUTTON_POSITIVE) {
100             new CacheClearingTask().execute();
101         } else {
102             finish();
103         }
104     }
105 
106     private class CacheClearingTask extends AsyncTask<Void, Void, Integer> {
107         private long mStartTime;
108 
109         @Override
onPreExecute()110         protected void onPreExecute() {
111             dismissDialogs(mActionDialog);
112             createLoadingDialog();
113             mLoadingDialog.show();
114             mStartTime = System.currentTimeMillis();
115         }
116 
117         @Override
doInBackground(Void... unused)118         public Integer doInBackground(Void... unused) {
119             return FileUtils.clearAppCacheDirectories();
120         }
121 
122         @Override
onPostExecute(Integer result)123         protected void onPostExecute(Integer result) {
124             // We take the convention of not using primitive wrapper pretty seriously
125             int status = result.intValue();
126 
127             if (result == 0) {
128                 setResult(RESULT_OK);
129             } else {
130                 setResult(status);
131             }
132 
133             // Don't dismiss the progress dialog too quick, it will cause bad UX.
134             final long duration = System.currentTimeMillis() - mStartTime;
135             if (duration > LEAST_SHOW_PROGRESS_TIME_MS) {
136                 dismissDialogs(mLoadingDialog);
137                 finish();
138             } else {
139                 Handler handler = new Handler(getMainLooper());
140                 handler.postDelayed(() -> {
141                     dismissDialogs(mLoadingDialog);
142                     finish();
143                 }, LEAST_SHOW_PROGRESS_TIME_MS - duration);
144             }
145         }
146     }
147 
createLoadingDialog()148     private void createLoadingDialog() {
149         final CharSequence dialogTitle = getString(R.string.cache_clearing_in_progress_title);
150         final View dialogTitleView = View.inflate(this, R.layout.cache_clearing_dialog, null);
151         final TextView titleText = dialogTitleView.findViewById(R.id.dialog_title);
152         final ProgressBar progressBar = new ProgressBar(CacheClearingActivity.this);
153         final int padding = getResources().getDimensionPixelOffset(R.dimen.dialog_space);
154 
155         progressBar.setIndeterminate(true);
156         progressBar.setPadding(0, padding / 2, 0, padding);
157         titleText.setText(dialogTitle);
158         mLoadingDialog = new AlertDialog.Builder(this)
159                 .setCustomTitle(dialogTitleView)
160                 .setView(progressBar)
161                 .setCancelable(false)
162                 .create();
163 
164         dialogTitleView.findViewById(R.id.dialog_icon).setVisibility(View.GONE);
165         mLoadingDialog.create();
166         setDialogOverlaySettings(mActionDialog);
167     }
168 
createActionDialog(CharSequence appLabel)169     private void createActionDialog(CharSequence appLabel) {
170         final TextPaint paint = new TextPaint();
171         paint.setTextSize(TEXT_SIZE);
172 
173         final String unsanitizedAppName = TextUtils.ellipsize(appLabel,
174                 paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
175         final String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
176         final String actionText = getString(R.string.cache_clearing_dialog_text, appName);
177         final CharSequence dialogTitle = getString(R.string.cache_clearing_dialog_title);
178 
179         final View dialogTitleView = View.inflate(this, R.layout.cache_clearing_dialog, null);
180         final TextView titleText = dialogTitleView.findViewById(R.id.dialog_title);
181         titleText.setText(dialogTitle);
182         mActionDialog = new AlertDialog.Builder(this)
183                 .setCustomTitle(dialogTitleView)
184                 .setMessage(actionText)
185                 .setPositiveButton(R.string.clear, this)
186                 .setNegativeButton(android.R.string.cancel, this)
187                 .setCancelable(false)
188                 .create();
189 
190         mActionDialog.create();
191         mActionDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
192 
193         setDialogOverlaySettings(mActionDialog);
194     }
195 
setDialogOverlaySettings(Dialog d)196     private static void setDialogOverlaySettings(Dialog d) {
197         final Window w = d.getWindow();
198         w.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
199     }
200 
dismissDialogs(Dialog... dialogs)201     private static void dismissDialogs(Dialog... dialogs) {
202         for (Dialog d : dialogs) {
203             if (d != null) {
204                 d.dismiss();
205             }
206         }
207     }
208 }
209