This commit *only* changes imports and optimize imports.
We don't do anything else.
This patch can't compile pass and run test case.
We will update other patches to fix these problem.
Change list.
1. import android.app.Fragment; ->
import androidx.fragment.app.Fragment;
2. import android.app.DialogFragment; ->
import androidx.fragment.app.DialogFragment;
3. import android.app.ListFragment; ->
import androidx.fragment.app.ListFragment;
4. import android.app.LoaderManager; ->
import androidx.loader.app.LoaderManager;
5. import android.content.AsyncTaskLoader; ->
import androidx.loader.content.AsyncTaskLoader;
6. import android.content.Loader; ->
import androidx.loader.content.Loader;
7. import android.app.FragmentTransaction; ->
import androidx.fragment.app.FragmentTransaction;
8. import android.app.FragmentManager; ->
import androidx.fragment.app.FragmentManager;
9. import android.app.LoaderManager.LoaderCallbacks; ->
import androidx.loader.app.LoaderManager.LoaderCallbacks;
Bug: 110259478
Test: Can't test it.
Change-Id: I0a3f98fff34a3494a839c3c42aeabcec3df2c8b3
479 lines
18 KiB
Java
479 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2013 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.settings.users;
|
|
|
|
import android.app.Activity;
|
|
import android.content.ClipData;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.database.Cursor;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Bitmap.Config;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.os.AsyncTask;
|
|
import android.os.StrictMode;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.provider.ContactsContract.DisplayPhoto;
|
|
import android.provider.MediaStore;
|
|
import android.util.Log;
|
|
import android.view.Gravity;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.view.ViewGroup;
|
|
import android.widget.AdapterView;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.ImageView;
|
|
import android.widget.ListPopupWindow;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settingslib.RestrictedLockUtils;
|
|
import com.android.settingslib.drawable.CircleFramedDrawable;
|
|
|
|
import libcore.io.Streams;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import androidx.core.content.FileProvider;
|
|
import androidx.fragment.app.Fragment;
|
|
|
|
public class EditUserPhotoController {
|
|
private static final String TAG = "EditUserPhotoController";
|
|
|
|
// It seems that this class generates custom request codes and they may
|
|
// collide with ours, these values are very unlikely to have a conflict.
|
|
private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
|
|
private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
|
|
private static final int REQUEST_CODE_CROP_PHOTO = 1003;
|
|
|
|
private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
|
|
private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
|
|
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
|
|
|
|
private final int mPhotoSize;
|
|
|
|
private final Context mContext;
|
|
private final Fragment mFragment;
|
|
private final ImageView mImageView;
|
|
|
|
private final Uri mCropPictureUri;
|
|
private final Uri mTakePictureUri;
|
|
|
|
private Bitmap mNewUserPhotoBitmap;
|
|
private Drawable mNewUserPhotoDrawable;
|
|
|
|
public EditUserPhotoController(Fragment fragment, ImageView view,
|
|
Bitmap bitmap, Drawable drawable, boolean waiting) {
|
|
mContext = view.getContext();
|
|
mFragment = fragment;
|
|
mImageView = view;
|
|
mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting);
|
|
mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting);
|
|
mPhotoSize = getPhotoSize(mContext);
|
|
mImageView.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
showUpdatePhotoPopup();
|
|
}
|
|
});
|
|
mNewUserPhotoBitmap = bitmap;
|
|
mNewUserPhotoDrawable = drawable;
|
|
}
|
|
|
|
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
if (resultCode != Activity.RESULT_OK) {
|
|
return false;
|
|
}
|
|
final Uri pictureUri = data != null && data.getData() != null
|
|
? data.getData() : mTakePictureUri;
|
|
switch (requestCode) {
|
|
case REQUEST_CODE_CROP_PHOTO:
|
|
onPhotoCropped(pictureUri, true);
|
|
return true;
|
|
case REQUEST_CODE_TAKE_PHOTO:
|
|
case REQUEST_CODE_CHOOSE_PHOTO:
|
|
if (mTakePictureUri.equals(pictureUri)) {
|
|
cropPhoto();
|
|
} else {
|
|
copyAndCropPhoto(pictureUri);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Bitmap getNewUserPhotoBitmap() {
|
|
return mNewUserPhotoBitmap;
|
|
}
|
|
|
|
public Drawable getNewUserPhotoDrawable() {
|
|
return mNewUserPhotoDrawable;
|
|
}
|
|
|
|
private void showUpdatePhotoPopup() {
|
|
final boolean canTakePhoto = canTakePhoto();
|
|
final boolean canChoosePhoto = canChoosePhoto();
|
|
|
|
if (!canTakePhoto && !canChoosePhoto) {
|
|
return;
|
|
}
|
|
|
|
final Context context = mImageView.getContext();
|
|
final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
|
|
|
|
if (canTakePhoto) {
|
|
final String title = context.getString(R.string.user_image_take_photo);
|
|
final Runnable action = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
takePhoto();
|
|
}
|
|
};
|
|
items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
|
|
action));
|
|
}
|
|
|
|
if (canChoosePhoto) {
|
|
final String title = context.getString(R.string.user_image_choose_photo);
|
|
final Runnable action = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
choosePhoto();
|
|
}
|
|
};
|
|
items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
|
|
action));
|
|
}
|
|
|
|
final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
|
|
|
|
listPopupWindow.setAnchorView(mImageView);
|
|
listPopupWindow.setModal(true);
|
|
listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
|
|
listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
|
|
|
|
final int width = Math.max(mImageView.getWidth(), context.getResources()
|
|
.getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
|
|
listPopupWindow.setWidth(width);
|
|
listPopupWindow.setDropDownGravity(Gravity.START);
|
|
|
|
listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
@Override
|
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
listPopupWindow.dismiss();
|
|
final RestrictedMenuItem item =
|
|
(RestrictedMenuItem) parent.getAdapter().getItem(position);
|
|
item.doAction();
|
|
}
|
|
});
|
|
|
|
listPopupWindow.show();
|
|
}
|
|
|
|
private boolean canTakePhoto() {
|
|
return mImageView.getContext().getPackageManager().queryIntentActivities(
|
|
new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
|
|
PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
|
|
}
|
|
|
|
private boolean canChoosePhoto() {
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
intent.setType("image/*");
|
|
return mImageView.getContext().getPackageManager().queryIntentActivities(
|
|
intent, 0).size() > 0;
|
|
}
|
|
|
|
private void takePhoto() {
|
|
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
appendOutputExtra(intent, mTakePictureUri);
|
|
mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
|
|
}
|
|
|
|
private void choosePhoto() {
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
|
|
intent.setType("image/*");
|
|
appendOutputExtra(intent, mTakePictureUri);
|
|
mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
|
|
}
|
|
|
|
private void copyAndCropPhoto(final Uri pictureUri) {
|
|
new AsyncTask<Void, Void, Void>() {
|
|
@Override
|
|
protected Void doInBackground(Void... params) {
|
|
final ContentResolver cr = mContext.getContentResolver();
|
|
try (InputStream in = cr.openInputStream(pictureUri);
|
|
OutputStream out = cr.openOutputStream(mTakePictureUri)) {
|
|
Streams.copy(in, out);
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Failed to copy photo", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Void result) {
|
|
if (!mFragment.isAdded()) return;
|
|
cropPhoto();
|
|
}
|
|
}.execute();
|
|
}
|
|
|
|
private void cropPhoto() {
|
|
// TODO: Use a public intent, when there is one.
|
|
Intent intent = new Intent("com.android.camera.action.CROP");
|
|
intent.setDataAndType(mTakePictureUri, "image/*");
|
|
appendOutputExtra(intent, mCropPictureUri);
|
|
appendCropExtras(intent);
|
|
if (intent.resolveActivity(mContext.getPackageManager()) != null) {
|
|
try {
|
|
StrictMode.disableDeathOnFileUriExposure();
|
|
mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
|
|
} finally {
|
|
StrictMode.enableDeathOnFileUriExposure();
|
|
}
|
|
} else {
|
|
onPhotoCropped(mTakePictureUri, false);
|
|
}
|
|
}
|
|
|
|
private void appendOutputExtra(Intent intent, Uri pictureUri) {
|
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
|
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
|
|
}
|
|
|
|
private void appendCropExtras(Intent intent) {
|
|
intent.putExtra("crop", "true");
|
|
intent.putExtra("scale", true);
|
|
intent.putExtra("scaleUpIfNeeded", true);
|
|
intent.putExtra("aspectX", 1);
|
|
intent.putExtra("aspectY", 1);
|
|
intent.putExtra("outputX", mPhotoSize);
|
|
intent.putExtra("outputY", mPhotoSize);
|
|
}
|
|
|
|
private void onPhotoCropped(final Uri data, final boolean cropped) {
|
|
new AsyncTask<Void, Void, Bitmap>() {
|
|
@Override
|
|
protected Bitmap doInBackground(Void... params) {
|
|
if (cropped) {
|
|
InputStream imageStream = null;
|
|
try {
|
|
imageStream = mContext.getContentResolver()
|
|
.openInputStream(data);
|
|
return BitmapFactory.decodeStream(imageStream);
|
|
} catch (FileNotFoundException fe) {
|
|
Log.w(TAG, "Cannot find image file", fe);
|
|
return null;
|
|
} finally {
|
|
if (imageStream != null) {
|
|
try {
|
|
imageStream.close();
|
|
} catch (IOException ioe) {
|
|
Log.w(TAG, "Cannot close image stream", ioe);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Scale and crop to a square aspect ratio
|
|
Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
|
|
Config.ARGB_8888);
|
|
Canvas canvas = new Canvas(croppedImage);
|
|
Bitmap fullImage = null;
|
|
try {
|
|
InputStream imageStream = mContext.getContentResolver()
|
|
.openInputStream(data);
|
|
fullImage = BitmapFactory.decodeStream(imageStream);
|
|
} catch (FileNotFoundException fe) {
|
|
return null;
|
|
}
|
|
if (fullImage != null) {
|
|
final int squareSize = Math.min(fullImage.getWidth(),
|
|
fullImage.getHeight());
|
|
final int left = (fullImage.getWidth() - squareSize) / 2;
|
|
final int top = (fullImage.getHeight() - squareSize) / 2;
|
|
Rect rectSource = new Rect(left, top,
|
|
left + squareSize, top + squareSize);
|
|
Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize);
|
|
Paint paint = new Paint();
|
|
canvas.drawBitmap(fullImage, rectSource, rectDest, paint);
|
|
return croppedImage;
|
|
} else {
|
|
// Bah! Got nothin.
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Bitmap bitmap) {
|
|
if (bitmap != null) {
|
|
mNewUserPhotoBitmap = bitmap;
|
|
mNewUserPhotoDrawable = CircleFramedDrawable
|
|
.getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
|
|
mImageView.setImageDrawable(mNewUserPhotoDrawable);
|
|
}
|
|
new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete();
|
|
new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete();
|
|
}
|
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
|
|
}
|
|
|
|
private static int getPhotoSize(Context context) {
|
|
Cursor cursor = context.getContentResolver().query(
|
|
DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
|
|
new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
|
|
try {
|
|
cursor.moveToFirst();
|
|
return cursor.getInt(0);
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
private Uri createTempImageUri(Context context, String fileName, boolean purge) {
|
|
final File folder = context.getCacheDir();
|
|
folder.mkdirs();
|
|
final File fullPath = new File(folder, fileName);
|
|
if (purge) {
|
|
fullPath.delete();
|
|
}
|
|
return FileProvider.getUriForFile(context,
|
|
RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, fullPath);
|
|
}
|
|
|
|
File saveNewUserPhotoBitmap() {
|
|
if (mNewUserPhotoBitmap == null) {
|
|
return null;
|
|
}
|
|
try {
|
|
File file = new File(mContext.getCacheDir(), NEW_USER_PHOTO_FILE_NAME);
|
|
OutputStream os = new FileOutputStream(file);
|
|
mNewUserPhotoBitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
|
|
os.flush();
|
|
os.close();
|
|
return file;
|
|
} catch (IOException e) {
|
|
Log.e(TAG, "Cannot create temp file", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Bitmap loadNewUserPhotoBitmap(File file) {
|
|
return BitmapFactory.decodeFile(file.getAbsolutePath());
|
|
}
|
|
|
|
void removeNewUserPhotoBitmapFile() {
|
|
new File(mContext.getCacheDir(), NEW_USER_PHOTO_FILE_NAME).delete();
|
|
}
|
|
|
|
private static final class RestrictedMenuItem {
|
|
private final Context mContext;
|
|
private final String mTitle;
|
|
private final Runnable mAction;
|
|
private final RestrictedLockUtils.EnforcedAdmin mAdmin;
|
|
// Restriction may be set by system or something else via UserManager.setUserRestriction().
|
|
private final boolean mIsRestrictedByBase;
|
|
|
|
/**
|
|
* The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
|
|
* @param context A context.
|
|
* @param title The title of the menu item.
|
|
* @param restriction The restriction, that if is set, blocks the menu item.
|
|
* @param action The action on menu item click.
|
|
*/
|
|
public RestrictedMenuItem(Context context, String title, String restriction,
|
|
Runnable action) {
|
|
mContext = context;
|
|
mTitle = title;
|
|
mAction = action;
|
|
|
|
final int myUserId = UserHandle.myUserId();
|
|
mAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(context,
|
|
restriction, myUserId);
|
|
mIsRestrictedByBase = RestrictedLockUtils.hasBaseUserRestriction(mContext,
|
|
restriction, myUserId);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return mTitle;
|
|
}
|
|
|
|
final void doAction() {
|
|
if (isRestrictedByBase()) {
|
|
return;
|
|
}
|
|
|
|
if (isRestrictedByAdmin()) {
|
|
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
|
|
return;
|
|
}
|
|
|
|
mAction.run();
|
|
}
|
|
|
|
final boolean isRestrictedByAdmin() {
|
|
return mAdmin != null;
|
|
}
|
|
|
|
final boolean isRestrictedByBase() {
|
|
return mIsRestrictedByBase;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
|
|
* any element can be restricted by admin (profile owner or device owner).
|
|
*/
|
|
private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
|
|
public RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
|
|
super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
|
|
}
|
|
|
|
@Override
|
|
public View getView(int position, View convertView, ViewGroup parent) {
|
|
final View view = super.getView(position, convertView, parent);
|
|
final RestrictedMenuItem item = getItem(position);
|
|
final TextView text = (TextView) view.findViewById(R.id.text);
|
|
final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
|
|
|
|
text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
|
|
image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase() ?
|
|
ImageView.VISIBLE : ImageView.GONE);
|
|
|
|
return view;
|
|
}
|
|
}
|
|
}
|