blob: e3aff1524af1aeb9da5986c2aac498a863a40ed5 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
package com.android.providers.media;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.BidiFormatter;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.providers.media.util.FileUtils;
public class CacheClearingActivity extends Activity implements DialogInterface.OnClickListener {
private static final String TAG = "CacheClearingActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
private static final float TEXT_SIZE = 42f;
private static final Long LEAST_SHOW_PROGRESS_TIME_MS = 300L;
private AlertDialog mActionDialog;
private Dialog mLoadingDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String packageName = getCallingPackage();
setResult(RESULT_CANCELED);
if (packageName == null) {
finish();
return;
}
final PackageManager packageManager = getPackageManager();
final ApplicationInfo aInfo;
try {
aInfo = packageManager.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "unable to look up package name", e);
finish();
return;
}
if (!MediaProvider.hasPermissionToClearCaches(this, aInfo)) {
Log.i(TAG, "Calling package " + packageName + " has no permission clear app caches");
finish();
return;
}
// If the label contains new line characters it may push the security
// message below the fold of the dialog. Labels shouldn't have new line
// characters anyways, so we just delete all of the newlines (if there are any).
final String label = aInfo.loadSafeLabel(packageManager, MAX_APP_NAME_SIZE_PX,
TextUtils.SAFE_STRING_FLAG_SINGLE_LINE).toString();
createActionDialog(label);
mActionDialog.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
dismissDialogs(mActionDialog, mLoadingDialog);
}
@Override
public void onClick(DialogInterface dialog, int which) {
dismissDialogs(mActionDialog);
if (which == AlertDialog.BUTTON_POSITIVE) {
new CacheClearingTask().execute();
} else {
finish();
}
}
private class CacheClearingTask extends AsyncTask<Void, Void, Integer> {
private long mStartTime;
@Override
protected void onPreExecute() {
dismissDialogs(mActionDialog);
createLoadingDialog();
mLoadingDialog.show();
mStartTime = System.currentTimeMillis();
}
@Override
public Integer doInBackground(Void... unused) {
return FileUtils.clearAppCacheDirectories();
}
@Override
protected void onPostExecute(Integer result) {
// We take the convention of not using primitive wrapper pretty seriously
int status = result.intValue();
if (result == 0) {
setResult(RESULT_OK);
} else {
setResult(status);
}
// Don't dismiss the progress dialog too quick, it will cause bad UX.
final long duration = System.currentTimeMillis() - mStartTime;
if (duration > LEAST_SHOW_PROGRESS_TIME_MS) {
dismissDialogs(mLoadingDialog);
finish();
} else {
Handler handler = new Handler(getMainLooper());
handler.postDelayed(() -> {
dismissDialogs(mLoadingDialog);
finish();
}, LEAST_SHOW_PROGRESS_TIME_MS - duration);
}
}
}
private void createLoadingDialog() {
final CharSequence dialogTitle = getString(R.string.cache_clearing_in_progress_title);
final View dialogTitleView = View.inflate(this, R.layout.cache_clearing_dialog, null);
final TextView titleText = dialogTitleView.findViewById(R.id.dialog_title);
final ProgressBar progressBar = new ProgressBar(CacheClearingActivity.this);
final int padding = getResources().getDimensionPixelOffset(R.dimen.dialog_space);
progressBar.setIndeterminate(true);
progressBar.setPadding(0, padding / 2, 0, padding);
titleText.setText(dialogTitle);
mLoadingDialog = new AlertDialog.Builder(this)
.setCustomTitle(dialogTitleView)
.setView(progressBar)
.setCancelable(false)
.create();
dialogTitleView.findViewById(R.id.dialog_icon).setVisibility(View.GONE);
mLoadingDialog.create();
setDialogOverlaySettings(mActionDialog);
}
private void createActionDialog(CharSequence appLabel) {
final TextPaint paint = new TextPaint();
paint.setTextSize(TEXT_SIZE);
final String unsanitizedAppName = TextUtils.ellipsize(appLabel,
paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
final String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
final String actionText = getString(R.string.cache_clearing_dialog_text, appName);
final CharSequence dialogTitle = getString(R.string.cache_clearing_dialog_title);
final View dialogTitleView = View.inflate(this, R.layout.cache_clearing_dialog, null);
final TextView titleText = dialogTitleView.findViewById(R.id.dialog_title);
titleText.setText(dialogTitle);
mActionDialog = new AlertDialog.Builder(this)
.setCustomTitle(dialogTitleView)
.setMessage(actionText)
.setPositiveButton(R.string.clear, this)
.setNegativeButton(android.R.string.cancel, this)
.setCancelable(false)
.create();
mActionDialog.create();
mActionDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
setDialogOverlaySettings(mActionDialog);
}
private static void setDialogOverlaySettings(Dialog d) {
final Window w = d.getWindow();
w.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
}
private static void dismissDialogs(Dialog... dialogs) {
for (Dialog d : dialogs) {
if (d != null) {
d.dismiss();
}
}
}
}