fix: 引入Settings的Module
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
/**
|
||||
* Preference category that accepts a content description for accessibility.
|
||||
*/
|
||||
public class AccessiblePreferenceCategory extends PreferenceCategory {
|
||||
private String mContentDescription;
|
||||
|
||||
public AccessiblePreferenceCategory(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setContentDescription(String contentDescription) {
|
||||
mContentDescription = contentDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
|
||||
view.itemView.setContentDescription(mContentDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class ActionDisabledByAppOpsDialog extends Activity
|
||||
implements DialogInterface.OnDismissListener {
|
||||
|
||||
private static final String TAG = "ActionDisabledByAppOpsDialog";
|
||||
|
||||
private ActionDisabledByAppOpsHelper mDialogHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDialogHelper = new ActionDisabledByAppOpsHelper(this);
|
||||
mDialogHelper.prepareDialogBuilder()
|
||||
.setOnDismissListener(this)
|
||||
.show();
|
||||
updateAppOps();
|
||||
}
|
||||
|
||||
private void updateAppOps() {
|
||||
final Intent intent = getIntent();
|
||||
final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
|
||||
final int uid = intent.getIntExtra(Intent.EXTRA_UID, android.os.Process.INVALID_UID);
|
||||
getSystemService(AppOpsManager.class)
|
||||
.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
|
||||
uid,
|
||||
packageName,
|
||||
AppOpsManager.MODE_IGNORED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
mDialogHelper.updateDialog();
|
||||
updateAppOps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settingslib.HelpUtils;
|
||||
|
||||
final class ActionDisabledByAppOpsHelper {
|
||||
|
||||
private final ViewGroup mDialogView;
|
||||
private final Activity mActivity;
|
||||
|
||||
ActionDisabledByAppOpsHelper(Activity activity) {
|
||||
mActivity = activity;
|
||||
mDialogView = (ViewGroup) LayoutInflater.from(mActivity).inflate(
|
||||
R.layout.support_details_dialog, null);
|
||||
}
|
||||
|
||||
public AlertDialog.Builder prepareDialogBuilder() {
|
||||
final String helpUrl = mActivity.getString(
|
||||
R.string.help_url_action_disabled_by_restricted_settings);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity)
|
||||
.setPositiveButton(R.string.okay, null)
|
||||
.setView(mDialogView);
|
||||
if (!TextUtils.isEmpty(helpUrl)) {
|
||||
builder.setNeutralButton(R.string.learn_more,
|
||||
(DialogInterface.OnClickListener) (dialog, which) -> {
|
||||
final Intent intent = HelpUtils.getHelpIntent(mActivity,
|
||||
helpUrl, mActivity.getClass().getName());
|
||||
if (intent != null) {
|
||||
mActivity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
initializeDialogViews(mDialogView);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void updateDialog() {
|
||||
initializeDialogViews(mDialogView);
|
||||
}
|
||||
|
||||
private void initializeDialogViews(View root) {
|
||||
setSupportTitle(root);
|
||||
setSupportDetails(root);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setSupportTitle(View root) {
|
||||
final TextView titleView = root.findViewById(R.id.admin_support_dialog_title);
|
||||
if (titleView == null) {
|
||||
return;
|
||||
}
|
||||
titleView.setText(R.string.blocked_by_restricted_settings_title);
|
||||
}
|
||||
|
||||
void setSupportDetails(final View root) {
|
||||
final TextView textView = root.findViewById(R.id.admin_support_msg);
|
||||
textView.setText(R.string.blocked_by_restricted_settings_content);
|
||||
}
|
||||
}
|
||||
487
Settings/src/com/android/settings/ActivityPicker.java
Normal file
487
Settings/src/com/android/settings/ActivityPicker.java
Normal file
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.Intent.ShortcutIconResource;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PaintFlagsDrawFilter;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.PaintDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.app.AlertActivity;
|
||||
import com.android.internal.app.AlertController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a list of all activities matching the incoming
|
||||
* {@link Intent#EXTRA_INTENT} query, along with any injected items.
|
||||
*/
|
||||
public class ActivityPicker extends AlertActivity implements
|
||||
DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
|
||||
|
||||
/**
|
||||
* Adapter of items that are displayed in this dialog.
|
||||
*/
|
||||
private PickAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Base {@link Intent} used when building list.
|
||||
*/
|
||||
private Intent mBaseIntent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
|
||||
// Read base intent from extras, otherwise assume default
|
||||
Parcelable parcel = intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
if (parcel instanceof Intent) {
|
||||
mBaseIntent = (Intent) parcel;
|
||||
mBaseIntent.setFlags(mBaseIntent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
|
||||
} else {
|
||||
mBaseIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
mBaseIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
// Create dialog parameters
|
||||
AlertController.AlertParams params = mAlertParams;
|
||||
params.mOnClickListener = this;
|
||||
params.mOnCancelListener = this;
|
||||
|
||||
// Use custom title if provided, otherwise default window title
|
||||
if (intent.hasExtra(Intent.EXTRA_TITLE)) {
|
||||
params.mTitle = intent.getStringExtra(Intent.EXTRA_TITLE);
|
||||
} else {
|
||||
params.mTitle = getTitle();
|
||||
}
|
||||
|
||||
// Build list adapter of pickable items
|
||||
List<PickAdapter.Item> items = getItems();
|
||||
mAdapter = new PickAdapter(this, items);
|
||||
params.mAdapter = mAdapter;
|
||||
|
||||
setupAlert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicking of dialog item by passing back
|
||||
* {@link #getIntentForPosition(int)} in {@link #setResult(int, Intent)}.
|
||||
*/
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = getIntentForPosition(which);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle canceled dialog by passing back {@link Activity#RESULT_CANCELED}.
|
||||
*/
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the specific {@link Intent} for a given list position. Convenience
|
||||
* method that calls through to {@link PickAdapter.Item#getIntent(Intent)}.
|
||||
*/
|
||||
protected Intent getIntentForPosition(int position) {
|
||||
PickAdapter.Item item = (PickAdapter.Item) mAdapter.getItem(position);
|
||||
return item.getIntent(mBaseIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return list of items to be shown in dialog. Default
|
||||
* implementation mixes activities matching {@link #mBaseIntent} from
|
||||
* {@link #putIntentItems(Intent, List)} with any injected items from
|
||||
* {@link Intent#EXTRA_SHORTCUT_NAME}. Override this method in subclasses to
|
||||
* change the items shown.
|
||||
*/
|
||||
protected List<PickAdapter.Item> getItems() {
|
||||
PackageManager packageManager = getPackageManager();
|
||||
List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>();
|
||||
|
||||
// Add any injected pick items
|
||||
final Intent intent = getIntent();
|
||||
ArrayList<String> labels =
|
||||
intent.getStringArrayListExtra(Intent.EXTRA_SHORTCUT_NAME);
|
||||
ArrayList<ShortcutIconResource> icons =
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
|
||||
|
||||
if (labels != null && icons != null && labels.size() == icons.size()) {
|
||||
for (int i = 0; i < labels.size(); i++) {
|
||||
String label = labels.get(i);
|
||||
Drawable icon = null;
|
||||
|
||||
try {
|
||||
// Try loading icon from requested package
|
||||
ShortcutIconResource iconResource = icons.get(i);
|
||||
Resources res = packageManager.getResourcesForApplication(
|
||||
iconResource.packageName);
|
||||
icon = res.getDrawable(res.getIdentifier(
|
||||
iconResource.resourceName, null, null), null);
|
||||
} catch (NameNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
items.add(new PickAdapter.Item(this, label, icon));
|
||||
}
|
||||
}
|
||||
|
||||
// Add any intent items if base was given
|
||||
if (mBaseIntent != null) {
|
||||
putIntentItems(mBaseIntent, items);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the given list with any activities matching the base {@link Intent}.
|
||||
*/
|
||||
protected void putIntentItems(Intent baseIntent, List<PickAdapter.Item> items) {
|
||||
PackageManager packageManager = getPackageManager();
|
||||
List<ResolveInfo> list = packageManager.queryIntentActivities(baseIntent,
|
||||
0 /* no flags */);
|
||||
Collections.sort(list, new ResolveInfo.DisplayNameComparator(packageManager));
|
||||
|
||||
final int listSize = list.size();
|
||||
for (int i = 0; i < listSize; i++) {
|
||||
ResolveInfo resolveInfo = list.get(i);
|
||||
items.add(new PickAdapter.Item(this, packageManager, resolveInfo));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter which shows the set of activities that can be performed for a
|
||||
* given {@link Intent}.
|
||||
*/
|
||||
protected static class PickAdapter extends BaseAdapter {
|
||||
|
||||
/**
|
||||
* Item that appears in a {@link PickAdapter} list.
|
||||
*/
|
||||
public static class Item implements AppWidgetLoader.LabelledItem {
|
||||
protected static IconResizer sResizer;
|
||||
|
||||
protected IconResizer getResizer(Context context) {
|
||||
if (sResizer == null) {
|
||||
final Resources resources = context.getResources();
|
||||
int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
|
||||
sResizer = new IconResizer(size, size, resources.getDisplayMetrics());
|
||||
}
|
||||
return sResizer;
|
||||
}
|
||||
|
||||
CharSequence label;
|
||||
Drawable icon;
|
||||
String packageName;
|
||||
String className;
|
||||
Bundle extras;
|
||||
|
||||
/**
|
||||
* Create a list item from given label and icon.
|
||||
*/
|
||||
Item(Context context, CharSequence label, Drawable icon) {
|
||||
this.label = label;
|
||||
this.icon = getResizer(context).createIconThumbnail(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list item and fill it with details from the given
|
||||
* {@link ResolveInfo} object.
|
||||
*/
|
||||
Item(Context context, PackageManager pm, ResolveInfo resolveInfo) {
|
||||
label = resolveInfo.loadLabel(pm);
|
||||
if (label == null && resolveInfo.activityInfo != null) {
|
||||
label = resolveInfo.activityInfo.name;
|
||||
}
|
||||
|
||||
icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm));
|
||||
packageName = resolveInfo.activityInfo.applicationInfo.packageName;
|
||||
className = resolveInfo.activityInfo.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link Intent} described by this item. If this item
|
||||
* can't create a valid {@link android.content.ComponentName}, it will return
|
||||
* {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
|
||||
*/
|
||||
Intent getIntent(Intent baseIntent) {
|
||||
Intent intent = new Intent(baseIntent);
|
||||
if (packageName != null && className != null) {
|
||||
// Valid package and class, so fill details as normal intent
|
||||
intent.setClassName(packageName, className);
|
||||
if (extras != null) {
|
||||
intent.putExtras(extras);
|
||||
}
|
||||
} else {
|
||||
// No valid package or class, so treat as shortcut with label
|
||||
intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
public CharSequence getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final List<Item> mItems;
|
||||
|
||||
/**
|
||||
* Create an adapter for the given items.
|
||||
*/
|
||||
public PickAdapter(Context context, List<Item> items) {
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mItems = items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Object getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(R.layout.pick_item, parent, false);
|
||||
}
|
||||
|
||||
Item item = (Item) getItem(position);
|
||||
TextView textView = (TextView) convertView;
|
||||
textView.setText(item.label);
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to resize icons to match default icon size. Code is mostly
|
||||
* borrowed from Launcher.
|
||||
*/
|
||||
private static class IconResizer {
|
||||
private final int mIconWidth;
|
||||
private final int mIconHeight;
|
||||
|
||||
private final DisplayMetrics mMetrics;
|
||||
private final Rect mOldBounds = new Rect();
|
||||
private final Canvas mCanvas = new Canvas();
|
||||
|
||||
public IconResizer(int width, int height, DisplayMetrics metrics) {
|
||||
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
|
||||
Paint.FILTER_BITMAP_FLAG));
|
||||
|
||||
mMetrics = metrics;
|
||||
mIconWidth = width;
|
||||
mIconHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Drawable representing the thumbnail of the specified Drawable.
|
||||
* The size of the thumbnail is defined by the dimension
|
||||
* android.R.dimen.launcher_application_icon_size.
|
||||
*
|
||||
* This method is not thread-safe and should be invoked on the UI thread only.
|
||||
*
|
||||
* @param icon The icon to get a thumbnail of.
|
||||
*
|
||||
* @return A thumbnail for the specified icon or the icon itself if the
|
||||
* thumbnail could not be created.
|
||||
*/
|
||||
public Drawable createIconThumbnail(Drawable icon) {
|
||||
int width = mIconWidth;
|
||||
int height = mIconHeight;
|
||||
|
||||
if (icon == null) {
|
||||
return new EmptyDrawable(width, height);
|
||||
}
|
||||
|
||||
try {
|
||||
if (icon instanceof PaintDrawable) {
|
||||
PaintDrawable painter = (PaintDrawable) icon;
|
||||
painter.setIntrinsicWidth(width);
|
||||
painter.setIntrinsicHeight(height);
|
||||
} else if (icon instanceof BitmapDrawable) {
|
||||
// Ensure the bitmap has a density.
|
||||
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
|
||||
Bitmap bitmap = bitmapDrawable.getBitmap();
|
||||
if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
|
||||
bitmapDrawable.setTargetDensity(mMetrics);
|
||||
}
|
||||
}
|
||||
int iconWidth = icon.getIntrinsicWidth();
|
||||
int iconHeight = icon.getIntrinsicHeight();
|
||||
|
||||
if (iconWidth > 0 && iconHeight > 0) {
|
||||
if (width < iconWidth || height < iconHeight) {
|
||||
final float ratio = (float) iconWidth / iconHeight;
|
||||
|
||||
if (iconWidth > iconHeight) {
|
||||
height = (int) (width / ratio);
|
||||
} else if (iconHeight > iconWidth) {
|
||||
width = (int) (height * ratio);
|
||||
}
|
||||
|
||||
final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
|
||||
Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
|
||||
final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
|
||||
final Canvas canvas = mCanvas;
|
||||
canvas.setBitmap(thumb);
|
||||
// Copy the old bounds to restore them later
|
||||
// If we were to do oldBounds = icon.getBounds(),
|
||||
// the call to setBounds() that follows would
|
||||
// change the same instance and we would lose the
|
||||
// old bounds
|
||||
mOldBounds.set(icon.getBounds());
|
||||
final int x = (mIconWidth - width) / 2;
|
||||
final int y = (mIconHeight - height) / 2;
|
||||
icon.setBounds(x, y, x + width, y + height);
|
||||
icon.draw(canvas);
|
||||
icon.setBounds(mOldBounds);
|
||||
//noinspection deprecation
|
||||
icon = new BitmapDrawable(thumb);
|
||||
((BitmapDrawable) icon).setTargetDensity(mMetrics);
|
||||
canvas.setBitmap(null);
|
||||
} else if (iconWidth < width && iconHeight < height) {
|
||||
final Bitmap.Config c = Bitmap.Config.ARGB_8888;
|
||||
final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
|
||||
final Canvas canvas = mCanvas;
|
||||
canvas.setBitmap(thumb);
|
||||
mOldBounds.set(icon.getBounds());
|
||||
final int x = (width - iconWidth) / 2;
|
||||
final int y = (height - iconHeight) / 2;
|
||||
icon.setBounds(x, y, x + iconWidth, y + iconHeight);
|
||||
icon.draw(canvas);
|
||||
icon.setBounds(mOldBounds);
|
||||
//noinspection deprecation
|
||||
icon = new BitmapDrawable(thumb);
|
||||
((BitmapDrawable) icon).setTargetDensity(mMetrics);
|
||||
canvas.setBitmap(null);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
icon = new EmptyDrawable(width, height);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EmptyDrawable extends Drawable {
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
|
||||
EmptyDrawable(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
217
Settings/src/com/android/settings/AirplaneModeEnabler.java
Normal file
217
Settings/src/com/android/settings/AirplaneModeEnabler.java
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.internal.telephony.flags.Flags;
|
||||
import com.android.settings.network.GlobalSettingsChangeListener;
|
||||
import com.android.settings.network.ProxySubscriptionManager;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.WirelessUtils;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Monitor and update configuration of airplane mode settings
|
||||
*/
|
||||
public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
|
||||
|
||||
private static final String LOG_TAG = "AirplaneModeEnabler";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
|
||||
private OnAirplaneModeChangedListener mOnAirplaneModeChangedListener;
|
||||
|
||||
public interface OnAirplaneModeChangedListener {
|
||||
/**
|
||||
* Called when airplane mode status is changed.
|
||||
*
|
||||
* @param isAirplaneModeOn the airplane mode is on
|
||||
*/
|
||||
void onAirplaneModeChanged(boolean isAirplaneModeOn);
|
||||
}
|
||||
|
||||
private TelephonyManager mTelephonyManager;
|
||||
@VisibleForTesting
|
||||
PhoneStateListener mPhoneStateListener;
|
||||
|
||||
public AirplaneModeEnabler(Context context, OnAirplaneModeChangedListener listener) {
|
||||
super(context, Settings.Global.AIRPLANE_MODE_ON);
|
||||
|
||||
mContext = context;
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
mOnAirplaneModeChangedListener = listener;
|
||||
|
||||
mTelephonyManager = context.getSystemService(TelephonyManager.class);
|
||||
|
||||
mPhoneStateListener = new PhoneStateListener(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void onRadioPowerStateChanged(int state) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "RadioPower: " + state);
|
||||
}
|
||||
onAirplaneModeChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of GlobalSettingsChangeListener.onChanged
|
||||
*/
|
||||
@Override
|
||||
public void onChanged(String field) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Airplane mode configuration update");
|
||||
}
|
||||
onAirplaneModeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to the phone state change
|
||||
*/
|
||||
public void start() {
|
||||
mTelephonyManager.listen(mPhoneStateListener,
|
||||
PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening to the phone state change
|
||||
*/
|
||||
public void stop() {
|
||||
mTelephonyManager.listen(mPhoneStateListener,
|
||||
PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
|
||||
private void setAirplaneModeOn(boolean enabling) {
|
||||
// Change the system setting
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
|
||||
enabling ? 1 : 0);
|
||||
|
||||
// Notify listener the system setting is changed.
|
||||
if (mOnAirplaneModeChangedListener != null) {
|
||||
mOnAirplaneModeChangedListener.onAirplaneModeChanged(enabling);
|
||||
}
|
||||
|
||||
// Post the intent
|
||||
final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||||
intent.putExtra("state", enabling);
|
||||
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we've received confirmation that the airplane mode was set.
|
||||
* TODO: We update the checkbox summary when we get notified
|
||||
* that mobile radio is powered up/down. We should not have dependency
|
||||
* on one radio alone. We need to do the following:
|
||||
* - handle the case of wifi/bluetooth failures
|
||||
* - mobile does not send failure notification, fail on timeout.
|
||||
*/
|
||||
private void onAirplaneModeChanged() {
|
||||
if (mOnAirplaneModeChangedListener != null) {
|
||||
mOnAirplaneModeChangedListener.onAirplaneModeChanged(isAirplaneModeOn());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the status of ECM mode
|
||||
*
|
||||
* @return any subscription within device is under ECM mode
|
||||
*/
|
||||
public boolean isInEcmMode() {
|
||||
if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
|
||||
try {
|
||||
if (mTelephonyManager.getEmergencyCallbackMode()) {
|
||||
return true;
|
||||
}
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Device doesn't support FEATURE_TELEPHONY_CALLING
|
||||
// Ignore exception, device is not in ECM mode.
|
||||
}
|
||||
} else {
|
||||
if (mTelephonyManager.getEmergencyCallbackMode()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final List<SubscriptionInfo> subInfoList =
|
||||
ProxySubscriptionManager.getInstance(mContext).getActiveSubscriptionsInfo();
|
||||
if (subInfoList == null) {
|
||||
return false;
|
||||
}
|
||||
for (SubscriptionInfo subInfo : subInfoList) {
|
||||
final TelephonyManager telephonyManager =
|
||||
mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
|
||||
if (telephonyManager != null) {
|
||||
if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
|
||||
if (telephonyManager.getEmergencyCallbackMode()) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (telephonyManager.getEmergencyCallbackMode()) {
|
||||
return true;
|
||||
}
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Ignore exception, device is not in ECM mode.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setAirplaneMode(boolean isAirplaneModeOn) {
|
||||
if (isInEcmMode()) {
|
||||
// In ECM mode, do not update database at this point
|
||||
Log.d(LOG_TAG, "ECM airplane mode=" + isAirplaneModeOn);
|
||||
} else {
|
||||
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AIRPLANE_TOGGLE,
|
||||
isAirplaneModeOn);
|
||||
setAirplaneModeOn(isAirplaneModeOn);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAirplaneModeInECM(boolean isECMExit, boolean isAirplaneModeOn) {
|
||||
Log.d(LOG_TAG, "Exist ECM=" + isECMExit + ", with airplane mode=" + isAirplaneModeOn);
|
||||
if (isECMExit) {
|
||||
// update database based on the current checkbox state
|
||||
setAirplaneModeOn(isAirplaneModeOn);
|
||||
} else {
|
||||
// update summary
|
||||
onAirplaneModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAirplaneModeOn() {
|
||||
return WirelessUtils.isAirplaneModeOn(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.utils.VoiceSettingsActivity;
|
||||
|
||||
/**
|
||||
* Activity for modifying the {@link Settings.Global#AIRPLANE_MODE_ON AIRPLANE_MODE_ON}
|
||||
* setting using the Voice Interaction API.
|
||||
*/
|
||||
public class AirplaneModeVoiceActivity extends VoiceSettingsActivity {
|
||||
private static final String TAG = "AirplaneModeVoiceActivity";
|
||||
|
||||
protected boolean onVoiceSettingInteraction(Intent intent) {
|
||||
if (intent.hasExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED)) {
|
||||
ConnectivityManager mgr = (ConnectivityManager) getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
mgr.setAirplaneMode(intent.getBooleanExtra(
|
||||
Settings.EXTRA_AIRPLANE_MODE_ENABLED, false));
|
||||
} else {
|
||||
Log.v(TAG, "Missing airplane mode extra");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.internal.app.AlertActivity;
|
||||
import com.android.internal.app.AlertController;
|
||||
|
||||
/**
|
||||
* This activity is displayed when an app launches the BIND_APPWIDGET intent. This allows apps
|
||||
* that don't have the BIND_APPWIDGET permission to bind specific widgets.
|
||||
*/
|
||||
public class AllowBindAppWidgetActivity extends AlertActivity implements
|
||||
DialogInterface.OnClickListener {
|
||||
|
||||
private CheckBox mAlwaysUse;
|
||||
private int mAppWidgetId;
|
||||
private Bundle mBindOptions;
|
||||
private UserHandle mProfile;
|
||||
private ComponentName mComponentName;
|
||||
private String mCallingPackage;
|
||||
private AppWidgetManager mAppWidgetManager;
|
||||
|
||||
// Indicates whether this activity was closed because of a click
|
||||
private boolean mClicked;
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mClicked = true;
|
||||
if (which == AlertDialog.BUTTON_POSITIVE) {
|
||||
if (mAppWidgetId != -1 && mComponentName != null && mCallingPackage != null) {
|
||||
try {
|
||||
final boolean bound = mAppWidgetManager.bindAppWidgetIdIfAllowed(mAppWidgetId,
|
||||
mProfile, mComponentName, mBindOptions);
|
||||
if (bound) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.v("BIND_APPWIDGET", "Error binding widget with id "
|
||||
+ mAppWidgetId + " and component " + mComponentName);
|
||||
}
|
||||
|
||||
final boolean alwaysAllowBind = mAlwaysUse.isChecked();
|
||||
if (alwaysAllowBind != mAppWidgetManager.hasBindAppWidgetPermission(
|
||||
mCallingPackage)) {
|
||||
mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage,
|
||||
alwaysAllowBind);
|
||||
}
|
||||
}
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (!mClicked) { // RESULT_CANCELED
|
||||
finish();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
|
||||
setResult(RESULT_CANCELED); // By default, set the result to cancelled
|
||||
Intent intent = getIntent();
|
||||
CharSequence label = "";
|
||||
if (intent != null) {
|
||||
try {
|
||||
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
|
||||
mProfile = intent.getParcelableExtra(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
|
||||
if (mProfile == null) {
|
||||
mProfile = android.os.Process.myUserHandle();
|
||||
}
|
||||
mComponentName =
|
||||
intent.getParcelableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER);
|
||||
mBindOptions =
|
||||
intent.getParcelableExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
|
||||
mCallingPackage = getCallingPackage();
|
||||
PackageManager pm = getPackageManager();
|
||||
ApplicationInfo ai = pm.getApplicationInfo(mCallingPackage, 0);
|
||||
label = pm.getApplicationLabel(ai);
|
||||
} catch (Exception e) {
|
||||
mAppWidgetId = -1;
|
||||
mComponentName = null;
|
||||
mCallingPackage = null;
|
||||
Log.v("BIND_APPWIDGET", "Error getting parameters");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
mAppWidgetManager = AppWidgetManager.getInstance(this);
|
||||
final String widgetLabel = getWidgetLabel();
|
||||
AlertController.AlertParams ap = mAlertParams;
|
||||
ap.mTitle = getString(R.string.allow_bind_app_widget_activity_allow_bind_title);
|
||||
ap.mMessage = getString(R.string.allow_bind_app_widget_activity_allow_bind, label,
|
||||
widgetLabel);
|
||||
ap.mPositiveButtonText = getString(R.string.create);
|
||||
ap.mNegativeButtonText = getString(android.R.string.cancel);
|
||||
ap.mPositiveButtonListener = this;
|
||||
ap.mNegativeButtonListener = this;
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
|
||||
mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
|
||||
mAlwaysUse.setText(
|
||||
getString(R.string.allow_bind_app_widget_activity_always_allow_bind, label));
|
||||
|
||||
mAlwaysUse.setPadding(mAlwaysUse.getPaddingLeft(),
|
||||
mAlwaysUse.getPaddingTop(),
|
||||
mAlwaysUse.getPaddingRight(),
|
||||
(int) (mAlwaysUse.getPaddingBottom() +
|
||||
getResources().getDimension(
|
||||
R.dimen.bind_app_widget_dialog_checkbox_bottom_padding)));
|
||||
|
||||
mAlwaysUse.setChecked(mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage,
|
||||
mProfile.getIdentifier()));
|
||||
|
||||
setupAlert();
|
||||
}
|
||||
|
||||
private String getWidgetLabel() {
|
||||
String label = "";
|
||||
for (AppWidgetProviderInfo providerInfo : mAppWidgetManager.getInstalledProviders()) {
|
||||
if (providerInfo.provider.equals(mComponentName)) {
|
||||
label = providerInfo.loadLabel(getPackageManager());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
183
Settings/src/com/android/settings/AppWidgetLoader.java
Normal file
183
Settings/src/com/android/settings/AppWidgetLoader.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AppWidgetLoader<Item extends AppWidgetLoader.LabelledItem> {
|
||||
private static final String TAG = "AppWidgetAdapter";
|
||||
private static final boolean LOGD = AppWidgetPickActivity.LOGD;
|
||||
|
||||
private Context mContext;
|
||||
private AppWidgetManager mAppWidgetManager;
|
||||
ItemConstructor<Item> mItemConstructor;
|
||||
|
||||
interface LabelledItem {
|
||||
CharSequence getLabel();
|
||||
}
|
||||
|
||||
public AppWidgetLoader(Context context, AppWidgetManager appWidgetManager,
|
||||
ItemConstructor<Item> itemConstructor) {
|
||||
mContext = context;
|
||||
mAppWidgetManager = appWidgetManager;
|
||||
mItemConstructor = itemConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create list entries for any custom widgets requested through
|
||||
* {@link AppWidgetManager#EXTRA_CUSTOM_INFO}.
|
||||
*/
|
||||
void putCustomAppWidgets(List<Item> items, Intent intent) {
|
||||
// get and validate the extras they gave us
|
||||
ArrayList<AppWidgetProviderInfo> customInfo = null;
|
||||
ArrayList<Bundle> customExtras = null;
|
||||
try_custom_items: {
|
||||
customInfo = intent.getParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO);
|
||||
if (customInfo == null || customInfo.size() == 0) {
|
||||
Log.i(TAG, "EXTRA_CUSTOM_INFO not present.");
|
||||
break try_custom_items;
|
||||
}
|
||||
|
||||
int customInfoSize = customInfo.size();
|
||||
for (int i=0; i<customInfoSize; i++) {
|
||||
Parcelable p = customInfo.get(i);
|
||||
if (p == null || !(p instanceof AppWidgetProviderInfo)) {
|
||||
customInfo = null;
|
||||
Log.e(TAG, "error using EXTRA_CUSTOM_INFO index=" + i);
|
||||
break try_custom_items;
|
||||
}
|
||||
}
|
||||
|
||||
customExtras = intent.getParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS);
|
||||
if (customExtras == null) {
|
||||
customInfo = null;
|
||||
Log.e(TAG, "EXTRA_CUSTOM_INFO without EXTRA_CUSTOM_EXTRAS");
|
||||
break try_custom_items;
|
||||
}
|
||||
|
||||
int customExtrasSize = customExtras.size();
|
||||
if (customInfoSize != customExtrasSize) {
|
||||
customInfo = null;
|
||||
customExtras = null;
|
||||
Log.e(TAG, "list size mismatch: EXTRA_CUSTOM_INFO: " + customInfoSize
|
||||
+ " EXTRA_CUSTOM_EXTRAS: " + customExtrasSize);
|
||||
break try_custom_items;
|
||||
}
|
||||
|
||||
|
||||
for (int i=0; i<customExtrasSize; i++) {
|
||||
Parcelable p = customExtras.get(i);
|
||||
if (p == null || !(p instanceof Bundle)) {
|
||||
customInfo = null;
|
||||
customExtras = null;
|
||||
Log.e(TAG, "error using EXTRA_CUSTOM_EXTRAS index=" + i);
|
||||
break try_custom_items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOGD) Log.d(TAG, "Using " + customInfo.size() + " custom items");
|
||||
putAppWidgetItems(customInfo, customExtras, items, 0, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create list entries for the given {@link AppWidgetProviderInfo} widgets,
|
||||
* inserting extras if provided.
|
||||
*/
|
||||
void putAppWidgetItems(List<AppWidgetProviderInfo> appWidgets,
|
||||
List<Bundle> customExtras, List<Item> items, int categoryFilter,
|
||||
boolean ignoreFilter) {
|
||||
if (appWidgets == null) return;
|
||||
final int size = appWidgets.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
AppWidgetProviderInfo info = appWidgets.get(i);
|
||||
|
||||
// We remove any widgets whose category isn't included in the filter
|
||||
if (!ignoreFilter && (info.widgetCategory & categoryFilter) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Item item = mItemConstructor.createItem(mContext, info,
|
||||
customExtras != null ? customExtras.get(i) : null);
|
||||
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemConstructor<Item> {
|
||||
Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build and return list of items to be shown in dialog. This will mix both
|
||||
* installed {@link AppWidgetProviderInfo} and those provided through
|
||||
* {@link AppWidgetManager#EXTRA_CUSTOM_INFO}, sorting them alphabetically.
|
||||
*/
|
||||
protected List<Item> getItems(Intent intent) {
|
||||
boolean sortCustomAppWidgets =
|
||||
intent.getBooleanExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, true);
|
||||
|
||||
List<Item> items = new ArrayList<Item>();
|
||||
|
||||
// Default category is home screen
|
||||
int categoryFilter = intent.getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
|
||||
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
|
||||
|
||||
putInstalledAppWidgets(items, categoryFilter);
|
||||
|
||||
// Sort all items together by label
|
||||
if (sortCustomAppWidgets) {
|
||||
putCustomAppWidgets(items, intent);
|
||||
}
|
||||
Collections.sort(items, new Comparator<Item>() {
|
||||
Collator mCollator = Collator.getInstance();
|
||||
|
||||
public int compare(Item lhs, Item rhs) {
|
||||
return mCollator.compare(lhs.getLabel(), rhs.getLabel());
|
||||
}
|
||||
});
|
||||
if (!sortCustomAppWidgets) {
|
||||
List<Item> customItems = new ArrayList<Item>();
|
||||
putCustomAppWidgets(customItems, intent);
|
||||
items.addAll(customItems);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create list entries for installed {@link AppWidgetProviderInfo} widgets.
|
||||
*/
|
||||
void putInstalledAppWidgets(List<Item> items, int categoryFilter) {
|
||||
List<AppWidgetProviderInfo> installed =
|
||||
mAppWidgetManager.getInstalledProviders(categoryFilter);
|
||||
putAppWidgetItems(installed, null, items, categoryFilter, false);
|
||||
}
|
||||
}
|
||||
188
Settings/src/com/android/settings/AppWidgetPickActivity.java
Normal file
188
Settings/src/com/android/settings/AppWidgetPickActivity.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.ActivityPicker.PickAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a list of {@link AppWidgetProviderInfo} widgets, along with any
|
||||
* injected special widgets specified through
|
||||
* {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and
|
||||
* {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}.
|
||||
* <p>
|
||||
* When an installed {@link AppWidgetProviderInfo} is selected, this activity
|
||||
* will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID},
|
||||
* otherwise it will return the requested extras.
|
||||
*/
|
||||
public class AppWidgetPickActivity extends ActivityPicker
|
||||
implements AppWidgetLoader.ItemConstructor<PickAdapter.Item>{
|
||||
private static final String TAG = "AppWidgetPickActivity";
|
||||
static final boolean LOGD = false;
|
||||
|
||||
List<PickAdapter.Item> mItems;
|
||||
|
||||
/**
|
||||
* The allocated {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that this
|
||||
* activity is binding.
|
||||
*/
|
||||
private int mAppWidgetId;
|
||||
private AppWidgetLoader<PickAdapter.Item> mAppWidgetLoader;
|
||||
private AppWidgetManager mAppWidgetManager;
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
mPackageManager = getPackageManager();
|
||||
mAppWidgetManager = AppWidgetManager.getInstance(this);
|
||||
mAppWidgetLoader = new AppWidgetLoader<PickAdapter.Item>
|
||||
(this, mAppWidgetManager, this);
|
||||
|
||||
super.onCreate(icicle);
|
||||
|
||||
// Set default return data
|
||||
setResultData(RESULT_CANCELED, null);
|
||||
|
||||
// Read the appWidgetId passed our direction, otherwise bail if not found
|
||||
final Intent intent = getIntent();
|
||||
if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
|
||||
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return list of items to be shown in dialog. This will mix both
|
||||
* installed {@link AppWidgetProviderInfo} and those provided through
|
||||
* {@link AppWidgetManager#EXTRA_CUSTOM_INFO}, sorting them alphabetically.
|
||||
*/
|
||||
@Override
|
||||
protected List<PickAdapter.Item> getItems() {
|
||||
mItems = mAppWidgetLoader.getItems(getIntent());
|
||||
return mItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickAdapter.Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) {
|
||||
CharSequence label = info.label;
|
||||
Drawable icon = null;
|
||||
|
||||
if (info.icon != 0) {
|
||||
try {
|
||||
final Resources res = context.getResources();
|
||||
final int density = res.getDisplayMetrics().densityDpi;
|
||||
int iconDensity;
|
||||
switch (density) {
|
||||
case DisplayMetrics.DENSITY_MEDIUM:
|
||||
iconDensity = DisplayMetrics.DENSITY_LOW;
|
||||
case DisplayMetrics.DENSITY_TV:
|
||||
iconDensity = DisplayMetrics.DENSITY_MEDIUM;
|
||||
case DisplayMetrics.DENSITY_HIGH:
|
||||
iconDensity = DisplayMetrics.DENSITY_MEDIUM;
|
||||
case DisplayMetrics.DENSITY_XHIGH:
|
||||
iconDensity = DisplayMetrics.DENSITY_HIGH;
|
||||
case DisplayMetrics.DENSITY_XXHIGH:
|
||||
iconDensity = DisplayMetrics.DENSITY_XHIGH;
|
||||
default:
|
||||
// The density is some abnormal value. Return some other
|
||||
// abnormal value that is a reasonable scaling of it.
|
||||
iconDensity = (int)((density*0.75f)+.5f);
|
||||
}
|
||||
Resources packageResources = mPackageManager.
|
||||
getResourcesForApplication(info.provider.getPackageName());
|
||||
icon = packageResources.getDrawableForDensity(info.icon, iconDensity);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
|
||||
+ " for provider: " + info.provider);
|
||||
}
|
||||
if (icon == null) {
|
||||
Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
|
||||
+ " for provider: " + info.provider);
|
||||
}
|
||||
}
|
||||
|
||||
PickAdapter.Item item = new PickAdapter.Item(context, label, icon);
|
||||
item.packageName = info.provider.getPackageName();
|
||||
item.className = info.provider.getClassName();
|
||||
item.extras = extras;
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = getIntentForPosition(which);
|
||||
PickAdapter.Item item = mItems.get(which);
|
||||
|
||||
int result;
|
||||
if (item.extras != null) {
|
||||
// If these extras are present it's because this entry is custom.
|
||||
// Don't try to bind it, just pass it back to the app.
|
||||
setResultData(RESULT_OK, intent);
|
||||
} else {
|
||||
try {
|
||||
Bundle options = null;
|
||||
if (intent.getExtras() != null) {
|
||||
options = intent.getExtras().getBundle(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
|
||||
}
|
||||
mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent(), options);
|
||||
result = RESULT_OK;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This is thrown if they're already bound, or otherwise somehow
|
||||
// bogus. Set the result to canceled, and exit. The app *should*
|
||||
// clean up at this point. We could pass the error along, but
|
||||
// it's not clear that that's useful -- the widget will simply not
|
||||
// appear.
|
||||
result = RESULT_CANCELED;
|
||||
}
|
||||
setResultData(result, null);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for setting the result code and intent. This method
|
||||
* correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that
|
||||
* most hosts expect returned.
|
||||
*/
|
||||
void setResultData(int code, Intent intent) {
|
||||
Intent result = intent != null ? intent : new Intent();
|
||||
result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
|
||||
setResult(code, result);
|
||||
}
|
||||
}
|
||||
66
Settings/src/com/android/settings/AsyncTaskSidecar.java
Normal file
66
Settings/src/com/android/settings/AsyncTaskSidecar.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.settings;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/** A {@link SidecarFragment} which uses an {@link AsyncTask} to perform background work. */
|
||||
public abstract class AsyncTaskSidecar<Param, Result> extends SidecarFragment {
|
||||
|
||||
private Future<Result> mAsyncTask;
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mAsyncTask != null) {
|
||||
mAsyncTask.cancel(true /* mayInterruptIfRunning */);
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the background task.
|
||||
*
|
||||
* @param param parameters passed in from {@link #run}
|
||||
*/
|
||||
protected abstract Result doInBackground(@Nullable Param param);
|
||||
|
||||
/** Handles the background task's result. */
|
||||
protected void onPostExecute(Result result) {}
|
||||
|
||||
/** Runs the sidecar and sets the state to RUNNING. */
|
||||
public void run(@Nullable final Param param) {
|
||||
setState(State.RUNNING, Substate.UNUSED);
|
||||
|
||||
if (mAsyncTask != null) {
|
||||
mAsyncTask.cancel(true /* mayInterruptIfRunning */);
|
||||
}
|
||||
|
||||
mAsyncTask =
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
Result result = doInBackground(param);
|
||||
ThreadUtils.postOnMainThread(() -> onPostExecute(result));
|
||||
});
|
||||
}
|
||||
}
|
||||
37
Settings/src/com/android/settings/BrightnessPreference.java
Normal file
37
Settings/src/com/android/settings/BrightnessPreference.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
public class BrightnessPreference extends Preference {
|
||||
|
||||
public BrightnessPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
getContext().startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
|
||||
UserHandle.CURRENT_OR_SELF);
|
||||
}
|
||||
}
|
||||
109
Settings/src/com/android/settings/BugreportPreference.java
Normal file
109
Settings/src/com/android/settings/BugreportPreference.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.RemoteException;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.CustomDialogPreferenceCompat;
|
||||
|
||||
public class BugreportPreference extends CustomDialogPreferenceCompat {
|
||||
|
||||
private static final String TAG = "BugreportPreference";
|
||||
|
||||
private CheckedTextView mInteractiveTitle;
|
||||
private TextView mInteractiveSummary;
|
||||
private CheckedTextView mFullTitle;
|
||||
private TextView mFullSummary;
|
||||
|
||||
public BugreportPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(Builder builder,
|
||||
DialogInterface.OnClickListener listener) {
|
||||
super.onPrepareDialogBuilder(builder, listener);
|
||||
|
||||
final View dialogView = View.inflate(getContext(), R.layout.bugreport_options_dialog, null);
|
||||
mInteractiveTitle = (CheckedTextView) dialogView
|
||||
.findViewById(R.id.bugreport_option_interactive_title);
|
||||
mInteractiveSummary = (TextView) dialogView
|
||||
.findViewById(R.id.bugreport_option_interactive_summary);
|
||||
mFullTitle = (CheckedTextView) dialogView.findViewById(R.id.bugreport_option_full_title);
|
||||
mFullSummary = (TextView) dialogView.findViewById(R.id.bugreport_option_full_summary);
|
||||
final View.OnClickListener l = new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == mFullTitle || v == mFullSummary) {
|
||||
mInteractiveTitle.setChecked(false);
|
||||
mFullTitle.setChecked(true);
|
||||
}
|
||||
if (v == mInteractiveTitle || v == mInteractiveSummary) {
|
||||
mInteractiveTitle.setChecked(true);
|
||||
mFullTitle.setChecked(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
mInteractiveTitle.setOnClickListener(l);
|
||||
mFullTitle.setOnClickListener(l);
|
||||
mInteractiveSummary.setOnClickListener(l);
|
||||
mFullSummary.setOnClickListener(l);
|
||||
|
||||
builder.setPositiveButton(com.android.internal.R.string.report, listener);
|
||||
builder.setView(dialogView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
|
||||
final Context context = getContext();
|
||||
if (mFullTitle.isChecked()) {
|
||||
Log.v(TAG, "Taking full bugreport right away");
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context,
|
||||
SettingsEnums.ACTION_BUGREPORT_FROM_SETTINGS_FULL);
|
||||
try {
|
||||
ActivityManager.getService().requestFullBugReport();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "error taking bugreport (bugreportType=Full)", e);
|
||||
}
|
||||
} else {
|
||||
Log.v(TAG, "Taking interactive bugreport right away");
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context,
|
||||
SettingsEnums.ACTION_BUGREPORT_FROM_SETTINGS_INTERACTIVE);
|
||||
try {
|
||||
ActivityManager.getService().requestInteractiveBugReport();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "error taking bugreport (bugreportType=Interactive)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Settings/src/com/android/settings/ButtonBarHandler.java
Normal file
27
Settings/src/com/android/settings/ButtonBarHandler.java
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import android.widget.Button;
|
||||
|
||||
/**
|
||||
* Interface letting {@link SettingsPreferenceFragment} access to bottom bar inside
|
||||
* {@link SettingsActivity}.
|
||||
*/
|
||||
public interface ButtonBarHandler {
|
||||
public boolean hasNextButton();
|
||||
public Button getNextButton();
|
||||
}
|
||||
71
Settings/src/com/android/settings/CancellablePreference.java
Normal file
71
Settings/src/com/android/settings/CancellablePreference.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
public class CancellablePreference extends Preference implements OnClickListener {
|
||||
|
||||
private boolean mCancellable;
|
||||
private OnCancelListener mListener;
|
||||
|
||||
public CancellablePreference(Context context) {
|
||||
super(context);
|
||||
setWidgetLayoutResource(R.layout.cancel_pref_widget);
|
||||
}
|
||||
|
||||
public CancellablePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setWidgetLayoutResource(R.layout.cancel_pref_widget);
|
||||
}
|
||||
|
||||
public void setCancellable(boolean isCancellable) {
|
||||
mCancellable = isCancellable;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public void setOnCancelListener(OnCancelListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
|
||||
ImageView cancel = (ImageView) view.findViewById(R.id.cancel);
|
||||
cancel.setVisibility(mCancellable ? View.VISIBLE : View.INVISIBLE);
|
||||
cancel.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mListener != null) {
|
||||
mListener.onCancel(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCancelListener {
|
||||
void onCancel(CancellablePreference preference);
|
||||
}
|
||||
|
||||
}
|
||||
35
Settings/src/com/android/settings/ChangeIds.java
Normal file
35
Settings/src/com/android/settings/ChangeIds.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
import android.compat.annotation.ChangeId;
|
||||
import android.compat.annotation.LoggingOnly;
|
||||
|
||||
/**
|
||||
* All the {@link ChangeId} used for Settings App.
|
||||
*/
|
||||
public class ChangeIds {
|
||||
/**
|
||||
* Intents with action {@code android.settings.MANAGE_APP_OVERLAY_PERMISSION}
|
||||
* and data URI scheme {@code package} don't go to the app-specific screen for managing the
|
||||
* permission anymore. Instead, they redirect to this screen for managing all the apps that have
|
||||
* requested such permission.
|
||||
*/
|
||||
@ChangeId
|
||||
@LoggingOnly
|
||||
public static final long CHANGE_RESTRICT_SAW_INTENT = 135920175L;
|
||||
}
|
||||
72
Settings/src/com/android/settings/CheckableLinearLayout.java
Normal file
72
Settings/src/com/android/settings/CheckableLinearLayout.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class CheckableLinearLayout extends LinearLayout implements Checkable {
|
||||
|
||||
private boolean mChecked;
|
||||
private float mDisabledAlpha;
|
||||
|
||||
public CheckableLinearLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
TypedValue alpha = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
|
||||
mDisabledAlpha = alpha.getFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
final int N = getChildCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
getChildAt(i).setAlpha(enabled ? 1 : mDisabledAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
mChecked = checked;
|
||||
updateChecked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return mChecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
setChecked(!mChecked);
|
||||
}
|
||||
|
||||
private void updateChecked() {
|
||||
final int N = getChildCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child instanceof Checkable) {
|
||||
((Checkable) child).setChecked(mChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
227
Settings/src/com/android/settings/CustomListPreference.java
Normal file
227
Settings/src/com/android/settings/CustomListPreference.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.ListPreferenceDialogFragmentCompat;
|
||||
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
public class CustomListPreference extends ListPreference {
|
||||
|
||||
public CustomListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CustomListPreference(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
protected void onPrepareDialogBuilder(Builder builder,
|
||||
DialogInterface.OnClickListener listener) {
|
||||
}
|
||||
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
}
|
||||
|
||||
protected void onDialogCreated(Dialog dialog) {
|
||||
}
|
||||
|
||||
protected boolean isAutoClosePreference() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user is about to choose the given value, to determine if we
|
||||
* should show a confirmation dialog.
|
||||
*
|
||||
* @param value the value the user is about to choose
|
||||
* @return the message to show in a confirmation dialog, or {@code null} to
|
||||
* not request confirmation
|
||||
*/
|
||||
protected CharSequence getConfirmationMessage(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onDialogStateRestored(Dialog dialog, Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
public static class CustomListPreferenceDialogFragment extends
|
||||
ListPreferenceDialogFragmentCompat {
|
||||
|
||||
private static final java.lang.String KEY_CLICKED_ENTRY_INDEX
|
||||
= "settings.CustomListPrefDialog.KEY_CLICKED_ENTRY_INDEX";
|
||||
|
||||
private int mClickedDialogEntryIndex;
|
||||
|
||||
public static ListPreferenceDialogFragmentCompat newInstance(String key) {
|
||||
final ListPreferenceDialogFragmentCompat fragment =
|
||||
new CustomListPreferenceDialogFragment();
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private CustomListPreference getCustomizablePreference() {
|
||||
return (CustomListPreference) getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
mClickedDialogEntryIndex = getCustomizablePreference()
|
||||
.findIndexOfValue(getCustomizablePreference().getValue());
|
||||
getCustomizablePreference().onPrepareDialogBuilder(builder, getOnItemClickListener());
|
||||
if (!getCustomizablePreference().isAutoClosePreference()) {
|
||||
builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onItemChosen();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mClickedDialogEntryIndex = savedInstanceState.getInt(KEY_CLICKED_ENTRY_INDEX,
|
||||
mClickedDialogEntryIndex);
|
||||
}
|
||||
getCustomizablePreference().onDialogCreated(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(KEY_CLICKED_ENTRY_INDEX, mClickedDialogEntryIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
getCustomizablePreference().onDialogStateRestored(getDialog(), savedInstanceState);
|
||||
}
|
||||
|
||||
protected DialogInterface.OnClickListener getOnItemClickListener() {
|
||||
return new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setClickedDialogEntryIndex(which);
|
||||
if (getCustomizablePreference().isAutoClosePreference()) {
|
||||
onItemChosen();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void setClickedDialogEntryIndex(int which) {
|
||||
mClickedDialogEntryIndex = which;
|
||||
}
|
||||
|
||||
private String getValue() {
|
||||
final ListPreference preference = getCustomizablePreference();
|
||||
if (mClickedDialogEntryIndex >= 0 && preference.getEntryValues() != null) {
|
||||
return preference.getEntryValues()[mClickedDialogEntryIndex].toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user has made a concrete item choice, but we might need
|
||||
* to make a quick detour to confirm that choice with a second dialog.
|
||||
*/
|
||||
protected void onItemChosen() {
|
||||
final CharSequence message = getCustomizablePreference()
|
||||
.getConfirmationMessage(getValue());
|
||||
if (message != null) {
|
||||
final Fragment f = new ConfirmDialogFragment();
|
||||
final Bundle args = new Bundle();
|
||||
args.putCharSequence(Intent.EXTRA_TEXT, message);
|
||||
f.setArguments(args);
|
||||
f.setTargetFragment(CustomListPreferenceDialogFragment.this, 0);
|
||||
final FragmentTransaction ft = getFragmentManager().beginTransaction();
|
||||
ft.add(f, getTag() + "-Confirm");
|
||||
ft.commitAllowingStateLoss();
|
||||
} else {
|
||||
onItemConfirmed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user has made a concrete item choice and we've fully
|
||||
* confirmed they want to move forward (if we took a detour above).
|
||||
*/
|
||||
protected void onItemConfirmed() {
|
||||
onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
|
||||
getDialog().dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
getCustomizablePreference().onDialogClosed(positiveResult);
|
||||
final ListPreference preference = getCustomizablePreference();
|
||||
final String value = getValue();
|
||||
if (positiveResult && value != null) {
|
||||
if (preference.callChangeListener(value)) {
|
||||
preference.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConfirmDialogFragment extends InstrumentedDialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new Builder(getActivity())
|
||||
.setMessage(getArguments().getCharSequence(Intent.EXTRA_TEXT))
|
||||
.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final Fragment f = getTargetFragment();
|
||||
if (f != null) {
|
||||
((CustomListPreferenceDialogFragment) f).onItemConfirmed();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_CUSTOM_LIST_CONFIRMATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
public class DefaultRingtonePreference extends RingtonePreference {
|
||||
private static final String TAG = "DefaultRingtonePreference";
|
||||
|
||||
public DefaultRingtonePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
|
||||
super.onPrepareRingtonePickerIntent(ringtonePickerIntent);
|
||||
|
||||
/*
|
||||
* Since this preference is for choosing the default ringtone, it
|
||||
* doesn't make sense to show a 'Default' item.
|
||||
*/
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveRingtone(Uri ringtoneUri) {
|
||||
if (ringtoneUri == null) {
|
||||
setActualDefaultRingtoneUri(ringtoneUri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidRingtoneUri(ringtoneUri)) {
|
||||
Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
|
||||
+ " ignored: invalid ringtone Uri");
|
||||
return;
|
||||
}
|
||||
|
||||
setActualDefaultRingtoneUri(ringtoneUri);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setActualDefaultRingtoneUri(Uri ringtoneUri) {
|
||||
RingtoneManager.setActualDefaultRingtoneUri(mUserContext, getRingtoneType(), ringtoneUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri onRestoreRingtone() {
|
||||
return RingtoneManager.getActualDefaultRingtoneUri(mUserContext, getRingtoneType());
|
||||
}
|
||||
|
||||
}
|
||||
31
Settings/src/com/android/settings/DialogCreatable.java
Normal file
31
Settings/src/com/android/settings/DialogCreatable.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
import android.app.Dialog;
|
||||
|
||||
/**
|
||||
* Letting the class, assumed to be Fragment, create a Dialog on it. Should be useful
|
||||
* you want to utilize some capability in {@link SettingsPreferenceFragment} but don't want
|
||||
* the class inherit the class itself (See {@link ProxySelector} for example).
|
||||
*/
|
||||
public interface DialogCreatable {
|
||||
|
||||
Dialog onCreateDialog(int dialogId);
|
||||
|
||||
int getDialogMetricsCategory(int dialogId);
|
||||
}
|
||||
95
Settings/src/com/android/settings/DisplaySettings.java
Normal file
95
Settings/src/com/android/settings/DisplaySettings.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.display.BrightnessLevelPreferenceController;
|
||||
import com.android.settings.display.CameraGesturePreferenceController;
|
||||
import com.android.settings.display.LiftToWakePreferenceController;
|
||||
import com.android.settings.display.ShowOperatorNamePreferenceController;
|
||||
import com.android.settings.display.TapToWakePreferenceController;
|
||||
import com.android.settings.display.ThemePreferenceController;
|
||||
import com.android.settings.display.VrDisplayPreferenceController;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public class DisplaySettings extends DashboardFragment {
|
||||
private static final String TAG = "DisplaySettings";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DISPLAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.display_settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
|
||||
return buildPreferenceControllers(context, getSettingsLifecycle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return R.string.help_uri_display;
|
||||
}
|
||||
|
||||
private static List<AbstractPreferenceController> buildPreferenceControllers(
|
||||
Context context, Lifecycle lifecycle) {
|
||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
||||
controllers.add(new CameraGesturePreferenceController(context));
|
||||
controllers.add(new LiftToWakePreferenceController(context));
|
||||
controllers.add(new TapToWakePreferenceController(context));
|
||||
controllers.add(new VrDisplayPreferenceController(context));
|
||||
controllers.add(new ShowOperatorNamePreferenceController(context));
|
||||
controllers.add(new ThemePreferenceController(context));
|
||||
controllers.add(new BrightnessLevelPreferenceController(context, lifecycle));
|
||||
return controllers;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.display_settings) {
|
||||
|
||||
@Override
|
||||
public List<AbstractPreferenceController> createPreferenceControllers(
|
||||
Context context) {
|
||||
return buildPreferenceControllers(context, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
83
Settings/src/com/android/settings/EditPinPreference.java
Normal file
83
Settings/src/com/android/settings/EditPinPreference.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.android.settingslib.CustomEditTextPreferenceCompat;
|
||||
|
||||
/**
|
||||
* TODO: Add a soft dialpad for PIN entry.
|
||||
*/
|
||||
class EditPinPreference extends CustomEditTextPreferenceCompat {
|
||||
|
||||
interface OnPinEnteredListener {
|
||||
void onPinEntered(EditPinPreference preference, boolean positiveResult);
|
||||
}
|
||||
|
||||
private OnPinEnteredListener mPinListener;
|
||||
|
||||
public EditPinPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public EditPinPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void setOnPinEnteredListener(OnPinEnteredListener listener) {
|
||||
mPinListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
final EditText editText = (EditText) view.findViewById(android.R.id.edit);
|
||||
|
||||
if (editText != null) {
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER |
|
||||
InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
editText.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDialogOpen() {
|
||||
Dialog dialog = getDialog();
|
||||
return dialog != null && dialog.isShowing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
if (mPinListener != null) {
|
||||
mPinListener.onPinEntered(this, positiveResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void showPinDialog() {
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog == null || !dialog.isShowing()) {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Settings/src/com/android/settings/EventLogTags.logtags
Normal file
18
Settings/src/com/android/settings/EventLogTags.logtags
Normal file
@@ -0,0 +1,18 @@
|
||||
# See system/core/logcat/event.logtags for a description of the format of this file.
|
||||
|
||||
option java_package com.android.settings
|
||||
|
||||
# log the type of screen lock when user sets lock screen
|
||||
90200 lock_screen_type (type|3)
|
||||
|
||||
# log whether user accepted and activated device admin
|
||||
90201 exp_det_device_admin_activated_by_user (app_signature|3)
|
||||
|
||||
# log whether user declined activation of device admin
|
||||
90202 exp_det_device_admin_declined_by_user (app_signature|3)
|
||||
|
||||
# log whether user uninstalled device admin on activation screen
|
||||
90203 exp_det_device_admin_uninstalled_by_user (app_signature|3)
|
||||
|
||||
# log latency for settings UI events
|
||||
90204 settings_latency (action|1|6),(latency|1|3)
|
||||
202
Settings/src/com/android/settings/FallbackHome.java
Normal file
202
Settings/src/com/android/settings/FallbackHome.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.WallpaperColors;
|
||||
import android.app.WallpaperManager;
|
||||
import android.app.WallpaperManager.OnColorsChangedListener;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class FallbackHome extends Activity {
|
||||
private static final String TAG = "FallbackHome";
|
||||
private int mProgressTimeout;
|
||||
|
||||
private boolean mProvisioned;
|
||||
private WallpaperManager mWallManager;
|
||||
|
||||
private final Runnable mProgressTimeoutRunnable = () -> {
|
||||
View v = getLayoutInflater().inflate(
|
||||
R.layout.fallback_home_finishing_boot, null /* root */);
|
||||
setContentView(v);
|
||||
v.setAlpha(0f);
|
||||
v.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(500)
|
||||
.setInterpolator(AnimationUtils.loadInterpolator(
|
||||
this, android.R.interpolator.fast_out_slow_in))
|
||||
.start();
|
||||
getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
};
|
||||
|
||||
private final OnColorsChangedListener mColorsChangedListener = new OnColorsChangedListener() {
|
||||
@Override
|
||||
public void onColorsChanged(WallpaperColors colors, int which) {
|
||||
if (colors != null) {
|
||||
final View decorView = getWindow().getDecorView();
|
||||
decorView.setSystemUiVisibility(
|
||||
updateVisibilityFlagsFromColors(colors, decorView.getSystemUiVisibility()));
|
||||
mWallManager.removeOnColorsChangedListener(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mProgressTimeout = getResources().getInteger(
|
||||
com.android.internal.R.integer.config_progressTimeoutFallbackHome);
|
||||
|
||||
if (mProgressTimeout <= 0) {
|
||||
mProgressTimeout = 0;
|
||||
}
|
||||
|
||||
// Set ourselves totally black before the device is provisioned so that
|
||||
// we don't flash the wallpaper before SUW
|
||||
mProvisioned = Settings.Global.getInt(getContentResolver(),
|
||||
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
|
||||
final int flags;
|
||||
if (!mProvisioned) {
|
||||
setTheme(R.style.FallbackHome_SetupWizard);
|
||||
flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
} else {
|
||||
flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
|
||||
}
|
||||
|
||||
mWallManager = getSystemService(WallpaperManager.class);
|
||||
if (mWallManager == null) {
|
||||
Log.w(TAG, "Wallpaper manager isn't ready, can't listen to color changes!");
|
||||
} else {
|
||||
loadWallpaperColors(flags);
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(flags);
|
||||
|
||||
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||
maybeFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mProvisioned) {
|
||||
mHandler.postDelayed(mProgressTimeoutRunnable, mProgressTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mHandler.removeCallbacks(mProgressTimeoutRunnable);
|
||||
}
|
||||
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
unregisterReceiver(mReceiver);
|
||||
if (mWallManager != null) {
|
||||
mWallManager.removeOnColorsChangedListener(mColorsChangedListener);
|
||||
}
|
||||
}
|
||||
|
||||
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
maybeFinish();
|
||||
}
|
||||
};
|
||||
|
||||
private void loadWallpaperColors(int flags) {
|
||||
final AsyncTask loadWallpaperColorsTask = new AsyncTask<Object, Void, Integer>() {
|
||||
@Override
|
||||
protected Integer doInBackground(Object... params) {
|
||||
final WallpaperColors colors =
|
||||
mWallManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
|
||||
|
||||
// Use a listener to wait for colors if not ready yet.
|
||||
if (colors == null) {
|
||||
mWallManager.addOnColorsChangedListener(mColorsChangedListener,
|
||||
null /* handler */);
|
||||
return null;
|
||||
}
|
||||
return updateVisibilityFlagsFromColors(colors, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer flagsToUpdate) {
|
||||
if (flagsToUpdate == null) {
|
||||
return;
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(flagsToUpdate);
|
||||
}
|
||||
};
|
||||
loadWallpaperColorsTask.execute();
|
||||
}
|
||||
|
||||
private void maybeFinish() {
|
||||
if (getSystemService(UserManager.class).isUserUnlocked()) {
|
||||
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME);
|
||||
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
|
||||
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
|
||||
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
|
||||
mHandler.sendEmptyMessageDelayed(0, 500);
|
||||
} else {
|
||||
Log.d(TAG, "User unlocked and real home found; let's go!");
|
||||
getSystemService(PowerManager.class).userActivity(
|
||||
SystemClock.uptimeMillis(), false);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the system ui flags to light status bar if the wallpaper supports dark text to match
|
||||
// current system ui color tints.
|
||||
private int updateVisibilityFlagsFromColors(WallpaperColors colors, int flags) {
|
||||
if ((colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0) {
|
||||
return flags | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
| View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
}
|
||||
return flags & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
|
||||
& ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
|
||||
}
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
maybeFinish();
|
||||
}
|
||||
};
|
||||
}
|
||||
61
Settings/src/com/android/settings/HelpTrampoline.java
Normal file
61
Settings/src/com/android/settings/HelpTrampoline.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settingslib.HelpUtils;
|
||||
|
||||
public class HelpTrampoline extends Activity {
|
||||
private static final String TAG = "HelpTrampoline";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
final String name = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
finishAndRemoveTask();
|
||||
return;
|
||||
}
|
||||
|
||||
final int id = getResources().getIdentifier(name, "string", getPackageName());
|
||||
final String value = getResources().getString(id);
|
||||
|
||||
final Intent intent = HelpUtils.getHelpIntent(this, value, null);
|
||||
if (intent != null) {
|
||||
/*
|
||||
* TODO: b/38230998.
|
||||
* Move to startActivity once the HelpUtils.getHelpIntent is refactored
|
||||
*/
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
} catch (Resources.NotFoundException | ActivityNotFoundException e) {
|
||||
Log.w(TAG, "Failed to resolve help", e);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
773
Settings/src/com/android/settings/IccLockSettings.java
Normal file
773
Settings/src/com/android/settings/IccLockSettings.java
Normal file
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PersistableBundle;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.PinResult;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets.Type;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.OnTabChangeListener;
|
||||
import android.widget.TabHost.TabContentFactory;
|
||||
import android.widget.TabHost.TabSpec;
|
||||
import android.widget.TabWidget;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.android.settings.network.ProxySubscriptionManager;
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settings.network.telephony.MobileNetworkUtils;
|
||||
import com.android.settingslib.utils.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements the preference screen to enable/disable ICC lock and
|
||||
* also the dialogs to change the ICC PIN. In the former case, enabling/disabling
|
||||
* the ICC lock will prompt the user for the current PIN.
|
||||
* In the Change PIN case, it prompts the user for old pin, new pin and new pin
|
||||
* again before attempting to change it. Calls the SimCard interface to execute
|
||||
* these operations.
|
||||
*
|
||||
*/
|
||||
public class IccLockSettings extends SettingsPreferenceFragment
|
||||
implements EditPinPreference.OnPinEnteredListener {
|
||||
private static final String TAG = "IccLockSettings";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private static final int OFF_MODE = 0;
|
||||
// State when enabling/disabling ICC lock
|
||||
private static final int ICC_LOCK_MODE = 1;
|
||||
// State when entering the old pin
|
||||
private static final int ICC_OLD_MODE = 2;
|
||||
// State when entering the new pin - first time
|
||||
private static final int ICC_NEW_MODE = 3;
|
||||
// State when entering the new pin - second time
|
||||
private static final int ICC_REENTER_MODE = 4;
|
||||
|
||||
// Keys in xml file
|
||||
private static final String PIN_DIALOG = "sim_pin";
|
||||
private static final String PIN_TOGGLE = "sim_toggle";
|
||||
// Keys in icicle
|
||||
private static final String DIALOG_SUB_ID = "dialogSubId";
|
||||
private static final String DIALOG_STATE = "dialogState";
|
||||
private static final String DIALOG_PIN = "dialogPin";
|
||||
private static final String DIALOG_ERROR = "dialogError";
|
||||
private static final String ENABLE_TO_STATE = "enableState";
|
||||
private static final String CURRENT_TAB = "currentTab";
|
||||
|
||||
// Save and restore inputted PIN code when configuration changed
|
||||
// (ex. portrait<-->landscape) during change PIN code
|
||||
private static final String OLD_PINCODE = "oldPinCode";
|
||||
private static final String NEW_PINCODE = "newPinCode";
|
||||
|
||||
private static final int MIN_PIN_LENGTH = 4;
|
||||
private static final int MAX_PIN_LENGTH = 8;
|
||||
// Which dialog to show next when popped up
|
||||
private int mDialogState = OFF_MODE;
|
||||
|
||||
private String mPin;
|
||||
private String mOldPin;
|
||||
private String mNewPin;
|
||||
private String mError;
|
||||
// Are we trying to enable or disable ICC lock?
|
||||
private boolean mToState;
|
||||
|
||||
private TabHost mTabHost;
|
||||
private TabWidget mTabWidget;
|
||||
private ListView mListView;
|
||||
|
||||
private ProxySubscriptionManager mProxySubscriptionMgr;
|
||||
|
||||
private EditPinPreference mPinDialog;
|
||||
private TwoStatePreference mPinToggle;
|
||||
|
||||
private Resources mRes;
|
||||
|
||||
// For async handler to identify request type
|
||||
private static final int MSG_SIM_STATE_CHANGED = 102;
|
||||
|
||||
// @see android.widget.Toast$TN
|
||||
private static final long LONG_DURATION_TIMEOUT = 7000;
|
||||
|
||||
private int mSlotId = -1;
|
||||
private int mSubId;
|
||||
private TelephonyManager mTelephonyManager;
|
||||
|
||||
// For replies from IccCard interface
|
||||
private Handler mHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_SIM_STATE_CHANGED:
|
||||
updatePreferences();
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mSimStateReceiver = new BroadcastReceiver() {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SIM_STATE_CHANGED));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For top-level settings screen to query
|
||||
private boolean isIccLockEnabled() {
|
||||
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
|
||||
return mTelephonyManager.isIccLockEnabled();
|
||||
}
|
||||
|
||||
private String getSummary(Context context) {
|
||||
final Resources res = context.getResources();
|
||||
final String summary = isIccLockEnabled()
|
||||
? res.getString(R.string.sim_lock_on)
|
||||
: res.getString(R.string.sim_lock_off);
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Utils.isMonkeyRunning() ||
|
||||
!SubscriptionUtil.isSimHardwareVisible(getContext()) ||
|
||||
MobileNetworkUtils.isMobileNetworkUserRestricted(getContext())) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// enable ProxySubscriptionMgr with Lifecycle support for all controllers
|
||||
// live within this fragment
|
||||
mProxySubscriptionMgr = ProxySubscriptionManager.getInstance(getContext());
|
||||
mProxySubscriptionMgr.setLifecycle(getLifecycle());
|
||||
|
||||
mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
|
||||
|
||||
addPreferencesFromResource(R.xml.sim_lock_settings);
|
||||
|
||||
mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
|
||||
mPinToggle = (TwoStatePreference) findPreference(PIN_TOGGLE);
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(DIALOG_STATE)
|
||||
&& restoreDialogStates(savedInstanceState)) {
|
||||
Log.d(TAG, "onCreate: restore dialog for slotId=" + mSlotId + ", subId=" + mSubId);
|
||||
} else if (savedInstanceState.containsKey(CURRENT_TAB)
|
||||
&& restoreTabFocus(savedInstanceState)) {
|
||||
Log.d(TAG, "onCreate: restore focus on slotId=" + mSlotId + ", subId=" + mSubId);
|
||||
}
|
||||
}
|
||||
|
||||
mPinDialog.setOnPinEnteredListener(this);
|
||||
|
||||
// Don't need any changes to be remembered
|
||||
getPreferenceScreen().setPersistent(false);
|
||||
|
||||
mRes = getResources();
|
||||
}
|
||||
|
||||
private boolean restoreDialogStates(Bundle savedInstanceState) {
|
||||
final SubscriptionInfo subInfo = mProxySubscriptionMgr
|
||||
.getActiveSubscriptionInfo(savedInstanceState.getInt(DIALOG_SUB_ID));
|
||||
if (subInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SubscriptionInfo visibleSubInfo = getVisibleSubscriptionInfoForSimSlotIndex(
|
||||
subInfo.getSimSlotIndex());
|
||||
if (visibleSubInfo == null) {
|
||||
return false;
|
||||
}
|
||||
if (visibleSubInfo.getSubscriptionId() != subInfo.getSubscriptionId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mSlotId = subInfo.getSimSlotIndex();
|
||||
mSubId = subInfo.getSubscriptionId();
|
||||
mDialogState = savedInstanceState.getInt(DIALOG_STATE);
|
||||
mPin = savedInstanceState.getString(DIALOG_PIN);
|
||||
mError = savedInstanceState.getString(DIALOG_ERROR);
|
||||
mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
|
||||
|
||||
// Restore inputted PIN code
|
||||
switch (mDialogState) {
|
||||
case ICC_NEW_MODE:
|
||||
mOldPin = savedInstanceState.getString(OLD_PINCODE);
|
||||
break;
|
||||
|
||||
case ICC_REENTER_MODE:
|
||||
mOldPin = savedInstanceState.getString(OLD_PINCODE);
|
||||
mNewPin = savedInstanceState.getString(NEW_PINCODE);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean restoreTabFocus(Bundle savedInstanceState) {
|
||||
int slotId = 0;
|
||||
try {
|
||||
slotId = Integer.parseInt(savedInstanceState.getString(CURRENT_TAB));
|
||||
} catch (NumberFormatException exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SubscriptionInfo subInfo = getVisibleSubscriptionInfoForSimSlotIndex(slotId);
|
||||
if (subInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mSlotId = subInfo.getSimSlotIndex();
|
||||
mSubId = subInfo.getSubscriptionId();
|
||||
if (mTabHost != null) {
|
||||
mTabHost.setCurrentTabByTag(getTagForSlotId(mSlotId));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
final int numSims = mProxySubscriptionMgr.getActiveSubscriptionInfoCountMax();
|
||||
final List<SubscriptionInfo> componenterList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < numSims; ++i) {
|
||||
final SubscriptionInfo subInfo = getVisibleSubscriptionInfoForSimSlotIndex(i);
|
||||
if (subInfo != null) {
|
||||
componenterList.add(subInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (componenterList.size() == 0) {
|
||||
Log.e(TAG, "onCreateView: no sim info");
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
if (mSlotId < 0) {
|
||||
mSlotId = componenterList.get(0).getSimSlotIndex();
|
||||
mSubId = componenterList.get(0).getSubscriptionId();
|
||||
Log.d(TAG, "onCreateView: default slotId=" + mSlotId + ", subId=" + mSubId);
|
||||
}
|
||||
|
||||
if (componenterList.size() > 1) {
|
||||
final View view = inflater.inflate(R.layout.icc_lock_tabs, container, false);
|
||||
final ViewGroup prefs_container = (ViewGroup) view.findViewById(R.id.prefs_container);
|
||||
Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
|
||||
final View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
|
||||
prefs_container.addView(prefs);
|
||||
|
||||
mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
|
||||
mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
|
||||
mListView = (ListView) view.findViewById(android.R.id.list);
|
||||
|
||||
mTabHost.setup();
|
||||
mTabHost.clearAllTabs();
|
||||
|
||||
for (SubscriptionInfo subInfo : componenterList) {
|
||||
final int slot = subInfo.getSimSlotIndex();
|
||||
final String tag = getTagForSlotId(slot);
|
||||
mTabHost.addTab(buildTabSpec(tag,
|
||||
String.valueOf(subInfo == null
|
||||
? getContext().getString(R.string.sim_editor_title, slot + 1)
|
||||
: SubscriptionUtil.getUniqueSubscriptionDisplayName(
|
||||
subInfo, getContext()))));
|
||||
}
|
||||
|
||||
mTabHost.setCurrentTabByTag(getTagForSlotId(mSlotId));
|
||||
mTabHost.setOnTabChangedListener(mTabListener);
|
||||
return view;
|
||||
} else {
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
private void updatePreferences() {
|
||||
|
||||
final SubscriptionInfo sir = getVisibleSubscriptionInfoForSimSlotIndex(mSlotId);
|
||||
final int subId = (sir != null) ? sir.getSubscriptionId()
|
||||
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
if (mSubId != subId) {
|
||||
mSubId = subId;
|
||||
resetDialogState();
|
||||
if ((mPinDialog != null) && mPinDialog.isDialogOpen()) {
|
||||
mPinDialog.getDialog().dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
if (mPinDialog != null) {
|
||||
mPinDialog.setEnabled(sir != null);
|
||||
}
|
||||
if (mPinToggle != null) {
|
||||
mPinToggle.setEnabled(sir != null);
|
||||
|
||||
if (sir != null) {
|
||||
mPinToggle.setChecked(isIccLockEnabled());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ICC_LOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
|
||||
// which will call updatePreferences().
|
||||
final IntentFilter filter = new IntentFilter(Intent.ACTION_SIM_STATE_CHANGED);
|
||||
getContext().registerReceiver(mSimStateReceiver, filter);
|
||||
|
||||
if (mDialogState != OFF_MODE) {
|
||||
showPinDialog();
|
||||
} else {
|
||||
// Prep for standard click on "Change PIN"
|
||||
resetDialogState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
getContext().unregisterReceiver(mSimStateReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return R.string.help_url_icc_lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
// Need to store this state for slider open/close
|
||||
// There is one case where the dialog is popped up by the preference
|
||||
// framework. In that case, let the preference framework store the
|
||||
// dialog state. In other cases, where this activity manually launches
|
||||
// the dialog, store the state of the dialog.
|
||||
if (mPinDialog.isDialogOpen()) {
|
||||
out.putInt(DIALOG_SUB_ID, mSubId);
|
||||
out.putInt(DIALOG_STATE, mDialogState);
|
||||
out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
|
||||
out.putString(DIALOG_ERROR, mError);
|
||||
out.putBoolean(ENABLE_TO_STATE, mToState);
|
||||
|
||||
// Save inputted PIN code
|
||||
switch (mDialogState) {
|
||||
case ICC_NEW_MODE:
|
||||
out.putString(OLD_PINCODE, mOldPin);
|
||||
break;
|
||||
|
||||
case ICC_REENTER_MODE:
|
||||
out.putString(OLD_PINCODE, mOldPin);
|
||||
out.putString(NEW_PINCODE, mNewPin);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
super.onSaveInstanceState(out);
|
||||
}
|
||||
|
||||
if (mTabHost != null) {
|
||||
out.putString(CURRENT_TAB, mTabHost.getCurrentTabTag());
|
||||
}
|
||||
}
|
||||
|
||||
private void showPinDialog() {
|
||||
if (mDialogState == OFF_MODE) {
|
||||
return;
|
||||
}
|
||||
setDialogValues();
|
||||
|
||||
mPinDialog.showPinDialog();
|
||||
|
||||
final EditText editText = mPinDialog.getEditText();
|
||||
if (!TextUtils.isEmpty(mPin) && editText != null) {
|
||||
editText.setSelection(mPin.length());
|
||||
}
|
||||
}
|
||||
|
||||
private void setDialogValues() {
|
||||
mPinDialog.setText(mPin);
|
||||
String message = "";
|
||||
switch (mDialogState) {
|
||||
case ICC_LOCK_MODE:
|
||||
message = mRes.getString(R.string.sim_enter_pin);
|
||||
mPinDialog.setDialogTitle(mToState
|
||||
? mRes.getString(R.string.sim_enable_sim_lock)
|
||||
: mRes.getString(R.string.sim_disable_sim_lock));
|
||||
break;
|
||||
case ICC_OLD_MODE:
|
||||
message = mRes.getString(R.string.sim_enter_old);
|
||||
mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
|
||||
break;
|
||||
case ICC_NEW_MODE:
|
||||
message = mRes.getString(R.string.sim_enter_new);
|
||||
mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
|
||||
break;
|
||||
case ICC_REENTER_MODE:
|
||||
message = mRes.getString(R.string.sim_reenter_new);
|
||||
mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
|
||||
break;
|
||||
}
|
||||
if (mError != null) {
|
||||
message = mError + "\n" + message;
|
||||
mError = null;
|
||||
}
|
||||
mPinDialog.setDialogMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
|
||||
if (!positiveResult) {
|
||||
resetDialogState();
|
||||
return;
|
||||
}
|
||||
|
||||
mPin = preference.getText();
|
||||
if (!reasonablePin(mPin)) {
|
||||
// inject error message and display dialog again
|
||||
mError = mRes.getString(R.string.sim_invalid_pin_hint);
|
||||
showPinDialog();
|
||||
return;
|
||||
}
|
||||
switch (mDialogState) {
|
||||
case ICC_LOCK_MODE:
|
||||
tryChangeIccLockState();
|
||||
break;
|
||||
case ICC_OLD_MODE:
|
||||
mOldPin = mPin;
|
||||
mDialogState = ICC_NEW_MODE;
|
||||
mError = null;
|
||||
mPin = null;
|
||||
showPinDialog();
|
||||
break;
|
||||
case ICC_NEW_MODE:
|
||||
mNewPin = mPin;
|
||||
mDialogState = ICC_REENTER_MODE;
|
||||
mPin = null;
|
||||
showPinDialog();
|
||||
break;
|
||||
case ICC_REENTER_MODE:
|
||||
if (!mPin.equals(mNewPin)) {
|
||||
mError = mRes.getString(R.string.sim_pins_dont_match);
|
||||
mDialogState = ICC_NEW_MODE;
|
||||
mPin = null;
|
||||
showPinDialog();
|
||||
} else {
|
||||
mError = null;
|
||||
tryChangePin();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
if (preference == mPinToggle) {
|
||||
// Get the new, preferred state
|
||||
mToState = mPinToggle.isChecked();
|
||||
// Flip it back and pop up pin dialog
|
||||
mPinToggle.setChecked(!mToState);
|
||||
mDialogState = ICC_LOCK_MODE;
|
||||
showPinDialog();
|
||||
} else if (preference == mPinDialog) {
|
||||
mDialogState = ICC_OLD_MODE;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void tryChangeIccLockState() {
|
||||
// Try to change icc lock. If it succeeds, toggle the lock state and
|
||||
// reset dialog state. Else inject error message and show dialog again.
|
||||
new SetIccLockEnabled(mToState, mPin).execute();
|
||||
// Disable the setting till the response is received.
|
||||
mPinToggle.setEnabled(false);
|
||||
}
|
||||
|
||||
private class SetIccLockEnabled extends AsyncTask<Void, Void, PinResult> {
|
||||
private final boolean mState;
|
||||
private final String mPin;
|
||||
|
||||
private SetIccLockEnabled(boolean state, String pin) {
|
||||
mState = state;
|
||||
mPin = pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PinResult doInBackground(Void... params) {
|
||||
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
|
||||
return mTelephonyManager.setIccLockEnabled(mState, mPin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(PinResult pinResult) {
|
||||
iccLockChanged(pinResult.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS /* success */,
|
||||
pinResult.getAttemptsRemaining() /* attemptsRemaining */);
|
||||
}
|
||||
}
|
||||
|
||||
private void iccLockChanged(boolean success, int attemptsRemaining) {
|
||||
Log.d(TAG, "iccLockChanged: success = " + success);
|
||||
if (success) {
|
||||
mPinToggle.setChecked(mToState);
|
||||
} else {
|
||||
if (attemptsRemaining >= 0) {
|
||||
createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining));
|
||||
} else {
|
||||
if (mToState) {
|
||||
Toast.makeText(getContext(), mRes.getString(
|
||||
R.string.sim_pin_enable_failed), Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), mRes.getString(
|
||||
R.string.sim_pin_disable_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
mPinToggle.setEnabled(true);
|
||||
resetDialogState();
|
||||
}
|
||||
|
||||
private void createCustomTextToast(CharSequence errorMessage) {
|
||||
// Cannot overlay Toast on PUK unlock screen.
|
||||
// The window type of Toast is set by NotificationManagerService.
|
||||
// It can't be overwritten by LayoutParams.type.
|
||||
// Ovarlay a custom window with LayoutParams (TYPE_STATUS_BAR_PANEL) on PUK unlock screen.
|
||||
final View v = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE))
|
||||
.inflate(com.android.internal.R.layout.transient_notification, null);
|
||||
final TextView tv = (TextView) v.findViewById(com.android.internal.R.id.message);
|
||||
tv.setText(errorMessage);
|
||||
tv.setSingleLine(false);
|
||||
|
||||
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
|
||||
final Configuration config = v.getContext().getResources().getConfiguration();
|
||||
final int gravity = Gravity.getAbsoluteGravity(
|
||||
getContext().getResources().getInteger(
|
||||
com.android.internal.R.integer.config_toastDefaultGravity),
|
||||
config.getLayoutDirection());
|
||||
params.gravity = gravity;
|
||||
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
|
||||
params.horizontalWeight = 1.0f;
|
||||
}
|
||||
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
|
||||
params.verticalWeight = 1.0f;
|
||||
}
|
||||
params.y = getContext().getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.toast_y_offset);
|
||||
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.format = PixelFormat.TRANSLUCENT;
|
||||
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
|
||||
params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
|
||||
params.setFitInsetsTypes(params.getFitInsetsTypes() & ~Type.statusBars());
|
||||
params.setTitle(errorMessage);
|
||||
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
||||
|
||||
final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
||||
wm.addView(v, params);
|
||||
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
wm.removeViewImmediate(v);
|
||||
}
|
||||
}, LONG_DURATION_TIMEOUT);
|
||||
}
|
||||
|
||||
private void iccPinChanged(boolean success, int attemptsRemaining) {
|
||||
Log.d(TAG, "iccPinChanged: success = " + success);
|
||||
if (!success) {
|
||||
createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining));
|
||||
} else {
|
||||
Toast.makeText(getContext(), mRes.getString(R.string.sim_change_succeeded),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
resetDialogState();
|
||||
}
|
||||
|
||||
private void tryChangePin() {
|
||||
new ChangeIccLockPin(mOldPin, mNewPin).execute();
|
||||
}
|
||||
|
||||
private class ChangeIccLockPin extends AsyncTask<Void, Void, PinResult> {
|
||||
private final String mOldPin;
|
||||
private final String mNewPin;
|
||||
|
||||
private ChangeIccLockPin(String oldPin, String newPin) {
|
||||
mOldPin = oldPin;
|
||||
mNewPin = newPin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PinResult doInBackground(Void... params) {
|
||||
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
|
||||
return mTelephonyManager.changeIccLockPin(mOldPin, mNewPin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(PinResult pinResult) {
|
||||
iccPinChanged(pinResult.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS /* success */,
|
||||
pinResult.getAttemptsRemaining() /* attemptsRemaining */);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPinPasswordErrorMessage(int attemptsRemaining) {
|
||||
String displayMessage;
|
||||
|
||||
if (attemptsRemaining == 0) {
|
||||
displayMessage = mRes.getString(R.string.wrong_pin_code_pukked);
|
||||
} else if (attemptsRemaining == 1) {
|
||||
displayMessage = mRes.getString(R.string.wrong_pin_code_one, attemptsRemaining);
|
||||
} else if (attemptsRemaining > 1) {
|
||||
displayMessage = StringUtil.getIcuPluralsString(getPrefContext(), attemptsRemaining,
|
||||
R.string.wrong_pin_code);
|
||||
} else {
|
||||
displayMessage = mRes.getString(R.string.pin_failed);
|
||||
}
|
||||
if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:"
|
||||
+ " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
|
||||
return displayMessage;
|
||||
}
|
||||
|
||||
private boolean reasonablePin(String pin) {
|
||||
if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetDialogState() {
|
||||
mError = null;
|
||||
mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
|
||||
mPin = "";
|
||||
setDialogValues();
|
||||
mDialogState = OFF_MODE;
|
||||
}
|
||||
|
||||
private String getTagForSlotId(int slotId) {
|
||||
return String.valueOf(slotId);
|
||||
}
|
||||
|
||||
private int getSlotIndexFromTag(String tag) {
|
||||
int slotId = -1;
|
||||
try {
|
||||
slotId = Integer.parseInt(tag);
|
||||
} catch (NumberFormatException exception) {
|
||||
}
|
||||
return slotId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SubscriptionInfo getVisibleSubscriptionInfoForSimSlotIndex(int slotId) {
|
||||
final List<SubscriptionInfo> subInfoList =
|
||||
mProxySubscriptionMgr.getActiveSubscriptionsInfo();
|
||||
if (subInfoList == null) {
|
||||
return null;
|
||||
}
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
final CarrierConfigManager carrierConfigManager = context.getSystemService(
|
||||
CarrierConfigManager.class);
|
||||
for (SubscriptionInfo subInfo : subInfoList) {
|
||||
if ((isSubscriptionVisible(carrierConfigManager, subInfo)
|
||||
&& (subInfo.getSimSlotIndex() == slotId))) {
|
||||
return subInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isSubscriptionVisible(CarrierConfigManager carrierConfigManager,
|
||||
SubscriptionInfo subInfo) {
|
||||
final PersistableBundle bundle = carrierConfigManager
|
||||
.getConfigForSubId(subInfo.getSubscriptionId());
|
||||
if (bundle == null) {
|
||||
return false;
|
||||
}
|
||||
return !bundle.getBoolean(CarrierConfigManager.KEY_HIDE_SIM_LOCK_SETTINGS_BOOL);
|
||||
}
|
||||
|
||||
private OnTabChangeListener mTabListener = new OnTabChangeListener() {
|
||||
@Override
|
||||
public void onTabChanged(String tabId) {
|
||||
mSlotId = getSlotIndexFromTag(tabId);
|
||||
|
||||
// The User has changed tab; update the body.
|
||||
updatePreferences();
|
||||
}
|
||||
};
|
||||
|
||||
private TabContentFactory mEmptyTabContent = new TabContentFactory() {
|
||||
@Override
|
||||
public View createTabContent(String tag) {
|
||||
return new View(mTabHost.getContext());
|
||||
}
|
||||
};
|
||||
|
||||
private TabSpec buildTabSpec(String tag, String title) {
|
||||
return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
|
||||
mEmptyTabContent);
|
||||
}
|
||||
}
|
||||
47
Settings/src/com/android/settings/LegalSettings.java
Normal file
47
Settings/src/com/android/settings/LegalSettings.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
|
||||
@SearchIndexable
|
||||
public class LegalSettings extends DashboardFragment {
|
||||
|
||||
private static final String TAG = "LegalSettings";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ABOUT_LEGAL_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.about_legal;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.about_legal);
|
||||
}
|
||||
84
Settings/src/com/android/settings/LinkifyUtils.java
Normal file
84
Settings/src/com/android/settings/LinkifyUtils.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.BufferType;
|
||||
|
||||
/**
|
||||
* Utility class to create clickable links inside {@link TextView TextViews}.
|
||||
*/
|
||||
public class LinkifyUtils {
|
||||
private static final String PLACE_HOLDER_LINK_BEGIN = "LINK_BEGIN";
|
||||
private static final String PLACE_HOLDER_LINK_END = "LINK_END";
|
||||
|
||||
private LinkifyUtils() {
|
||||
}
|
||||
|
||||
/** Interface that handles the click event of the link */
|
||||
public interface OnClickListener {
|
||||
void onClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the text into the {@link TextView} and part of it a clickable link.
|
||||
* The text surrounded with "LINK_BEGIN" and "LINK_END" will become a clickable link. Only
|
||||
* supports at most one link.
|
||||
* @return true if the link has been successfully applied, or false if the original text
|
||||
* contains no link place holders.
|
||||
*/
|
||||
public static boolean linkify(TextView textView, StringBuilder text,
|
||||
final OnClickListener listener) {
|
||||
// Remove place-holders from the string and record their positions
|
||||
final int beginIndex = text.indexOf(PLACE_HOLDER_LINK_BEGIN);
|
||||
if (beginIndex == -1) {
|
||||
textView.setText(text);
|
||||
return false;
|
||||
}
|
||||
text.delete(beginIndex, beginIndex + PLACE_HOLDER_LINK_BEGIN.length());
|
||||
final int endIndex = text.indexOf(PLACE_HOLDER_LINK_END);
|
||||
if (endIndex == -1) {
|
||||
textView.setText(text);
|
||||
return false;
|
||||
}
|
||||
text.delete(endIndex, endIndex + PLACE_HOLDER_LINK_END.length());
|
||||
|
||||
textView.setText(text.toString(), BufferType.SPANNABLE);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
Spannable spannableContent = (Spannable) textView.getText();
|
||||
ClickableSpan spannableLink = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
listener.onClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(true);
|
||||
}
|
||||
};
|
||||
spannableContent.setSpan(spannableLink, beginIndex, endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
618
Settings/src/com/android/settings/MainClear.java
Normal file
618
Settings/src/com/android/settings/MainClear.java
Normal file
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AuthenticatorDescription;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.image.DynamicSystemManager;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnScrollChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
|
||||
import com.android.settings.flags.Flags;
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||
import com.android.settings.password.ConfirmLockPattern;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.development.DevelopmentSettingsEnabler;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupcompat.template.FooterButton.ButtonType;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the device to a clean "just out of the box"
|
||||
* state. Multiple confirmations are required: first, a general "are you sure
|
||||
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
|
||||
* has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
|
||||
* ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
|
||||
* locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the initial screen.
|
||||
*/
|
||||
public class MainClear extends InstrumentedFragment implements OnGlobalLayoutListener {
|
||||
private static final String TAG = "MainClear";
|
||||
|
||||
@VisibleForTesting
|
||||
static final int KEYGUARD_REQUEST = 55;
|
||||
@VisibleForTesting
|
||||
static final int CREDENTIAL_CONFIRM_REQUEST = 56;
|
||||
private static final String KEY_SHOW_ESIM_RESET_CHECKBOX =
|
||||
"masterclear.allow_retain_esim_profiles_after_fdr";
|
||||
|
||||
static final String ERASE_EXTERNAL_EXTRA = "erase_sd";
|
||||
static final String ERASE_ESIMS_EXTRA = "erase_esim";
|
||||
|
||||
private View mContentView;
|
||||
@VisibleForTesting
|
||||
FooterButton mInitiateButton;
|
||||
private View mExternalStorageContainer;
|
||||
@VisibleForTesting
|
||||
CheckBox mExternalStorage;
|
||||
@VisibleForTesting
|
||||
View mEsimStorageContainer;
|
||||
@VisibleForTesting
|
||||
CheckBox mEsimStorage;
|
||||
@VisibleForTesting
|
||||
ScrollView mScrollView;
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
mInitiateButton.setEnabled(hasReachedBottom(mScrollView));
|
||||
}
|
||||
|
||||
private void setUpActionBarAndTitle() {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
|
||||
return;
|
||||
}
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar == null) {
|
||||
Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
|
||||
return;
|
||||
}
|
||||
actionBar.hide();
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyguard validation is run using the standard {@link ConfirmLockPattern}
|
||||
* component as a subactivity
|
||||
*
|
||||
* @param request the request code to be returned once confirmation finishes
|
||||
* @return true if confirmation launched
|
||||
*/
|
||||
private boolean runKeyguardConfirmation(int request) {
|
||||
Resources res = getActivity().getResources();
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(getActivity(), this);
|
||||
return builder.setRequestCode(request)
|
||||
.setTitle(res.getText(R.string.main_clear_short_title))
|
||||
.show();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isValidRequestCode(int requestCode) {
|
||||
return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
onActivityResultInternal(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal method that allows easy testing without dealing with super references.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void onActivityResultInternal(int requestCode, int resultCode, Intent data) {
|
||||
if (!isValidRequestCode(requestCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
establishInitialState();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = null;
|
||||
// If returning from a Keyguard request, try to show an account confirmation request if
|
||||
// applciable.
|
||||
if (CREDENTIAL_CONFIRM_REQUEST != requestCode
|
||||
&& (intent = getAccountConfirmationIntent()) != null) {
|
||||
showAccountCredentialConfirmation(intent);
|
||||
} else {
|
||||
showFinalConfirmation();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void showFinalConfirmation() {
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
|
||||
args.putBoolean(ERASE_ESIMS_EXTRA, mEsimStorage.isChecked());
|
||||
final Intent intent = new Intent();
|
||||
intent.setClass(getContext(),
|
||||
com.android.settings.Settings.FactoryResetConfirmActivity.class);
|
||||
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, MainClearConfirm.class.getName());
|
||||
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
|
||||
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
|
||||
R.string.main_clear_confirm_title);
|
||||
intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, getMetricsCategory());
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void showAccountCredentialConfirmation(Intent intent) {
|
||||
startActivityForResult(intent, CREDENTIAL_CONFIRM_REQUEST);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Intent getAccountConfirmationIntent() {
|
||||
final Context context = getActivity();
|
||||
final String accountType = context.getString(R.string.account_type);
|
||||
final String packageName = context.getString(R.string.account_confirmation_package);
|
||||
final String className = context.getString(R.string.account_confirmation_class);
|
||||
if (TextUtils.isEmpty(accountType)
|
||||
|| TextUtils.isEmpty(packageName)
|
||||
|| TextUtils.isEmpty(className)) {
|
||||
Log.i(TAG, "Resources not set for account confirmation.");
|
||||
return null;
|
||||
}
|
||||
final AccountManager am = AccountManager.get(context);
|
||||
Account[] accounts = am.getAccountsByType(accountType);
|
||||
if (accounts != null && accounts.length > 0) {
|
||||
final Intent requestAccountConfirmation = new Intent()
|
||||
.setPackage(packageName)
|
||||
.setComponent(new ComponentName(packageName, className));
|
||||
// Check to make sure that the intent is supported.
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final ResolveInfo resolution = pm.resolveActivity(requestAccountConfirmation, 0);
|
||||
if (resolution != null
|
||||
&& resolution.activityInfo != null
|
||||
&& packageName.equals(resolution.activityInfo.packageName)) {
|
||||
// Note that we need to check the packagename to make sure that an Activity resolver
|
||||
// wasn't returned.
|
||||
return requestAccountConfirmation;
|
||||
} else {
|
||||
Log.i(TAG, "Unable to resolve Activity: " + packageName + "/" + className);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "No " + accountType + " accounts installed!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user clicks to begin the reset sequence, we next require a
|
||||
* keyguard confirmation if the user has currently enabled one. If there
|
||||
* is no keyguard available, we simply go to the final confirmation prompt.
|
||||
*
|
||||
* If the user is in demo mode, route to the demo mode app for confirmation.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
|
||||
|
||||
public void onClick(View view) {
|
||||
final Context context = view.getContext();
|
||||
if (Utils.isDemoUser(context)) {
|
||||
final ComponentName componentName = Utils.getDeviceOwnerComponent(context);
|
||||
if (componentName != null) {
|
||||
final Intent requestFactoryReset = new Intent()
|
||||
.setPackage(componentName.getPackageName())
|
||||
.setAction(Intent.ACTION_FACTORY_RESET);
|
||||
context.startActivity(requestFactoryReset);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final DynamicSystemManager dsuManager = (DynamicSystemManager)
|
||||
getActivity().getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
|
||||
if (dsuManager.isInUse()) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle(R.string.dsu_is_running);
|
||||
builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {}
|
||||
});
|
||||
AlertDialog dsuAlertdialog = builder.create();
|
||||
dsuAlertdialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (runKeyguardConfirmation(KEYGUARD_REQUEST)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = getAccountConfirmationIntent();
|
||||
if (intent != null) {
|
||||
showAccountCredentialConfirmation(intent);
|
||||
} else {
|
||||
showFinalConfirmation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In its initial state, the activity presents a button for the user to
|
||||
* click in order to initiate a confirmation sequence. This method is
|
||||
* called from various other points in the code to reset the activity to
|
||||
* this base state.
|
||||
*
|
||||
* <p>Reinflating views from resources is expensive and prevents us from
|
||||
* caching widget pointers, so we use a single-inflate pattern: we lazy-
|
||||
* inflate each view, caching all of the widget pointers we'll need at the
|
||||
* time, then simply reuse the inflated views directly whenever we need
|
||||
* to change contents.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void establishInitialState() {
|
||||
setUpActionBarAndTitle();
|
||||
setUpInitiateButton();
|
||||
|
||||
mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container);
|
||||
mExternalStorage = mContentView.findViewById(R.id.erase_external);
|
||||
mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
|
||||
mEsimStorage = mContentView.findViewById(R.id.erase_esim);
|
||||
if (mScrollView != null) {
|
||||
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
mScrollView = mContentView.findViewById(R.id.main_clear_scrollview);
|
||||
|
||||
/*
|
||||
* If the external storage is emulated, it will be erased with a factory
|
||||
* reset at any rate. There is no need to have a separate option until
|
||||
* we have a factory reset that only erases some directories and not
|
||||
* others.
|
||||
*/
|
||||
if (Environment.isExternalStorageEmulated()) {
|
||||
mExternalStorageContainer.setVisibility(View.GONE);
|
||||
|
||||
final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
|
||||
externalOption.setVisibility(View.GONE);
|
||||
|
||||
final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external);
|
||||
externalAlsoErased.setVisibility(View.VISIBLE);
|
||||
|
||||
mExternalStorage.setChecked(false);
|
||||
} else {
|
||||
mExternalStorageContainer.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mExternalStorage.toggle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (showWipeEuicc()) {
|
||||
if (showWipeEuiccCheckbox()) {
|
||||
mEsimStorageContainer.setVisibility(View.VISIBLE);
|
||||
mEsimStorageContainer.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mEsimStorage.toggle();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final View esimAlsoErased = mContentView.findViewById(R.id.also_erases_esim);
|
||||
esimAlsoErased.setVisibility(View.VISIBLE);
|
||||
|
||||
final View noCancelMobilePlan = mContentView.findViewById(
|
||||
R.id.no_cancel_mobile_plan);
|
||||
noCancelMobilePlan.setVisibility(View.VISIBLE);
|
||||
mEsimStorage.setChecked(true /* checked */);
|
||||
}
|
||||
} else {
|
||||
mEsimStorage.setChecked(false /* checked */);
|
||||
}
|
||||
|
||||
final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
|
||||
loadAccountList(um);
|
||||
final StringBuffer contentDescription = new StringBuffer();
|
||||
final View mainClearContainer = mContentView.findViewById(R.id.main_clear_container);
|
||||
getContentDescription(mainClearContainer, contentDescription);
|
||||
mainClearContainer.setContentDescription(contentDescription);
|
||||
|
||||
// Set the status of initiateButton based on scrollview
|
||||
mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
|
||||
@Override
|
||||
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
|
||||
int oldScrollY) {
|
||||
if (v instanceof ScrollView && hasReachedBottom((ScrollView) v)) {
|
||||
mInitiateButton.setEnabled(true);
|
||||
mScrollView.setOnScrollChangeListener(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial state of the initiateButton
|
||||
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show any UI which is SIM related.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean showAnySubscriptionInfo(Context context) {
|
||||
return (context != null) && SubscriptionUtil.isSimHardwareVisible(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show strings indicating that the eUICC will be wiped.
|
||||
*
|
||||
* <p>We show the strings on any device which supports eUICC as long as the eUICC was ever
|
||||
* provisioned (that is, at least one profile was ever downloaded onto it).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean showWipeEuicc() {
|
||||
Context context = getContext();
|
||||
if (!showAnySubscriptionInfo(context) || !isEuiccEnabled(context)) {
|
||||
return false;
|
||||
}
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
return Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0
|
||||
|| DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean showWipeEuiccCheckbox() {
|
||||
return SystemProperties
|
||||
.getBoolean(KEY_SHOW_ESIM_RESET_CHECKBOX, false /* def */);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected boolean isEuiccEnabled(Context context) {
|
||||
EuiccManager euiccManager = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
|
||||
return euiccManager.isEnabled();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean hasReachedBottom(final ScrollView scrollView) {
|
||||
if (scrollView.getChildCount() < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final View view = scrollView.getChildAt(0);
|
||||
final int diff = view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY());
|
||||
|
||||
return diff <= 0;
|
||||
}
|
||||
|
||||
private void setUpInitiateButton() {
|
||||
if (mInitiateButton != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
|
||||
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
|
||||
final Activity activity = getActivity();
|
||||
mixin.setPrimaryButton(
|
||||
new FooterButton.Builder(activity)
|
||||
.setText(R.string.main_clear_button_text)
|
||||
.setListener(mInitiateListener)
|
||||
.setButtonType(ButtonType.OTHER)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build());
|
||||
if (Flags.showFactoryResetCancelButton()) {
|
||||
mixin.setSecondaryButton(
|
||||
new FooterButton.Builder(activity)
|
||||
.setText(android.R.string.cancel)
|
||||
.setListener(view -> activity.onBackPressed())
|
||||
.setButtonType(ButtonType.CANCEL)
|
||||
.setTheme(
|
||||
com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||
.build());
|
||||
}
|
||||
mInitiateButton = mixin.getPrimaryButton();
|
||||
}
|
||||
|
||||
private void getContentDescription(View v, StringBuffer description) {
|
||||
if (v.getVisibility() != View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
if (v instanceof ViewGroup) {
|
||||
ViewGroup vGroup = (ViewGroup) v;
|
||||
for (int i = 0; i < vGroup.getChildCount(); i++) {
|
||||
View nextChild = vGroup.getChildAt(i);
|
||||
getContentDescription(nextChild, description);
|
||||
}
|
||||
} else if (v instanceof TextView) {
|
||||
TextView vText = (TextView) v;
|
||||
description.append(vText.getText());
|
||||
description.append(","); // Allow Talkback to pause between sections.
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAccountList(final UserManager um) {
|
||||
View accountsLabel = mContentView.findViewById(R.id.accounts_label);
|
||||
LinearLayout contents = (LinearLayout) mContentView.findViewById(R.id.accounts);
|
||||
contents.removeAllViews();
|
||||
|
||||
Context context = getActivity();
|
||||
final List<UserInfo> profiles = um.getProfiles(UserHandle.myUserId());
|
||||
final int profilesSize = profiles.size();
|
||||
|
||||
AccountManager mgr = AccountManager.get(context);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
int accountsCount = 0;
|
||||
for (int profileIndex = 0; profileIndex < profilesSize; profileIndex++) {
|
||||
final UserInfo userInfo = profiles.get(profileIndex);
|
||||
final int profileId = userInfo.id;
|
||||
final UserHandle userHandle = new UserHandle(profileId);
|
||||
Account[] accounts = mgr.getAccountsAsUser(profileId);
|
||||
final int accountLength = accounts.length;
|
||||
if (accountLength == 0) {
|
||||
continue;
|
||||
}
|
||||
accountsCount += accountLength;
|
||||
|
||||
AuthenticatorDescription[] descs = AccountManager.get(context)
|
||||
.getAuthenticatorTypesAsUser(profileId);
|
||||
final int descLength = descs.length;
|
||||
|
||||
if (profilesSize > 1) {
|
||||
View titleView = Utils.inflateCategoryHeader(inflater, contents);
|
||||
titleView.setPadding(0 /* left */, titleView.getPaddingTop(),
|
||||
0 /* right */, titleView.getPaddingBottom());
|
||||
final TextView titleText = (TextView) titleView.findViewById(android.R.id.title);
|
||||
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
context.getSystemService(DevicePolicyManager.class);
|
||||
|
||||
if (userInfo.isManagedProfile()) {
|
||||
titleText.setText(devicePolicyManager.getResources().getString(
|
||||
WORK_CATEGORY_HEADER, () -> getString(
|
||||
com.android.settingslib.R.string.category_work)));
|
||||
} else {
|
||||
titleText.setText(devicePolicyManager.getResources().getString(
|
||||
PERSONAL_CATEGORY_HEADER, () -> getString(
|
||||
com.android.settingslib.R.string.category_personal)));
|
||||
}
|
||||
contents.addView(titleView);
|
||||
}
|
||||
|
||||
for (int i = 0; i < accountLength; i++) {
|
||||
Account account = accounts[i];
|
||||
AuthenticatorDescription desc = null;
|
||||
for (int j = 0; j < descLength; j++) {
|
||||
if (account.type.equals(descs[j].type)) {
|
||||
desc = descs[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (desc == null) {
|
||||
Log.w(TAG, "No descriptor for account name=" + account.name
|
||||
+ " type=" + account.type);
|
||||
continue;
|
||||
}
|
||||
Drawable icon = null;
|
||||
try {
|
||||
if (desc.iconId != 0) {
|
||||
Context authContext = context.createPackageContextAsUser(desc.packageName,
|
||||
0, userHandle);
|
||||
icon = context.getPackageManager().getUserBadgedIcon(
|
||||
authContext.getDrawable(desc.iconId), userHandle);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Bad package name for account type " + desc.type);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
Log.w(TAG, "Invalid icon id for account type " + desc.type, e);
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = context.getPackageManager().getDefaultActivityIcon();
|
||||
}
|
||||
|
||||
View child = inflater.inflate(R.layout.main_clear_account, contents, false);
|
||||
((ImageView) child.findViewById(android.R.id.icon)).setImageDrawable(icon);
|
||||
((TextView) child.findViewById(android.R.id.title)).setText(account.name);
|
||||
contents.addView(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (accountsCount > 0) {
|
||||
accountsLabel.setVisibility(View.VISIBLE);
|
||||
contents.setVisibility(View.VISIBLE);
|
||||
}
|
||||
// Checking for all other users and their profiles if any.
|
||||
View otherUsers = mContentView.findViewById(R.id.other_users_present);
|
||||
final boolean hasOtherUsers = (um.getUserCount() - profilesSize) > 0;
|
||||
otherUsers.setVisibility(hasOtherUsers ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final Context context = getContext();
|
||||
final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
|
||||
UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
|
||||
final UserManager um = UserManager.get(context);
|
||||
final boolean disallow = !um.isAdminUser() || RestrictedLockUtilsInternal
|
||||
.hasBaseUserRestriction(context, UserManager.DISALLOW_FACTORY_RESET,
|
||||
UserHandle.myUserId());
|
||||
if (disallow && !Utils.isDemoUser(context)) {
|
||||
return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
|
||||
} else if (admin != null && !Utils.isDemoUser(context)) {
|
||||
new ActionDisabledByAdminDialogHelper(getActivity())
|
||||
.prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
|
||||
.setOnDismissListener(__ -> getActivity().finish())
|
||||
.show();
|
||||
return new View(getContext());
|
||||
}
|
||||
|
||||
mContentView = inflater.inflate(R.layout.main_clear, null);
|
||||
|
||||
establishInitialState();
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.MASTER_CLEAR;
|
||||
}
|
||||
}
|
||||
293
Settings/src/com/android/settings/MainClearConfirm.java
Normal file
293
Settings/src/com/android/settings/MainClearConfirm.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.FactoryResetProtectionPolicy;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.service.oemlock.OemLockManager;
|
||||
import android.service.persistentdata.PersistentDataBlockManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupcompat.template.FooterButton.ButtonType;
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the device to a clean "just out of the box"
|
||||
* state. Multiple confirmations are required: first, a general "are you sure
|
||||
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
|
||||
* has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
|
||||
* ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
|
||||
* locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the confirmation screen.
|
||||
*/
|
||||
public class MainClearConfirm extends InstrumentedFragment {
|
||||
private static final String TAG = "MainClearConfirm";
|
||||
|
||||
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
|
||||
|
||||
@VisibleForTesting View mContentView;
|
||||
private boolean mEraseSdCard;
|
||||
@VisibleForTesting boolean mEraseEsims;
|
||||
|
||||
/**
|
||||
* The user has gone through the multiple confirmation, so now we go ahead
|
||||
* and invoke the Checkin Service to reset the device to its factory-default
|
||||
* state (rebooting in the process).
|
||||
*/
|
||||
private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
if (Utils.isMonkeyRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PersistentDataBlockManager pdbManager;
|
||||
// pre-flight check hardware support PersistentDataBlockManager
|
||||
if (!SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("")) {
|
||||
pdbManager = (PersistentDataBlockManager)
|
||||
getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
|
||||
} else {
|
||||
pdbManager = null;
|
||||
}
|
||||
|
||||
if (shouldWipePersistentDataBlock(pdbManager)) {
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
int mOldOrientation;
|
||||
ProgressDialog mProgressDialog;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
pdbManager.wipe();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
mProgressDialog.hide();
|
||||
if (getActivity() != null) {
|
||||
getActivity().setRequestedOrientation(mOldOrientation);
|
||||
doMainClear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mProgressDialog = getProgressDialog();
|
||||
mProgressDialog.show();
|
||||
|
||||
// need to prevent orientation changes as we're about to go into
|
||||
// a long IO request, so we won't be able to access inflate resources on
|
||||
// flash
|
||||
mOldOrientation = getActivity().getRequestedOrientation();
|
||||
getActivity().setRequestedOrientation(
|
||||
ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
||||
}
|
||||
}.execute();
|
||||
} else {
|
||||
doMainClear();
|
||||
}
|
||||
}
|
||||
|
||||
private ProgressDialog getProgressDialog() {
|
||||
final ProgressDialog progressDialog = new ProgressDialog(getActivity());
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setTitle(
|
||||
getActivity().getString(R.string.main_clear_progress_title));
|
||||
progressDialog.setMessage(
|
||||
getActivity().getString(R.string.main_clear_progress_text));
|
||||
return progressDialog;
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
boolean shouldWipePersistentDataBlock(PersistentDataBlockManager pdbManager) {
|
||||
if (pdbManager == null) {
|
||||
return false;
|
||||
}
|
||||
// The persistent data block will persist if the device is still being provisioned.
|
||||
if (isDeviceStillBeingProvisioned()) {
|
||||
return false;
|
||||
}
|
||||
// If OEM unlock is allowed, the persistent data block will be wiped during FR
|
||||
// process. If disabled, it will be wiped here instead.
|
||||
if (isOemUnlockedAllowed()) {
|
||||
return false;
|
||||
}
|
||||
final DevicePolicyManager dpm = (DevicePolicyManager) getActivity()
|
||||
.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
// Do not erase the factory reset protection data (from Settings) if factory reset
|
||||
// protection policy is not supported on the device.
|
||||
if (!dpm.isFactoryResetProtectionPolicySupported()) {
|
||||
return false;
|
||||
}
|
||||
// Do not erase the factory reset protection data (from Settings) if the
|
||||
// device is an organization-owned managed profile device and a factory
|
||||
// reset protection policy has been set.
|
||||
FactoryResetProtectionPolicy frpPolicy = dpm.getFactoryResetProtectionPolicy(null);
|
||||
if (dpm.isOrganizationOwnedDeviceWithManagedProfile() && frpPolicy != null
|
||||
&& frpPolicy.isNotEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isOemUnlockedAllowed() {
|
||||
return ((OemLockManager) getActivity().getSystemService(
|
||||
Context.OEM_LOCK_SERVICE)).isOemUnlockAllowed();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isDeviceStillBeingProvisioned() {
|
||||
return !WizardManagerHelper.isDeviceProvisioned(getActivity());
|
||||
}
|
||||
|
||||
private void doMainClear() {
|
||||
Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
|
||||
intent.setPackage("android");
|
||||
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
intent.putExtra(Intent.EXTRA_REASON, "MainClearConfirm");
|
||||
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
|
||||
intent.putExtra(Intent.EXTRA_WIPE_ESIMS, mEraseEsims);
|
||||
getActivity().sendBroadcast(intent);
|
||||
// Intent handling is asynchronous -- assume it will happen soon.
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the UI for the final confirmation interaction
|
||||
*/
|
||||
private void establishFinalConfirmationState() {
|
||||
final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
|
||||
|
||||
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
|
||||
mixin.setPrimaryButton(
|
||||
new FooterButton.Builder(getActivity())
|
||||
.setText(R.string.main_clear_button_text)
|
||||
.setListener(mFinalClickListener)
|
||||
.setButtonType(ButtonType.OTHER)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private void setUpActionBarAndTitle() {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
|
||||
return;
|
||||
}
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar == null) {
|
||||
Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
|
||||
return;
|
||||
}
|
||||
actionBar.hide();
|
||||
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
|
||||
getActivity(), UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
|
||||
if (RestrictedLockUtilsInternal.hasBaseUserRestriction(getActivity(),
|
||||
UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId())) {
|
||||
return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
|
||||
} else if (admin != null) {
|
||||
new ActionDisabledByAdminDialogHelper(getActivity())
|
||||
.prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
|
||||
.setOnDismissListener(__ -> getActivity().finish())
|
||||
.show();
|
||||
return new View(getActivity());
|
||||
}
|
||||
mContentView = inflater.inflate(R.layout.main_clear_confirm, null);
|
||||
setUpActionBarAndTitle();
|
||||
establishFinalConfirmationState();
|
||||
setAccessibilityTitle();
|
||||
setSubtitle();
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
private void setAccessibilityTitle() {
|
||||
CharSequence currentTitle = getActivity().getTitle();
|
||||
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
|
||||
if (confirmationMessage != null) {
|
||||
String accessibleText = new StringBuilder(currentTitle).append(",").append(
|
||||
confirmationMessage.getText()).toString();
|
||||
getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setSubtitle() {
|
||||
if (mEraseEsims) {
|
||||
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
|
||||
if (confirmationMessage != null) {
|
||||
confirmationMessage.setText(R.string.main_clear_final_desc_esim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
mEraseSdCard = args != null
|
||||
&& args.getBoolean(MainClear.ERASE_EXTERNAL_EXTRA);
|
||||
mEraseEsims = args != null
|
||||
&& args.getBoolean(MainClear.ERASE_ESIMS_EXTRA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.MASTER_CLEAR_CONFIRM;
|
||||
}
|
||||
}
|
||||
75
Settings/src/com/android/settings/ManualDisplayActivity.java
Normal file
75
Settings/src/com/android/settings/ManualDisplayActivity.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* The "dialog" that shows from "Manual" in the Settings app.
|
||||
*/
|
||||
public class ManualDisplayActivity extends Activity {
|
||||
private static final String TAG = "SettingsManualActivity";
|
||||
|
||||
private static final String MANUAL_PATH = "/system/etc/MANUAL.html.gz";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Resources resources = getResources();
|
||||
|
||||
if (!resources.getBoolean(R.bool.config_show_manual)) {
|
||||
finish(); // No manual to display for this device
|
||||
}
|
||||
|
||||
final File file = new File(MANUAL_PATH);
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
Log.e(TAG, "Manual file " + MANUAL_PATH + " does not exist");
|
||||
showErrorAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(file), "text/html");
|
||||
|
||||
intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_manual_activity_title));
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setPackage("com.android.htmlviewer");
|
||||
|
||||
try {
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Failed to find viewer", e);
|
||||
showErrorAndFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private void showErrorAndFinish() {
|
||||
Toast.makeText(this, R.string.settings_manual_activity_unavailable, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.content.Intent;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.utils.StringUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Activity that shows a dialog explaining that a CA cert is allowing someone to monitor network
|
||||
* traffic. This activity should be launched for the user into which the CA cert is installed
|
||||
* unless Intent.EXTRA_USER_ID is provided.
|
||||
*/
|
||||
public class MonitoringCertInfoActivity extends Activity implements OnClickListener,
|
||||
OnDismissListener {
|
||||
|
||||
private int mUserId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedStates) {
|
||||
super.onCreate(savedStates);
|
||||
|
||||
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
|
||||
|
||||
final UserHandle user;
|
||||
if (mUserId == UserHandle.USER_NULL) {
|
||||
user = null;
|
||||
} else {
|
||||
user = UserHandle.of(mUserId);
|
||||
}
|
||||
|
||||
DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
|
||||
final int numberOfCertificates = getIntent().getIntExtra(
|
||||
Settings.EXTRA_NUMBER_OF_CERTIFICATES, 1);
|
||||
final int titleId = RestrictedLockUtils.getProfileOrDeviceOwner(this, user) != null
|
||||
? R.string.ssl_ca_cert_settings_button // Check certificate
|
||||
: R.string.ssl_ca_cert_dialog_title; // Trust or remove certificate
|
||||
final CharSequence title = StringUtil.getIcuPluralsString(this, numberOfCertificates,
|
||||
titleId);
|
||||
setTitle(title);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(title);
|
||||
builder.setCancelable(true);
|
||||
builder.setPositiveButton(StringUtil.getIcuPluralsString(this, numberOfCertificates,
|
||||
R.string.ssl_ca_cert_settings_button) , this);
|
||||
builder.setNeutralButton(R.string.cancel, null);
|
||||
builder.setOnDismissListener(this);
|
||||
|
||||
if (dpm.getProfileOwnerAsUser(mUserId) != null) {
|
||||
MessageFormat msgFormat = new MessageFormat(
|
||||
dpm.getResources().getString(
|
||||
WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
|
||||
() -> getString(R.string.ssl_ca_cert_info_message)),
|
||||
Locale.getDefault());
|
||||
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
arguments.put("numberOfCertificates", numberOfCertificates);
|
||||
arguments.put("orgName", dpm.getProfileOwnerNameAsUser(mUserId));
|
||||
|
||||
builder.setMessage(msgFormat.format(arguments));
|
||||
} else if (dpm.getDeviceOwnerComponentOnCallingUser() != null) {
|
||||
MessageFormat msgFormat = new MessageFormat(
|
||||
dpm.getResources()
|
||||
.getString(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
|
||||
() -> getResources().getString(
|
||||
R.string.ssl_ca_cert_info_message_device_owner)),
|
||||
Locale.getDefault());
|
||||
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
arguments.put("numberOfCertificates", numberOfCertificates);
|
||||
arguments.put("orgName", dpm.getDeviceOwnerNameOnAnyUser());
|
||||
|
||||
builder.setMessage(msgFormat.format(arguments));
|
||||
} else {
|
||||
// Consumer case. Show scary warning.
|
||||
builder.setIcon(android.R.drawable.stat_notify_error);
|
||||
builder.setMessage(R.string.ssl_ca_cert_warning_message);
|
||||
}
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_TRUSTED_CREDENTIALS_USER);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.putExtra(TrustedCredentialsSettings.ARG_SHOW_NEW_FOR_USER, mUserId);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
199
Settings/src/com/android/settings/PointerSpeedPreference.java
Normal file
199
Settings/src/com/android/settings/PointerSpeedPreference.java
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
|
||||
|
||||
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_SLIDER;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.input.InputSettings;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.Settings;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
|
||||
public class PointerSpeedPreference extends SeekBarDialogPreference implements
|
||||
SeekBar.OnSeekBarChangeListener {
|
||||
private final InputManager mIm;
|
||||
private final InteractionJankMonitor mJankMonitor = InteractionJankMonitor.getInstance();
|
||||
private SeekBar mSeekBar;
|
||||
|
||||
private int mOldSpeed;
|
||||
private boolean mRestoredOldState;
|
||||
|
||||
private boolean mTouchInProgress;
|
||||
private int mLastProgress = -1;
|
||||
|
||||
private ContentObserver mSpeedObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
onSpeedChanged();
|
||||
}
|
||||
};
|
||||
|
||||
public PointerSpeedPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mIm = (InputManager)getContext().getSystemService(Context.INPUT_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
|
||||
getContext().getContentResolver().registerContentObserver(
|
||||
Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
|
||||
mSpeedObserver);
|
||||
|
||||
mRestoredOldState = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
mSeekBar = getSeekBar(view);
|
||||
mSeekBar.setMax(InputSettings.MAX_POINTER_SPEED - InputSettings.MIN_POINTER_SPEED);
|
||||
mOldSpeed = InputSettings.getPointerSpeed(getContext());
|
||||
mSeekBar.setProgress(mOldSpeed - InputSettings.MIN_POINTER_SPEED);
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
mSeekBar.setContentDescription(getTitle());
|
||||
}
|
||||
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
|
||||
if (!mTouchInProgress) {
|
||||
mIm.tryPointerSpeed(progress + InputSettings.MIN_POINTER_SPEED);
|
||||
}
|
||||
if (progress != mLastProgress) {
|
||||
seekBar.performHapticFeedback(CLOCK_TICK);
|
||||
mLastProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
mTouchInProgress = true;
|
||||
mJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
|
||||
.withView(CUJ_SETTINGS_SLIDER, seekBar)
|
||||
.setTag(getKey()));
|
||||
}
|
||||
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
mTouchInProgress = false;
|
||||
mIm.tryPointerSpeed(seekBar.getProgress() + InputSettings.MIN_POINTER_SPEED);
|
||||
mJankMonitor.end(CUJ_SETTINGS_SLIDER);
|
||||
}
|
||||
|
||||
private void onSpeedChanged() {
|
||||
int speed = InputSettings.getPointerSpeed(getContext());
|
||||
mSeekBar.setProgress(speed - InputSettings.MIN_POINTER_SPEED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
|
||||
if (positiveResult) {
|
||||
InputSettings.setPointerSpeed(getContext(),
|
||||
mSeekBar.getProgress() + InputSettings.MIN_POINTER_SPEED);
|
||||
} else {
|
||||
restoreOldState();
|
||||
}
|
||||
|
||||
resolver.unregisterContentObserver(mSpeedObserver);
|
||||
}
|
||||
|
||||
private void restoreOldState() {
|
||||
if (mRestoredOldState) return;
|
||||
|
||||
mIm.tryPointerSpeed(mOldSpeed);
|
||||
mRestoredOldState = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
final Parcelable superState = super.onSaveInstanceState();
|
||||
if (getDialog() == null || !getDialog().isShowing()) return superState;
|
||||
|
||||
// Save the dialog state
|
||||
final SavedState myState = new SavedState(superState);
|
||||
myState.progress = mSeekBar.getProgress();
|
||||
myState.oldSpeed = mOldSpeed;
|
||||
|
||||
// Restore the old state when the activity or dialog is being paused
|
||||
restoreOldState();
|
||||
return myState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Parcelable state) {
|
||||
if (state == null || !state.getClass().equals(SavedState.class)) {
|
||||
// Didn't save state for us in onSaveInstanceState
|
||||
super.onRestoreInstanceState(state);
|
||||
return;
|
||||
}
|
||||
|
||||
SavedState myState = (SavedState) state;
|
||||
super.onRestoreInstanceState(myState.getSuperState());
|
||||
mOldSpeed = myState.oldSpeed;
|
||||
mIm.tryPointerSpeed(myState.progress + InputSettings.MIN_POINTER_SPEED);
|
||||
}
|
||||
|
||||
private static class SavedState extends BaseSavedState {
|
||||
int progress;
|
||||
int oldSpeed;
|
||||
|
||||
public SavedState(Parcel source) {
|
||||
super(source);
|
||||
progress = source.readInt();
|
||||
oldSpeed = source.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(progress);
|
||||
dest.writeInt(oldSpeed);
|
||||
}
|
||||
|
||||
public SavedState(Parcelable superState) {
|
||||
super(superState);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR =
|
||||
new Parcelable.Creator<SavedState>() {
|
||||
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
94
Settings/src/com/android/settings/ProgressCategory.java
Normal file
94
Settings/src/com/android/settings/ProgressCategory.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
/**
|
||||
* A category with a progress spinner
|
||||
*/
|
||||
public class ProgressCategory extends ProgressCategoryBase {
|
||||
|
||||
private int mEmptyTextRes;
|
||||
private boolean mProgress = false;
|
||||
private Preference mNoDeviceFoundPreference;
|
||||
private boolean mNoDeviceFoundAdded;
|
||||
|
||||
public ProgressCategory(Context context) {
|
||||
super(context);
|
||||
setLayoutResource(R.layout.preference_progress_category);
|
||||
}
|
||||
|
||||
public ProgressCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setLayoutResource(R.layout.preference_progress_category);
|
||||
}
|
||||
|
||||
public ProgressCategory(Context context, AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setLayoutResource(R.layout.preference_progress_category);
|
||||
}
|
||||
|
||||
public ProgressCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setLayoutResource(R.layout.preference_progress_category);
|
||||
}
|
||||
|
||||
public void setEmptyTextRes(int emptyTextRes) {
|
||||
mEmptyTextRes = emptyTextRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
final View progressBar = view.findViewById(R.id.scanning_progress);
|
||||
|
||||
boolean noDeviceFound = (getPreferenceCount() == 0 ||
|
||||
(getPreferenceCount() == 1 && getPreference(0) == mNoDeviceFoundPreference));
|
||||
progressBar.setVisibility(mProgress ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (mProgress || !noDeviceFound) {
|
||||
if (mNoDeviceFoundAdded) {
|
||||
removePreference(mNoDeviceFoundPreference);
|
||||
mNoDeviceFoundAdded = false;
|
||||
}
|
||||
} else {
|
||||
if (!mNoDeviceFoundAdded) {
|
||||
if (mNoDeviceFoundPreference == null) {
|
||||
mNoDeviceFoundPreference = new Preference(getContext());
|
||||
mNoDeviceFoundPreference.setLayoutResource(R.layout.preference_empty_list);
|
||||
mNoDeviceFoundPreference.setTitle(mEmptyTextRes);
|
||||
mNoDeviceFoundPreference.setSelectable(false);
|
||||
}
|
||||
addPreference(mNoDeviceFoundPreference);
|
||||
mNoDeviceFoundAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(boolean progressOn) {
|
||||
mProgress = progressOn;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
48
Settings/src/com/android/settings/ProgressCategoryBase.java
Normal file
48
Settings/src/com/android/settings/ProgressCategoryBase.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
public abstract class ProgressCategoryBase extends PreferenceCategory {
|
||||
public ProgressCategoryBase(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ProgressCategoryBase(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ProgressCategoryBase(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public ProgressCategoryBase(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on/off the progress indicator and text on the right.
|
||||
*
|
||||
* @param progressOn whether or not the progress should be displayed
|
||||
*/
|
||||
public abstract void setProgress(boolean progressOn);
|
||||
}
|
||||
286
Settings/src/com/android/settings/ProxySelector.java
Normal file
286
Settings/src/com/android/settings/ProxySelector.java
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ProxyInfo;
|
||||
import android.os.Bundle;
|
||||
import android.text.Selection;
|
||||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.net.module.util.ProxyUtils;
|
||||
import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ProxySelector extends InstrumentedFragment implements DialogCreatable {
|
||||
private static final String TAG = "ProxySelector";
|
||||
|
||||
EditText mHostnameField;
|
||||
EditText mPortField;
|
||||
EditText mExclusionListField;
|
||||
Button mOKButton;
|
||||
Button mClearButton;
|
||||
Button mDefaultButton;
|
||||
|
||||
private static final int ERROR_DIALOG_ID = 0;
|
||||
|
||||
private SettingsDialogFragment mDialogFragment;
|
||||
private View mView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
mView = inflater.inflate(R.layout.proxy, container, false);
|
||||
initView(mView);
|
||||
// TODO: Populate based on connection status
|
||||
populateFields();
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
final DevicePolicyManager dpm =
|
||||
(DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
|
||||
final boolean userSetGlobalProxy = (dpm.getGlobalProxyAdmin() == null);
|
||||
// Disable UI if the Global Proxy is being controlled by a Device Admin
|
||||
mHostnameField.setEnabled(userSetGlobalProxy);
|
||||
mPortField.setEnabled(userSetGlobalProxy);
|
||||
mExclusionListField.setEnabled(userSetGlobalProxy);
|
||||
mOKButton.setEnabled(userSetGlobalProxy);
|
||||
mClearButton.setEnabled(userSetGlobalProxy);
|
||||
mDefaultButton.setEnabled(userSetGlobalProxy);
|
||||
}
|
||||
|
||||
// Dialog management
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(int id) {
|
||||
if (id == ERROR_DIALOG_ID) {
|
||||
String hostname = mHostnameField.getText().toString().trim();
|
||||
String portStr = mPortField.getText().toString().trim();
|
||||
String exclList = mExclusionListField.getText().toString().trim();
|
||||
String msg = getActivity().getString(validate(hostname, portStr, exclList));
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.proxy_error)
|
||||
.setPositiveButton(R.string.proxy_error_dismiss, null)
|
||||
.setMessage(msg)
|
||||
.create();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDialogMetricsCategory(int dialogId) {
|
||||
return SettingsEnums.DIALOG_PROXY_SELECTOR_ERROR;
|
||||
}
|
||||
|
||||
private void showDialog(int dialogId) {
|
||||
if (mDialogFragment != null) {
|
||||
Log.e(TAG, "Old dialog fragment not null!");
|
||||
}
|
||||
mDialogFragment = SettingsDialogFragment.newInstance(this, dialogId);
|
||||
mDialogFragment.show(getActivity().getSupportFragmentManager(), Integer.toString(dialogId));
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mHostnameField = (EditText)view.findViewById(R.id.hostname);
|
||||
mHostnameField.setOnFocusChangeListener(mOnFocusChangeHandler);
|
||||
|
||||
mPortField = (EditText)view.findViewById(R.id.port);
|
||||
mPortField.setOnClickListener(mOKHandler);
|
||||
mPortField.setOnFocusChangeListener(mOnFocusChangeHandler);
|
||||
|
||||
mExclusionListField = (EditText)view.findViewById(R.id.exclusionlist);
|
||||
mExclusionListField.setOnFocusChangeListener(mOnFocusChangeHandler);
|
||||
|
||||
mOKButton = (Button)view.findViewById(R.id.action);
|
||||
mOKButton.setOnClickListener(mOKHandler);
|
||||
|
||||
mClearButton = (Button)view.findViewById(R.id.clear);
|
||||
mClearButton.setOnClickListener(mClearHandler);
|
||||
|
||||
mDefaultButton = (Button)view.findViewById(R.id.defaultView);
|
||||
mDefaultButton.setOnClickListener(mDefaultHandler);
|
||||
}
|
||||
|
||||
void populateFields() {
|
||||
final Activity activity = getActivity();
|
||||
String hostname = "";
|
||||
int port = -1;
|
||||
String exclList = "";
|
||||
// Use the last setting given by the user
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
ProxyInfo proxy = cm.getGlobalProxy();
|
||||
if (proxy != null) {
|
||||
hostname = proxy.getHost();
|
||||
port = proxy.getPort();
|
||||
exclList = ProxyUtils.exclusionListAsString(proxy.getExclusionList());
|
||||
}
|
||||
|
||||
if (hostname == null) {
|
||||
hostname = "";
|
||||
}
|
||||
|
||||
mHostnameField.setText(hostname);
|
||||
|
||||
String portStr = port == -1 ? "" : Integer.toString(port);
|
||||
mPortField.setText(portStr);
|
||||
|
||||
mExclusionListField.setText(exclList);
|
||||
|
||||
final Intent intent = activity.getIntent();
|
||||
|
||||
String buttonLabel = intent.getStringExtra("button-label");
|
||||
if (!TextUtils.isEmpty(buttonLabel)) {
|
||||
mOKButton.setText(buttonLabel);
|
||||
}
|
||||
|
||||
String title = intent.getStringExtra("title");
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
activity.setTitle(title);
|
||||
} else {
|
||||
activity.setTitle(R.string.proxy_settings_title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* validate syntax of hostname and port entries
|
||||
* @return 0 on success, string resource ID on failure
|
||||
*/
|
||||
public static int validate(String hostname, String port, String exclList) {
|
||||
switch (ProxyUtils.validate(hostname, port, exclList)) {
|
||||
case ProxyUtils.PROXY_VALID:
|
||||
return 0;
|
||||
case ProxyUtils.PROXY_HOSTNAME_EMPTY:
|
||||
return R.string.proxy_error_empty_host_set_port;
|
||||
case ProxyUtils.PROXY_HOSTNAME_INVALID:
|
||||
return R.string.proxy_error_invalid_host;
|
||||
case ProxyUtils.PROXY_PORT_EMPTY:
|
||||
return R.string.proxy_error_empty_port;
|
||||
case ProxyUtils.PROXY_PORT_INVALID:
|
||||
return R.string.proxy_error_invalid_port;
|
||||
case ProxyUtils.PROXY_EXCLLIST_INVALID:
|
||||
return R.string.proxy_error_invalid_exclusion_list;
|
||||
default:
|
||||
// should neven happen
|
||||
Log.e(TAG, "Unknown proxy settings error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true on success, false if the user must correct something
|
||||
*/
|
||||
boolean saveToDb() {
|
||||
|
||||
String hostname = mHostnameField.getText().toString().trim();
|
||||
String portStr = mPortField.getText().toString().trim();
|
||||
String exclList = mExclusionListField.getText().toString().trim();
|
||||
int port = 0;
|
||||
|
||||
int result = validate(hostname, portStr, exclList);
|
||||
if (result != 0) {
|
||||
showDialog(ERROR_DIALOG_ID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (portStr.length() > 0) {
|
||||
try {
|
||||
port = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
// should never happen - caught by validate above
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ProxyInfo p = ProxyInfo.buildDirectProxy(
|
||||
hostname, port, Arrays.asList(exclList.split(",")));
|
||||
// FIXME: The best solution would be to make a better UI that would
|
||||
// disable editing of the text boxes if the user chooses to use the
|
||||
// default settings. i.e. checking a box to always use the default
|
||||
// carrier. http:/b/issue?id=756480
|
||||
// FIXME: If the user types in a proxy that matches the default, should
|
||||
// we keep that setting? Can be fixed with a new UI.
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
cm.setGlobalProxy(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
OnClickListener mOKHandler = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (saveToDb()) {
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OnClickListener mClearHandler = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mHostnameField.setText("");
|
||||
mPortField.setText("");
|
||||
mExclusionListField.setText("");
|
||||
}
|
||||
};
|
||||
|
||||
OnClickListener mDefaultHandler = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// TODO: populate based on connection status
|
||||
populateFields();
|
||||
}
|
||||
};
|
||||
|
||||
OnFocusChangeListener mOnFocusChangeHandler = new OnFocusChangeListener() {
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) {
|
||||
TextView textView = (TextView) v;
|
||||
Selection.selectAll((Spannable) textView.getText());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.PROXY_SELECTOR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.getRegulatoryInfo
|
||||
|
||||
/**
|
||||
* [Activity] that displays regulatory information for the "Regulatory information"
|
||||
* preference item, and when "*#07#" is dialed on the Phone keypad. To enable this feature,
|
||||
* set the "config_show_regulatory_info" boolean to true in a device overlay resource, and in the
|
||||
* same overlay, either add a drawable named "regulatory_info.png" containing a graphical version
|
||||
* of the required regulatory info (If ro.bootloader.hardware.sku property is set use
|
||||
* "regulatory_info_<sku>.png where sku is ro.bootloader.hardware.sku property value in lowercase"),
|
||||
* or add a string resource named "regulatory_info_text" with an HTML version of the required
|
||||
* information (text will be centered in the dialog).
|
||||
*/
|
||||
class RegulatoryInfoDisplayActivity : Activity() {
|
||||
|
||||
/** Display the regulatory info graphic in a dialog window. */
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val builder = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.regulatory_labels)
|
||||
.setOnDismissListener { finish() } // close the activity
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
|
||||
getRegulatoryInfo()?.let {
|
||||
val view = layoutInflater.inflate(R.layout.regulatory_info, null)
|
||||
val image = view.requireViewById<ImageView>(R.id.regulatoryInfo)
|
||||
image.setImageDrawable(it)
|
||||
builder.setView(view)
|
||||
builder.show()
|
||||
return
|
||||
}
|
||||
|
||||
val regulatoryText = resources.getText(R.string.regulatory_info_text)
|
||||
if (regulatoryText.isNotEmpty()) {
|
||||
builder.setMessage(regulatoryText)
|
||||
val dialog = builder.show()
|
||||
// we have to show the dialog first, or the setGravity() call will throw a NPE
|
||||
dialog.findViewById<TextView>(android.R.id.message)?.gravity = Gravity.CENTER
|
||||
} else {
|
||||
// neither drawable nor text resource exists, finish activity
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Settings/src/com/android/settings/RemoteBugreportActivity.java
Normal file
127
Settings/src/com/android/settings/RemoteBugreportActivity.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
/**
|
||||
* UI for the remote bugreport dialog. Shows one of 3 possible dialogs:
|
||||
* <ul>
|
||||
* <li>bugreport is still being taken and can be shared or declined</li>
|
||||
* <li>bugreport has been taken and can be shared or declined</li>
|
||||
* <li>bugreport has already been accepted to be shared, but is still being taken</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class RemoteBugreportActivity extends Activity {
|
||||
|
||||
private static final String TAG = "RemoteBugreportActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
|
||||
|
||||
final int notificationType = getIntent().getIntExtra(
|
||||
DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE, -1);
|
||||
|
||||
if (notificationType == DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setMessage(devicePolicyManager.getResources().getString(
|
||||
SHARING_REMOTE_BUGREPORT_MESSAGE,
|
||||
() -> getString(R.string.sharing_remote_bugreport_dialog_message)))
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
dialog.show();
|
||||
} else if (notificationType == DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED
|
||||
|| notificationType
|
||||
== DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
|
||||
|
||||
int defaultMessageId = notificationType
|
||||
== DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED
|
||||
? R.string.share_remote_bugreport_dialog_message
|
||||
: R.string.share_remote_bugreport_dialog_message_finished;
|
||||
String overrideMessageId = notificationType
|
||||
== DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED
|
||||
? SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT
|
||||
: SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle(devicePolicyManager.getResources().getString(
|
||||
SHARE_REMOTE_BUGREPORT_DIALOG_TITLE,
|
||||
() -> getString(R.string.share_remote_bugreport_dialog_title)))
|
||||
.setMessage(devicePolicyManager.getResources().getString(overrideMessageId,
|
||||
() -> getString(defaultMessageId)))
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.decline_remote_bugreport_action,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(
|
||||
DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED);
|
||||
RemoteBugreportActivity.this.sendBroadcastAsUser(intent,
|
||||
UserHandle.SYSTEM, android.Manifest.permission.DUMP);
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.share_remote_bugreport_action,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(
|
||||
DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED);
|
||||
RemoteBugreportActivity.this.sendBroadcastAsUser(intent,
|
||||
UserHandle.SYSTEM, android.Manifest.permission.DUMP);
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
dialog.show();
|
||||
} else {
|
||||
Log.e(TAG, "Incorrect dialog type, no dialog shown. Received: " + notificationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
323
Settings/src/com/android/settings/ResetNetwork.java
Normal file
323
Settings/src/com/android/settings/ResetNetwork.java
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.internal.telephony.flags.Flags;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.network.ResetNetworkRestrictionViewBuilder;
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||
import com.android.settings.password.ConfirmLockPattern;
|
||||
import com.android.settingslib.development.DevelopmentSettingsEnabler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the device's network settings to a clean "just out of the box"
|
||||
* state. Multiple confirmations are required: first, a general "are you sure you want to do this?"
|
||||
* prompt, followed by a keyguard pattern trace if the user has defined one, followed by a final
|
||||
* strongly-worded "THIS WILL RESET EVERYTHING" prompt. If at any time the phone is allowed to go
|
||||
* to sleep, is locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the initial screen.
|
||||
*/
|
||||
public class ResetNetwork extends InstrumentedFragment {
|
||||
private static final String TAG = "ResetNetwork";
|
||||
|
||||
// Arbitrary to avoid conficts
|
||||
private static final int KEYGUARD_REQUEST = 55;
|
||||
|
||||
private ActivityResultLauncher mActivityResultLauncher;
|
||||
private List<SubscriptionInfo> mSubscriptions;
|
||||
|
||||
private View mContentView;
|
||||
private Spinner mSubscriptionSpinner;
|
||||
private Button mInitiateButton;
|
||||
@VisibleForTesting View mEsimContainer;
|
||||
@VisibleForTesting CheckBox mEsimCheckbox;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getActivity().setTitle(R.string.reset_mobile_network_settings_title);
|
||||
|
||||
mActivityResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> onActivityLauncherResult(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyguard validation is run using the standard {@link ConfirmLockPattern}
|
||||
* component as a subactivity
|
||||
* @param request the request code to be returned once confirmation finishes
|
||||
* @return true if confirmation launched
|
||||
*/
|
||||
private boolean runKeyguardConfirmation(int request) {
|
||||
Resources res = getActivity().getResources();
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(getActivity(), this);
|
||||
return builder.setRequestCode(request)
|
||||
.setTitle(res.getText(R.string.reset_mobile_network_settings_title))
|
||||
.setActivityResultLauncher(mActivityResultLauncher)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void onActivityLauncherResult(ActivityResult result) {
|
||||
// If the user entered a valid keyguard trace, present the final
|
||||
// confirmation prompt; otherwise, go back to the initial state.
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
showFinalConfirmation();
|
||||
} else if (mContentView != null) {
|
||||
establishInitialState(getActiveSubscriptionInfoList());
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void showFinalConfirmation() {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
// TODO(b/317276437) Simplify the logic once flag is released
|
||||
int resetOptions = ResetNetworkRequest.RESET_CONNECTIVITY_MANAGER
|
||||
| ResetNetworkRequest.RESET_VPN_MANAGER;
|
||||
if (Flags.resetMobileNetworkSettings()) {
|
||||
resetOptions |= ResetNetworkRequest.RESET_IMS_STACK;
|
||||
resetOptions |= ResetNetworkRequest.RESET_PHONE_PROCESS;
|
||||
resetOptions |= ResetNetworkRequest.RESET_RILD;
|
||||
}
|
||||
ResetNetworkRequest request = new ResetNetworkRequest(resetOptions);
|
||||
if (mSubscriptions != null && mSubscriptions.size() > 0) {
|
||||
int selectedIndex = mSubscriptionSpinner.getSelectedItemPosition();
|
||||
SubscriptionInfo subscription = mSubscriptions.get(selectedIndex);
|
||||
int subId = subscription.getSubscriptionId();
|
||||
request.setResetTelephonyAndNetworkPolicyManager(subId)
|
||||
.setResetApn(subId);
|
||||
if (Flags.resetMobileNetworkSettings()) {
|
||||
request.setResetImsSubId(subId);
|
||||
}
|
||||
}
|
||||
if (mEsimContainer.getVisibility() == View.VISIBLE && mEsimCheckbox.isChecked()) {
|
||||
request.setResetEsim(getContext().getPackageName())
|
||||
.writeIntoBundle(args);
|
||||
} else {
|
||||
request.writeIntoBundle(args);
|
||||
}
|
||||
|
||||
new SubSettingLauncher(getContext())
|
||||
.setDestination(ResetNetworkConfirm.class.getName())
|
||||
.setArguments(args)
|
||||
.setTitleRes(R.string.reset_mobile_network_settings_confirm_title)
|
||||
.setSourceMetricsCategory(getMetricsCategory())
|
||||
.launch();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user clicks to begin the reset sequence, we next require a
|
||||
* keyguard confirmation if the user has currently enabled one. If there
|
||||
* is no keyguard available, we simply go to the final confirmation prompt.
|
||||
*/
|
||||
private final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
|
||||
showFinalConfirmation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In its initial state, the activity presents a button for the user to
|
||||
* click in order to initiate a confirmation sequence. This method is
|
||||
* called from various other points in the code to reset the activity to
|
||||
* this base state.
|
||||
*
|
||||
* <p>Reinflating views from resources is expensive and prevents us from
|
||||
* caching widget pointers, so we use a single-inflate pattern: we lazy-
|
||||
* inflate each view, caching all of the widget pointers we'll need at the
|
||||
* time, then simply reuse the inflated views directly whenever we need
|
||||
* to change contents.
|
||||
*
|
||||
* @param subscriptionsList is a list of SubscriptionInfo(s) which allow user to select from
|
||||
*/
|
||||
private void establishInitialState(List<SubscriptionInfo> subscriptionsList) {
|
||||
mSubscriptionSpinner = (Spinner) mContentView.findViewById(R.id.reset_network_subscription);
|
||||
mEsimContainer = mContentView.findViewById(R.id.erase_esim_container);
|
||||
mEsimCheckbox = mContentView.findViewById(R.id.erase_esim);
|
||||
|
||||
mSubscriptions = subscriptionsList;
|
||||
if (mSubscriptions != null && mSubscriptions.size() > 0) {
|
||||
// Get the default subscription in the order of data, voice, sms, first up.
|
||||
int defaultSubscription = SubscriptionManager.getDefaultDataSubscriptionId();
|
||||
if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
|
||||
defaultSubscription = SubscriptionManager.getDefaultVoiceSubscriptionId();
|
||||
}
|
||||
if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
|
||||
defaultSubscription = SubscriptionManager.getDefaultSmsSubscriptionId();
|
||||
}
|
||||
if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
|
||||
defaultSubscription = SubscriptionManager.getDefaultSubscriptionId();
|
||||
}
|
||||
|
||||
int selectedIndex = 0;
|
||||
int size = mSubscriptions.size();
|
||||
List<String> subscriptionNames = new ArrayList<>();
|
||||
for (SubscriptionInfo record : mSubscriptions) {
|
||||
if (record.getSubscriptionId() == defaultSubscription) {
|
||||
// Set the first selected value to the default
|
||||
selectedIndex = subscriptionNames.size();
|
||||
}
|
||||
String name = SubscriptionUtil.getUniqueSubscriptionDisplayName(
|
||||
record, getContext()).toString();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = record.getNumber();
|
||||
}
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
CharSequence carrierName = record.getCarrierName();
|
||||
name = TextUtils.isEmpty(carrierName) ? "" : carrierName.toString();
|
||||
}
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = String.format("MCC:%s MNC:%s Slot:%s Id:%s", record.getMcc(),
|
||||
record.getMnc(), record.getSimSlotIndex(), record.getSubscriptionId());
|
||||
}
|
||||
subscriptionNames.add(name);
|
||||
}
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
|
||||
android.R.layout.simple_spinner_item, subscriptionNames);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSubscriptionSpinner.setAdapter(adapter);
|
||||
mSubscriptionSpinner.setSelection(selectedIndex);
|
||||
if (mSubscriptions.size() > 1) {
|
||||
mSubscriptionSpinner.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSubscriptionSpinner.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
} else {
|
||||
mSubscriptionSpinner.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_reset_network);
|
||||
mInitiateButton.setOnClickListener(mInitiateListener);
|
||||
if (showEuiccSettings(getContext())) {
|
||||
mEsimContainer.setVisibility(View.VISIBLE);
|
||||
mEsimContainer.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mEsimCheckbox.toggle();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mEsimCheckbox.setChecked(false /* checked */);
|
||||
}
|
||||
}
|
||||
|
||||
private List<SubscriptionInfo> getActiveSubscriptionInfoList() {
|
||||
if (!SubscriptionUtil.isSimHardwareVisible(getActivity())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
SubscriptionManager mgr = getActivity().getSystemService(SubscriptionManager.class);
|
||||
if (mgr == null) {
|
||||
Log.w(TAG, "No SubscriptionManager");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Optional.ofNullable(mgr.getActiveSubscriptionInfoList())
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mContentView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update options if subcription has been changed
|
||||
List<SubscriptionInfo> updatedSubscriptions = getActiveSubscriptionInfoList();
|
||||
if ((mSubscriptions != null)
|
||||
&& (mSubscriptions.size() == updatedSubscriptions.size())
|
||||
&& mSubscriptions.containsAll(updatedSubscriptions)) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "subcription list changed");
|
||||
establishInitialState(updatedSubscriptions);
|
||||
}
|
||||
|
||||
private boolean showEuiccSettings(Context context) {
|
||||
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
|
||||
return false;
|
||||
}
|
||||
EuiccManager euiccManager =
|
||||
(EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
|
||||
if (!euiccManager.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
return Settings.Global.getInt(resolver, Global.EUICC_PROVISIONED, 0) != 0
|
||||
|| DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = (new ResetNetworkRestrictionViewBuilder(getActivity())).build();
|
||||
if (view != null) {
|
||||
Log.w(TAG, "Access deny.");
|
||||
return view;
|
||||
}
|
||||
|
||||
mContentView = inflater.inflate(R.layout.reset_mobile_network_settings, null);
|
||||
|
||||
establishInitialState(getActiveSubscriptionInfoList());
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.RESET_NETWORK;
|
||||
}
|
||||
}
|
||||
247
Settings/src/com/android/settings/ResetNetworkConfirm.java
Normal file
247
Settings/src/com/android/settings/ResetNetworkConfirm.java
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.network.ResetNetworkOperationBuilder;
|
||||
import com.android.settings.network.ResetNetworkRestrictionViewBuilder;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the network settings to a clean "just out of the box"
|
||||
* state. Multiple confirmations are required: first, a general "are you sure
|
||||
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
|
||||
* has defined one, followed by a final strongly-worded "THIS WILL RESET EVERYTHING"
|
||||
* prompt. If at any time the phone is allowed to go to sleep, is
|
||||
* locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the confirmation screen.
|
||||
*/
|
||||
public class ResetNetworkConfirm extends InstrumentedFragment {
|
||||
private static final String TAG = "ResetNetworkConfirm";
|
||||
|
||||
@VisibleForTesting View mContentView;
|
||||
@VisibleForTesting ResetNetworkTask mResetNetworkTask;
|
||||
@VisibleForTesting Activity mActivity;
|
||||
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private AlertDialog mAlertDialog;
|
||||
@VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
|
||||
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
|
||||
|
||||
/**
|
||||
* Async task used to do all reset task. If error happens during
|
||||
* erasing eSIM profiles or timeout, an error msg is shown.
|
||||
*/
|
||||
private class ResetNetworkTask extends AsyncTask<Void, Void, Boolean> {
|
||||
private static final String TAG = "ResetNetworkTask";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
ResetNetworkTask(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
final AtomicBoolean resetEsimSuccess = new AtomicBoolean(true);
|
||||
|
||||
String resetEsimPackageName = mResetNetworkRequest.getResetEsimPackageName();
|
||||
ResetNetworkOperationBuilder builder = mResetNetworkRequest
|
||||
.toResetNetworkOperationBuilder(mContext, Looper.getMainLooper());
|
||||
if (resetEsimPackageName != null) {
|
||||
// Override reset eSIM option for the result of reset operation
|
||||
builder = builder.resetEsim(resetEsimPackageName,
|
||||
success -> { resetEsimSuccess.set(success); }
|
||||
);
|
||||
}
|
||||
builder.build().run();
|
||||
|
||||
boolean isResetSucceed = resetEsimSuccess.get();
|
||||
Log.d(TAG, "network factoryReset complete. succeeded: "
|
||||
+ String.valueOf(isResetSucceed));
|
||||
return isResetSucceed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean succeeded) {
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
if (succeeded) {
|
||||
Toast.makeText(mContext, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
mAlertDialog = new AlertDialog.Builder(mContext)
|
||||
.setTitle(R.string.reset_esim_error_title)
|
||||
.setMessage(R.string.reset_esim_error_msg)
|
||||
.setPositiveButton(android.R.string.ok, null /* listener */)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has gone through the multiple confirmation, so now we go ahead
|
||||
* and reset the network settings to its factory-default state.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (Utils.isMonkeyRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// abandon execution if subscription no longer active
|
||||
Integer subId = mResetSubscriptionContract.getAnyMissingSubscriptionId();
|
||||
if (subId != null) {
|
||||
Log.w(TAG, "subId " + subId + " no longer active");
|
||||
getActivity().onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Should dismiss the progress dialog firstly if it is showing
|
||||
// Or not the progress dialog maybe not dismissed in fast clicking.
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
mProgressDialog = getProgressDialog(mActivity);
|
||||
mProgressDialog.show();
|
||||
|
||||
mResetNetworkTask = new ResetNetworkTask(mActivity);
|
||||
mResetNetworkTask.execute();
|
||||
}
|
||||
};
|
||||
|
||||
private ProgressDialog getProgressDialog(Context context) {
|
||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setMessage(
|
||||
context.getString(R.string.main_clear_progress_text));
|
||||
return progressDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the UI for the final confirmation interaction
|
||||
*/
|
||||
private void establishFinalConfirmationState() {
|
||||
mContentView.findViewById(R.id.execute_reset_network)
|
||||
.setOnClickListener(mFinalClickListener);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setSubtitle() {
|
||||
if (mResetNetworkRequest.getResetEsimPackageName() != null) {
|
||||
((TextView) mContentView.findViewById(R.id.reset_network_confirm))
|
||||
.setText(R.string.reset_network_final_desc_esim);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
|
||||
if (view != null) {
|
||||
mResetSubscriptionContract.close();
|
||||
Log.w(TAG, "Access deny.");
|
||||
return view;
|
||||
}
|
||||
mContentView = inflater.inflate(R.layout.reset_network_confirm, null);
|
||||
establishFinalConfirmationState();
|
||||
setSubtitle();
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args == null) {
|
||||
args = savedInstanceState;
|
||||
}
|
||||
mResetNetworkRequest = new ResetNetworkRequest(args);
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
mResetSubscriptionContract = new ResetSubscriptionContract(getContext(),
|
||||
mResetNetworkRequest) {
|
||||
@Override
|
||||
public void onSubscriptionInactive(int subscriptionId) {
|
||||
// close UI if subscription no longer active
|
||||
Log.w(TAG, "subId " + subscriptionId + " no longer active.");
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mResetNetworkRequest.writeIntoBundle(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mResetNetworkTask != null) {
|
||||
mResetNetworkTask.cancel(true /* mayInterruptIfRunning */);
|
||||
mResetNetworkTask = null;
|
||||
}
|
||||
if (mResetSubscriptionContract != null) {
|
||||
mResetSubscriptionContract.close();
|
||||
mResetSubscriptionContract = null;
|
||||
}
|
||||
if (mProgressDialog != null) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
if (mAlertDialog != null) {
|
||||
mAlertDialog.dismiss();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.RESET_NETWORK_CONFIRM;
|
||||
}
|
||||
}
|
||||
281
Settings/src/com/android/settings/ResetNetworkRequest.java
Normal file
281
Settings/src/com/android/settings/ResetNetworkRequest.java
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.telephony.SubscriptionManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.network.ResetNetworkOperationBuilder;
|
||||
|
||||
/**
|
||||
* A request which contains options required for resetting network.
|
||||
*/
|
||||
public class ResetNetworkRequest {
|
||||
|
||||
/* Reset option - nothing get reset */
|
||||
public static final int RESET_NONE = 0x00;
|
||||
|
||||
/* Reset option - reset ConnectivityManager */
|
||||
public static final int RESET_CONNECTIVITY_MANAGER = 0x01;
|
||||
|
||||
/* Reset option - reset VpnManager */
|
||||
public static final int RESET_VPN_MANAGER = 0x02;
|
||||
|
||||
/* Reset option - reset WiFiManager */
|
||||
public static final int RESET_WIFI_MANAGER = 0x04;
|
||||
|
||||
/* Reset option - reset WifiP2pManager */
|
||||
public static final int RESET_WIFI_P2P_MANAGER = 0x08;
|
||||
|
||||
/* Reset option - reset BluetoothManager */
|
||||
public static final int RESET_BLUETOOTH_MANAGER = 0x10;
|
||||
|
||||
/* Reset option - reset IMS stack */
|
||||
public static final int RESET_IMS_STACK = 0x20;
|
||||
|
||||
/* Reset option - reset phone process */
|
||||
public static final int RESET_PHONE_PROCESS = 0x40;
|
||||
|
||||
/* Reset option - reset RILD */
|
||||
public static final int RESET_RILD = 0x80;
|
||||
|
||||
/**
|
||||
* Subscription ID indicates NOT resetting any of the components below:
|
||||
* - TelephonyAndNetworkPolicy
|
||||
* - APN
|
||||
* - IMS
|
||||
*/
|
||||
public static final int INVALID_SUBSCRIPTION_ID = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
/**
|
||||
* Subscription ID indicates resetting components below for ALL subscriptions:
|
||||
* - TelephonyAndNetworkPolicy
|
||||
* - APN
|
||||
* - IMS
|
||||
*/
|
||||
public static final int ALL_SUBSCRIPTION_ID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
|
||||
|
||||
/* Key within Bundle. To store some connectivity options for reset */
|
||||
@VisibleForTesting
|
||||
protected static final String KEY_RESET_OPTIONS = "resetNetworkOptions";
|
||||
|
||||
/* Key within Bundle. To store package name for resetting eSIM */
|
||||
@VisibleForTesting
|
||||
protected static final String KEY_ESIM_PACKAGE = "resetEsimPackage";
|
||||
|
||||
/**
|
||||
* Key within Bundle. To store subscription ID for resetting
|
||||
* telephony manager and network and network policy manager.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected static final String KEY_TELEPHONY_NET_POLICY_MANAGER_SUBID =
|
||||
"resetTelephonyNetPolicySubId";
|
||||
|
||||
/* Key within Bundle. To store subscription ID for resetting APN. */
|
||||
@VisibleForTesting
|
||||
protected static final String KEY_APN_SUBID = "resetApnSubId";
|
||||
|
||||
/** Key within Bundle. To store subscription ID for resetting IMS. */
|
||||
protected static final String KEY_RESET_IMS_SUBID = "resetImsSubId";
|
||||
|
||||
private int mResetOptions = RESET_NONE;
|
||||
private String mResetEsimPackageName;
|
||||
private int mResetTelephonyManager = INVALID_SUBSCRIPTION_ID;
|
||||
private int mResetApn = INVALID_SUBSCRIPTION_ID;
|
||||
private int mSubscriptionIdToResetIms = INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
/**
|
||||
* Reconstruct based on keys stored within Bundle.
|
||||
* @param optionsFromBundle is a Bundle which previously stored through #writeIntoBundle()
|
||||
*/
|
||||
public ResetNetworkRequest(Bundle optionsFromBundle) {
|
||||
if (optionsFromBundle == null) {
|
||||
return;
|
||||
}
|
||||
mResetOptions = optionsFromBundle.getInt(KEY_RESET_OPTIONS, RESET_NONE);
|
||||
mResetEsimPackageName = optionsFromBundle.getString(KEY_ESIM_PACKAGE);
|
||||
mResetTelephonyManager = optionsFromBundle.getInt(
|
||||
KEY_TELEPHONY_NET_POLICY_MANAGER_SUBID, INVALID_SUBSCRIPTION_ID);
|
||||
mResetApn = optionsFromBundle.getInt(KEY_APN_SUBID, INVALID_SUBSCRIPTION_ID);
|
||||
mSubscriptionIdToResetIms = optionsFromBundle.getInt(KEY_RESET_IMS_SUBID,
|
||||
INVALID_SUBSCRIPTION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct of class
|
||||
* @param resetOptions is a binary combination(OR logic operation) of constants
|
||||
* comes with RESET_ prefix. Which are the reset options comes within.
|
||||
*/
|
||||
public ResetNetworkRequest(int resetOptions) {
|
||||
mResetOptions = resetOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package name applied for resetting eSIM.
|
||||
* @return package name. {@code null} means resetting eSIM is not part of the
|
||||
* option within this request.
|
||||
*/
|
||||
public String getResetEsimPackageName() {
|
||||
return mResetEsimPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the package name for resetting eSIM.
|
||||
* @param packageName is the package name for resetting eSIM.
|
||||
* {@code null} will remove the resetting eSIM option out of this request.
|
||||
* @return this request
|
||||
*/
|
||||
public ResetNetworkRequest setResetEsim(String packageName) {
|
||||
mResetEsimPackageName = packageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription ID applied for resetting Telephony and NetworkPolicy.
|
||||
* @return subscription ID.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means
|
||||
* resetting Telephony and NetworkPolicy is not part of the option
|
||||
* within this request.
|
||||
*/
|
||||
public int getResetTelephonyAndNetworkPolicyManager() {
|
||||
return mResetTelephonyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subscription ID applied for resetting Telephony and NetworkPolicy.
|
||||
* @param subscriptionId is the subscription ID referenced fron SubscriptionManager.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means resetting Telephony and NetworkPolicy
|
||||
* will not take place.
|
||||
* @return this request
|
||||
*/
|
||||
public ResetNetworkRequest setResetTelephonyAndNetworkPolicyManager(int subscriptionId) {
|
||||
mResetTelephonyManager = subscriptionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription ID applied for resetting APN.
|
||||
* @return subscription ID.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means resetting APN
|
||||
* is not part of the option within this request.
|
||||
*/
|
||||
public int getResetApnSubId() {
|
||||
return mResetApn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subscription ID applied for resetting APN.
|
||||
* @param subscriptionId is the subscription ID referenced fron SubscriptionManager.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means resetting APN will not take place.
|
||||
* @return this request
|
||||
*/
|
||||
public ResetNetworkRequest setResetApn(int subscriptionId) {
|
||||
mResetApn = subscriptionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription ID applied for resetting IMS.
|
||||
* @return subscription ID.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means resetting IMS
|
||||
* is not part of the option within this request.
|
||||
*/
|
||||
public int getResetImsSubId() {
|
||||
return mSubscriptionIdToResetIms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subscription ID applied for resetting APN.
|
||||
* @param subId is the subscription ID referenced from SubscriptionManager.
|
||||
* {@code ALL_SUBSCRIPTION_ID} for applying to all subscriptions.
|
||||
* {@code INVALID_SUBSCRIPTION_ID} means resetting IMS will not take place.
|
||||
* @return this
|
||||
*/
|
||||
public ResetNetworkRequest setResetImsSubId(int subId) {
|
||||
mSubscriptionIdToResetIms = subId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a copy of this request into Bundle given.
|
||||
* @param writeToBundle is a Bundle for storing configurations of this request.
|
||||
* @return this request
|
||||
*/
|
||||
public ResetNetworkRequest writeIntoBundle(Bundle writeToBundle) {
|
||||
writeToBundle.putInt(KEY_RESET_OPTIONS, mResetOptions);
|
||||
writeToBundle.putString(KEY_ESIM_PACKAGE, mResetEsimPackageName);
|
||||
writeToBundle.putInt(KEY_TELEPHONY_NET_POLICY_MANAGER_SUBID, mResetTelephonyManager);
|
||||
writeToBundle.putInt(KEY_APN_SUBID, mResetApn);
|
||||
writeToBundle.putInt(KEY_RESET_IMS_SUBID, mSubscriptionIdToResetIms);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a ResetNetworkOperationBuilder based on configurations within this request.
|
||||
* @param context required by ResetNetworkOperationBuilder
|
||||
* @param looper required by ResetNetworkOperationBuilder for callback support
|
||||
* @return a ResetNetworkOperationBuilder
|
||||
*/
|
||||
public ResetNetworkOperationBuilder toResetNetworkOperationBuilder(Context context,
|
||||
Looper looper) {
|
||||
// Follow specific order based on previous design within file ResetNetworkConfirm.java
|
||||
ResetNetworkOperationBuilder builder = new ResetNetworkOperationBuilder(context);
|
||||
if ((mResetOptions & RESET_CONNECTIVITY_MANAGER) != 0) {
|
||||
builder.resetConnectivityManager();
|
||||
}
|
||||
if ((mResetOptions & RESET_VPN_MANAGER) != 0) {
|
||||
builder.resetVpnManager();
|
||||
}
|
||||
if ((mResetOptions & RESET_WIFI_MANAGER) != 0) {
|
||||
builder.resetWifiManager();
|
||||
}
|
||||
if ((mResetOptions & RESET_WIFI_P2P_MANAGER) != 0) {
|
||||
builder.resetWifiP2pManager(looper);
|
||||
}
|
||||
if (mResetEsimPackageName != null) {
|
||||
builder.resetEsim(mResetEsimPackageName);
|
||||
}
|
||||
if (mResetTelephonyManager != INVALID_SUBSCRIPTION_ID) {
|
||||
builder.resetTelephonyAndNetworkPolicyManager(mResetTelephonyManager);
|
||||
}
|
||||
if ((mResetOptions & RESET_BLUETOOTH_MANAGER) != 0) {
|
||||
builder.resetBluetoothManager();
|
||||
}
|
||||
if (mResetApn != INVALID_SUBSCRIPTION_ID) {
|
||||
builder.resetApn(mResetApn);
|
||||
}
|
||||
if ((mResetOptions & RESET_IMS_STACK) != 0) {
|
||||
builder.resetIms(mSubscriptionIdToResetIms);
|
||||
}
|
||||
if ((mResetOptions & RESET_PHONE_PROCESS) != 0) {
|
||||
builder.restartPhoneProcess();
|
||||
}
|
||||
if ((mResetOptions & RESET_RILD) != 0) {
|
||||
builder.restartRild();
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
157
Settings/src/com/android/settings/ResetSubscriptionContract.java
Normal file
157
Settings/src/com/android/settings/ResetSubscriptionContract.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A Class monitoring the availability of subscription IDs provided within reset request.
|
||||
*
|
||||
* This is to detect the situation when user changing SIM card during the presenting of
|
||||
* confirmation UI.
|
||||
*/
|
||||
public class ResetSubscriptionContract implements AutoCloseable {
|
||||
private static final String TAG = "ResetSubscriptionContract";
|
||||
|
||||
private final Context mContext;
|
||||
private ExecutorService mExecutorService;
|
||||
private final int [] mResetSubscriptionIds;
|
||||
@VisibleForTesting
|
||||
protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
|
||||
private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param context Context
|
||||
* @param resetRequest the request object for perform network reset operation.
|
||||
*/
|
||||
public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
|
||||
mContext = context;
|
||||
// Only keeps specific subscription ID required to perform reset operation
|
||||
IntStream subIdStream = IntStream.of(
|
||||
resetRequest.getResetTelephonyAndNetworkPolicyManager(),
|
||||
resetRequest.getResetApnSubId(), resetRequest.getResetImsSubId());
|
||||
mResetSubscriptionIds = subIdStream.sorted().distinct()
|
||||
.filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
|
||||
.toArray();
|
||||
|
||||
if (mResetSubscriptionIds.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Monitoring callback through background thread
|
||||
mExecutorService = Executors.newSingleThreadExecutor();
|
||||
startMonitorSubscriptionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for detecting if there's any subscription under monitor no longer active.
|
||||
* @return subscription ID which is no longer active.
|
||||
*/
|
||||
public Integer getAnyMissingSubscriptionId() {
|
||||
if (mResetSubscriptionIds.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr == null) {
|
||||
Log.w(TAG, "Fail to access subscription manager");
|
||||
return mResetSubscriptionIds[0];
|
||||
}
|
||||
for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
|
||||
int subId = mResetSubscriptionIds[idx];
|
||||
if (mgr.getActiveSubscriptionInfo(subId) == null) {
|
||||
Log.w(TAG, "SubId " + subId + " no longer active.");
|
||||
return subId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async callback when detecting if there's any subscription under monitor no longer active.
|
||||
* @param subscriptionId subscription ID which is no longer active.
|
||||
*/
|
||||
public void onSubscriptionInactive(int subscriptionId) {}
|
||||
|
||||
@VisibleForTesting
|
||||
protected SubscriptionManager getSubscriptionManager() {
|
||||
return mContext.getSystemService(SubscriptionManager.class);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected OnSubscriptionsChangedListener getChangeListener() {
|
||||
return new OnSubscriptionsChangedListener() {
|
||||
@Override
|
||||
public void onSubscriptionsChanged() {
|
||||
/**
|
||||
* Reducing the processing time on main UI thread through a flag.
|
||||
* Once flag get into false, which means latest callback has been
|
||||
* processed.
|
||||
*/
|
||||
mSubscriptionsUpdateNotify.set(true);
|
||||
|
||||
// Back to main UI thread
|
||||
mContext.getMainExecutor().execute(() -> {
|
||||
// Remove notifications and perform checking.
|
||||
if (mSubscriptionsUpdateNotify.getAndSet(false)) {
|
||||
Integer subId = getAnyMissingSubscriptionId();
|
||||
if (subId != null) {
|
||||
onSubscriptionInactive(subId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void startMonitorSubscriptionChange() {
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr == null) {
|
||||
return;
|
||||
}
|
||||
// update monitor listener
|
||||
mSubscriptionsChangedListener = getChangeListener();
|
||||
|
||||
mgr.addOnSubscriptionsChangedListener(
|
||||
mExecutorService, mSubscriptionsChangedListener);
|
||||
}
|
||||
|
||||
// Implementation of AutoCloseable
|
||||
public void close() {
|
||||
if (mExecutorService == null) {
|
||||
return;
|
||||
}
|
||||
// Stop monitoring subscription change
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr != null) {
|
||||
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
|
||||
}
|
||||
// Release Executor
|
||||
mExecutorService.shutdownNow();
|
||||
mExecutorService = null;
|
||||
}
|
||||
}
|
||||
76
Settings/src/com/android/settings/RestrictedCheckBox.java
Normal file
76
Settings/src/com/android/settings/RestrictedCheckBox.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
/**
|
||||
* A checkbox that can be restricted by device policy, in which case it shows a dialog explaining
|
||||
* what component restricted it.
|
||||
*/
|
||||
public class RestrictedCheckBox extends CheckBox {
|
||||
private Context mContext;
|
||||
private boolean mDisabledByAdmin;
|
||||
private EnforcedAdmin mEnforcedAdmin;
|
||||
|
||||
public RestrictedCheckBox(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RestrictedCheckBox(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
if (mDisabledByAdmin) {
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
|
||||
return true;
|
||||
}
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
public void setDisabledByAdmin(EnforcedAdmin admin) {
|
||||
final boolean disabled = (admin != null);
|
||||
mEnforcedAdmin = admin;
|
||||
if (mDisabledByAdmin != disabled) {
|
||||
mDisabledByAdmin = disabled;
|
||||
RestrictedLockUtilsInternal.setTextViewAsDisabledByAdmin(mContext, this,
|
||||
mDisabledByAdmin);
|
||||
if (mDisabledByAdmin) {
|
||||
getButtonDrawable().setColorFilter(
|
||||
mContext.getColor(com.android.settingslib.R.color.disabled_text_color),
|
||||
PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
getButtonDrawable().clearColorFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDisabledByAdmin() {
|
||||
return mDisabledByAdmin;
|
||||
}
|
||||
}
|
||||
289
Settings/src/com/android/settings/RestrictedListPreference.java
Normal file
289
Settings/src/com/android/settings/RestrictedListPreference.java
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
import androidx.preference.ListPreferenceDialogFragmentCompat;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedPreferenceHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RestrictedListPreference extends CustomListPreference {
|
||||
private final RestrictedPreferenceHelper mHelper;
|
||||
private final List<RestrictedItem> mRestrictedItems = new ArrayList<>();
|
||||
private boolean mRequiresActiveUnlockedProfile = false;
|
||||
private int mProfileUserId;
|
||||
|
||||
public RestrictedListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mHelper = new RestrictedPreferenceHelper(context, this, attrs);
|
||||
}
|
||||
|
||||
public RestrictedListPreference(Context context, AttributeSet attrs,
|
||||
int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mHelper = new RestrictedPreferenceHelper(context, this, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
mHelper.onBindViewHolder(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performClick() {
|
||||
if (mRequiresActiveUnlockedProfile) {
|
||||
// Check if the profile is started, first.
|
||||
if (Utils.startQuietModeDialogIfNecessary(getContext(), UserManager.get(getContext()),
|
||||
mProfileUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next, check if the profile is unlocked.
|
||||
KeyguardManager manager =
|
||||
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
|
||||
if (manager.isDeviceLocked(mProfileUserId)) {
|
||||
Intent intent = manager.createConfirmDeviceCredentialIntent(
|
||||
null, null, mProfileUserId);
|
||||
getContext().startActivity(intent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mHelper.performClick()) {
|
||||
super.performClick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (enabled && isDisabledByAdmin()) {
|
||||
mHelper.setDisabledByAdmin(null);
|
||||
return;
|
||||
}
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setDisabledByAdmin(EnforcedAdmin admin) {
|
||||
if (mHelper.setDisabledByAdmin(admin)) {
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDisabledByAdmin() {
|
||||
return mHelper.isDisabledByAdmin();
|
||||
}
|
||||
|
||||
public void setRequiresActiveUnlockedProfile(boolean reqState) {
|
||||
mRequiresActiveUnlockedProfile = reqState;
|
||||
}
|
||||
|
||||
public void setProfileUserId(int profileUserId) {
|
||||
mProfileUserId = profileUserId;
|
||||
}
|
||||
|
||||
public boolean isRestrictedForEntry(CharSequence entry) {
|
||||
if (entry == null) {
|
||||
return false;
|
||||
}
|
||||
for (RestrictedItem item : mRestrictedItems) {
|
||||
if (entry.equals(item.entry)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addRestrictedItem(RestrictedItem item) {
|
||||
mRestrictedItems.add(item);
|
||||
}
|
||||
|
||||
public void clearRestrictedItems() {
|
||||
mRestrictedItems.clear();
|
||||
}
|
||||
|
||||
private RestrictedItem getRestrictedItemForEntryValue(CharSequence entryValue) {
|
||||
if (entryValue == null) {
|
||||
return null;
|
||||
}
|
||||
for (RestrictedItem item : mRestrictedItems) {
|
||||
if (entryValue.equals(item.entryValue)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ListAdapter createListAdapter(Context context) {
|
||||
return new RestrictedArrayAdapter(context, getEntries(),
|
||||
getSelectedValuePos());
|
||||
}
|
||||
|
||||
public int getSelectedValuePos() {
|
||||
final String selectedValue = getValue();
|
||||
final int selectedIndex =
|
||||
(selectedValue == null) ? -1 : findIndexOfValue(selectedValue);
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(Builder builder,
|
||||
DialogInterface.OnClickListener listener) {
|
||||
builder.setAdapter(createListAdapter(builder.getContext()), listener);
|
||||
}
|
||||
|
||||
public class RestrictedArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||
private final int mSelectedIndex;
|
||||
public RestrictedArrayAdapter(Context context, CharSequence[] objects, int selectedIndex) {
|
||||
super(context, R.layout.restricted_dialog_singlechoice, R.id.text1, objects);
|
||||
mSelectedIndex = selectedIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View root = super.getView(position, convertView, parent);
|
||||
CharSequence entry = getItem(position);
|
||||
CheckedTextView text = (CheckedTextView) root.findViewById(R.id.text1);
|
||||
if (isRestrictedForEntry(entry)) {
|
||||
text.setEnabled(false);
|
||||
text.setChecked(false);
|
||||
} else {
|
||||
if (mSelectedIndex != -1) {
|
||||
text.setChecked(position == mSelectedIndex);
|
||||
}
|
||||
if (!text.isEnabled()) {
|
||||
text.setEnabled(true);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RestrictedListPreferenceDialogFragment extends
|
||||
CustomListPreference.CustomListPreferenceDialogFragment {
|
||||
private int mLastCheckedPosition = AdapterView.INVALID_POSITION;
|
||||
|
||||
public static ListPreferenceDialogFragmentCompat newInstance(String key) {
|
||||
final ListPreferenceDialogFragmentCompat fragment
|
||||
= new RestrictedListPreferenceDialogFragment();
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private RestrictedListPreference getCustomizablePreference() {
|
||||
return (RestrictedListPreference) getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DialogInterface.OnClickListener getOnItemClickListener() {
|
||||
return new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final RestrictedListPreference preference = getCustomizablePreference();
|
||||
if (which < 0 || which >= preference.getEntryValues().length) {
|
||||
return;
|
||||
}
|
||||
String entryValue = preference.getEntryValues()[which].toString();
|
||||
RestrictedItem item = preference.getRestrictedItemForEntryValue(entryValue);
|
||||
if (item != null) {
|
||||
ListView listView = ((AlertDialog) dialog).getListView();
|
||||
listView.setItemChecked(getLastCheckedPosition(), true);
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
|
||||
item.enforcedAdmin);
|
||||
} else {
|
||||
setClickedDialogEntryIndex(which);
|
||||
}
|
||||
|
||||
if (getCustomizablePreference().isAutoClosePreference()) {
|
||||
/*
|
||||
* Clicking on an item simulates the positive button
|
||||
* click, and dismisses the dialog.
|
||||
*/
|
||||
RestrictedListPreferenceDialogFragment.this.onClick(dialog,
|
||||
DialogInterface.BUTTON_POSITIVE);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private int getLastCheckedPosition() {
|
||||
if (mLastCheckedPosition == AdapterView.INVALID_POSITION) {
|
||||
mLastCheckedPosition = ((RestrictedListPreference) getCustomizablePreference())
|
||||
.getSelectedValuePos();
|
||||
}
|
||||
return mLastCheckedPosition;
|
||||
}
|
||||
|
||||
private void setCheckedPosition(int checkedPosition) {
|
||||
mLastCheckedPosition = checkedPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setClickedDialogEntryIndex(int which) {
|
||||
super.setClickedDialogEntryIndex(which);
|
||||
mLastCheckedPosition = which;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RestrictedItem {
|
||||
public final CharSequence entry;
|
||||
public final CharSequence entryValue;
|
||||
public final EnforcedAdmin enforcedAdmin;
|
||||
|
||||
public RestrictedItem(CharSequence entry, CharSequence entryValue,
|
||||
EnforcedAdmin enforcedAdmin) {
|
||||
this.entry = entry;
|
||||
this.entryValue = entryValue;
|
||||
this.enforcedAdmin = enforcedAdmin;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Settings/src/com/android/settings/RestrictedRadioButton.java
Normal file
81
Settings/src/com/android/settings/RestrictedRadioButton.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
import com.android.settingslib.utils.ColorUtil;
|
||||
|
||||
public class RestrictedRadioButton extends RadioButton {
|
||||
private Context mContext;
|
||||
private boolean mDisabledByAdmin;
|
||||
private EnforcedAdmin mEnforcedAdmin;
|
||||
|
||||
public RestrictedRadioButton(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RestrictedRadioButton(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
|
||||
}
|
||||
|
||||
public RestrictedRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public RestrictedRadioButton(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
if (mDisabledByAdmin) {
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
|
||||
return true;
|
||||
}
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
public void setDisabledByAdmin(EnforcedAdmin admin) {
|
||||
final boolean disabled = (admin != null);
|
||||
mEnforcedAdmin = admin;
|
||||
if (mDisabledByAdmin != disabled) {
|
||||
mDisabledByAdmin = disabled;
|
||||
RestrictedLockUtilsInternal.setTextViewAsDisabledByAdmin(mContext,
|
||||
(TextView) this, mDisabledByAdmin);
|
||||
if (mDisabledByAdmin) {
|
||||
getButtonDrawable().setAlpha(
|
||||
(int) (255 * ColorUtil.getDisabledAlpha(mContext)));
|
||||
} else {
|
||||
getButtonDrawable().setAlpha(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDisabledByAdmin() {
|
||||
return mDisabledByAdmin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.RestrictionsManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
/**
|
||||
* Base class for settings screens that should be pin protected when in restricted mode or
|
||||
* that will display an admin support message in case an admin has disabled the options.
|
||||
* The constructor for this class will take the restriction key that this screen should be
|
||||
* locked by. If {@link RestrictionsManager.hasRestrictionsProvider()} and
|
||||
* {@link UserManager.hasUserRestriction()}, then the user will have to enter the restrictions
|
||||
* pin before seeing the Settings screen.
|
||||
*
|
||||
* If this settings screen should be pin protected whenever
|
||||
* {@link RestrictionsManager.hasRestrictionsProvider()} returns true, pass in
|
||||
* {@link RESTRICT_IF_OVERRIDABLE} to the constructor instead of a restrictions key.
|
||||
*
|
||||
* @deprecated Use {@link RestrictedDashboardFragment} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragment {
|
||||
|
||||
protected static final String RESTRICT_IF_OVERRIDABLE = "restrict_if_overridable";
|
||||
|
||||
// No RestrictedSettingsFragment screens should use this number in startActivityForResult.
|
||||
@VisibleForTesting static final int REQUEST_PIN_CHALLENGE = 12309;
|
||||
|
||||
private static final String KEY_CHALLENGE_SUCCEEDED = "chsc";
|
||||
private static final String KEY_CHALLENGE_REQUESTED = "chrq";
|
||||
|
||||
// If the restriction PIN is entered correctly.
|
||||
private boolean mChallengeSucceeded;
|
||||
private boolean mChallengeRequested;
|
||||
|
||||
private UserManager mUserManager;
|
||||
private RestrictionsManager mRestrictionsManager;
|
||||
|
||||
private final String mRestrictionKey;
|
||||
private EnforcedAdmin mEnforcedAdmin;
|
||||
private TextView mEmptyTextView;
|
||||
|
||||
private boolean mOnlyAvailableForAdmins = false;
|
||||
private boolean mIsAdminUser;
|
||||
|
||||
// Receiver to clear pin status when the screen is turned off.
|
||||
private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mChallengeRequested) {
|
||||
mChallengeSucceeded = false;
|
||||
mChallengeRequested = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
AlertDialog mActionDisabledDialog;
|
||||
|
||||
/**
|
||||
* @param restrictionKey The restriction key to check before pin protecting
|
||||
* this settings page. Pass in {@link RESTRICT_IF_OVERRIDABLE} if it should
|
||||
* be protected whenever a restrictions provider is set. Pass in
|
||||
* null if it should never be protected.
|
||||
*/
|
||||
public RestrictedSettingsFragment(String restrictionKey) {
|
||||
mRestrictionKey = restrictionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
mRestrictionsManager = (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
mIsAdminUser = mUserManager.isAdminUser();
|
||||
|
||||
if (icicle != null) {
|
||||
mChallengeSucceeded = icicle.getBoolean(KEY_CHALLENGE_SUCCEEDED, false);
|
||||
mChallengeRequested = icicle.getBoolean(KEY_CHALLENGE_REQUESTED, false);
|
||||
}
|
||||
|
||||
IntentFilter offFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
|
||||
offFilter.addAction(Intent.ACTION_USER_PRESENT);
|
||||
getActivity().registerReceiver(mScreenOffReceiver, offFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mEmptyTextView = initEmptyTextView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
if (getActivity().isChangingConfigurations()) {
|
||||
outState.putBoolean(KEY_CHALLENGE_REQUESTED, mChallengeRequested);
|
||||
outState.putBoolean(KEY_CHALLENGE_SUCCEEDED, mChallengeSucceeded);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (shouldBeProviderProtected(mRestrictionKey)) {
|
||||
ensurePin();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
getActivity().unregisterReceiver(mScreenOffReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_PIN_CHALLENGE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
mChallengeSucceeded = true;
|
||||
mChallengeRequested = false;
|
||||
if (mActionDisabledDialog != null && mActionDisabledDialog.isShowing()) {
|
||||
mActionDisabledDialog.setOnDismissListener(null);
|
||||
mActionDisabledDialog.dismiss();
|
||||
}
|
||||
} else {
|
||||
mChallengeSucceeded = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
private void ensurePin() {
|
||||
if (!mChallengeSucceeded && !mChallengeRequested
|
||||
&& mRestrictionsManager.hasRestrictionsProvider()) {
|
||||
Intent intent = mRestrictionsManager.createLocalApprovalIntent();
|
||||
if (intent != null) {
|
||||
mChallengeRequested = true;
|
||||
mChallengeSucceeded = false;
|
||||
PersistableBundle request = new PersistableBundle();
|
||||
request.putString(RestrictionsManager.REQUEST_KEY_MESSAGE,
|
||||
getResources().getString(R.string.restr_pin_enter_admin_pin));
|
||||
intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, request);
|
||||
startActivityForResult(intent, REQUEST_PIN_CHALLENGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this activity is restricted, but no restrictions provider has been set.
|
||||
* Used to determine if the settings UI should disable UI.
|
||||
*/
|
||||
protected boolean isRestrictedAndNotProviderProtected() {
|
||||
if (mRestrictionKey == null || RESTRICT_IF_OVERRIDABLE.equals(mRestrictionKey)) {
|
||||
return false;
|
||||
}
|
||||
return mUserManager.hasUserRestriction(mRestrictionKey)
|
||||
&& !mRestrictionsManager.hasRestrictionsProvider();
|
||||
}
|
||||
|
||||
protected boolean hasChallengeSucceeded() {
|
||||
return (mChallengeRequested && mChallengeSucceeded) || !mChallengeRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this restrictions key is locked down.
|
||||
*/
|
||||
protected boolean shouldBeProviderProtected(String restrictionKey) {
|
||||
if (restrictionKey == null) {
|
||||
return false;
|
||||
}
|
||||
boolean restricted = RESTRICT_IF_OVERRIDABLE.equals(restrictionKey)
|
||||
|| mUserManager.hasUserRestriction(mRestrictionKey);
|
||||
return restricted && mRestrictionsManager.hasRestrictionsProvider();
|
||||
}
|
||||
|
||||
protected TextView initEmptyTextView() {
|
||||
TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
|
||||
return emptyView;
|
||||
}
|
||||
|
||||
public EnforcedAdmin getRestrictionEnforcedAdmin() {
|
||||
mEnforcedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getActivity(),
|
||||
mRestrictionKey, UserHandle.myUserId());
|
||||
if (mEnforcedAdmin != null && mEnforcedAdmin.user == null) {
|
||||
mEnforcedAdmin.user = UserHandle.of(UserHandle.myUserId());
|
||||
}
|
||||
return mEnforcedAdmin;
|
||||
}
|
||||
|
||||
public TextView getEmptyTextView() {
|
||||
return mEmptyTextView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDataSetChanged() {
|
||||
highlightPreferenceIfNeeded();
|
||||
if (isUiRestrictedByOnlyAdmin()
|
||||
&& (mActionDisabledDialog == null || !mActionDisabledDialog.isShowing())) {
|
||||
final EnforcedAdmin admin = getRestrictionEnforcedAdmin();
|
||||
mActionDisabledDialog = new ActionDisabledByAdminDialogHelper(getActivity())
|
||||
.prepareDialogBuilder(mRestrictionKey, admin)
|
||||
.setOnDismissListener(__ -> getActivity().finish())
|
||||
.show();
|
||||
setEmptyView(new View(getContext()));
|
||||
} else if (mEmptyTextView != null) {
|
||||
setEmptyView(mEmptyTextView);
|
||||
}
|
||||
super.onDataSetChanged();
|
||||
}
|
||||
|
||||
public void setIfOnlyAvailableForAdmins(boolean onlyForAdmins) {
|
||||
mOnlyAvailableForAdmins = onlyForAdmins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether restricted or actionable UI elements should be removed or disabled.
|
||||
*/
|
||||
protected boolean isUiRestricted() {
|
||||
return isRestrictedAndNotProviderProtected() || !hasChallengeSucceeded()
|
||||
|| (!mIsAdminUser && mOnlyAvailableForAdmins);
|
||||
}
|
||||
|
||||
protected boolean isUiRestrictedByOnlyAdmin() {
|
||||
return isUiRestricted() && !mUserManager.hasBaseUserRestriction(mRestrictionKey,
|
||||
UserHandle.of(UserHandle.myUserId())) && (mIsAdminUser || !mOnlyAvailableForAdmins);
|
||||
}
|
||||
}
|
||||
325
Settings/src/com/android/settings/RingtonePreference.java
Normal file
325
Settings/src/com/android/settings/RingtonePreference.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings.System;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
/**
|
||||
* A {@link Preference} that allows the user to choose a ringtone from those on the device.
|
||||
* The chosen ringtone's URI will be persisted as a string.
|
||||
* <p>
|
||||
* If the user chooses the "Default" item, the saved string will be one of
|
||||
* {@link System#DEFAULT_RINGTONE_URI},
|
||||
* {@link System#DEFAULT_NOTIFICATION_URI}, or
|
||||
* {@link System#DEFAULT_ALARM_ALERT_URI}. If the user chooses the "Silent"
|
||||
* item, the saved string will be an empty string.
|
||||
*
|
||||
* @attr ref android.R.styleable#RingtonePreference_ringtoneType
|
||||
* @attr ref android.R.styleable#RingtonePreference_showDefault
|
||||
* @attr ref android.R.styleable#RingtonePreference_showSilent
|
||||
*
|
||||
* Based of frameworks/base/core/java/android/preference/RingtonePreference.java
|
||||
* but extends androidx.preference.Preference instead.
|
||||
*/
|
||||
public class RingtonePreference extends Preference {
|
||||
|
||||
private static final String TAG = "RingtonePreference";
|
||||
|
||||
private int mRingtoneType;
|
||||
private boolean mShowDefault;
|
||||
private boolean mShowSilent;
|
||||
|
||||
private int mRequestCode;
|
||||
protected int mUserId;
|
||||
protected Context mUserContext;
|
||||
|
||||
public RingtonePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
com.android.internal.R.styleable.RingtonePreference, 0, 0);
|
||||
mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
|
||||
RingtoneManager.TYPE_RINGTONE);
|
||||
mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
|
||||
true);
|
||||
mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
|
||||
true);
|
||||
setIntent(new Intent(RingtoneManager.ACTION_RINGTONE_PICKER));
|
||||
setUserId(UserHandle.myUserId());
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setUserId(int userId) {
|
||||
mUserId = userId;
|
||||
mUserContext = Utils.createPackageContextAsUser(getContext(), mUserId);
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound type(s) that are shown in the picker.
|
||||
*
|
||||
* @return The sound type(s) that are shown in the picker.
|
||||
* @see #setRingtoneType(int)
|
||||
*/
|
||||
public int getRingtoneType() {
|
||||
return mRingtoneType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sound type(s) that are shown in the picker.
|
||||
*
|
||||
* @param type The sound type(s) that are shown in the picker.
|
||||
* @see RingtoneManager#EXTRA_RINGTONE_TYPE
|
||||
*/
|
||||
public void setRingtoneType(int type) {
|
||||
mRingtoneType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to a show an item for the default sound/ringtone.
|
||||
*
|
||||
* @return Whether to show an item for the default sound/ringtone.
|
||||
*/
|
||||
public boolean getShowDefault() {
|
||||
return mShowDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an item for the default sound/ringtone. The default
|
||||
* to use will be deduced from the sound type(s) being shown.
|
||||
*
|
||||
* @param showDefault Whether to show the default or not.
|
||||
* @see RingtoneManager#EXTRA_RINGTONE_SHOW_DEFAULT
|
||||
*/
|
||||
public void setShowDefault(boolean showDefault) {
|
||||
mShowDefault = showDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to a show an item for 'Silent'.
|
||||
*
|
||||
* @return Whether to show an item for 'Silent'.
|
||||
*/
|
||||
public boolean getShowSilent() {
|
||||
return mShowSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an item for 'Silent'.
|
||||
*
|
||||
* @param showSilent Whether to show 'Silent'.
|
||||
* @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
|
||||
*/
|
||||
public void setShowSilent(boolean showSilent) {
|
||||
mShowSilent = showSilent;
|
||||
}
|
||||
|
||||
public int getRequestCode() {
|
||||
return mRequestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the intent to launch the ringtone picker. This can be modified
|
||||
* to adjust the parameters of the ringtone picker.
|
||||
*
|
||||
* @param ringtonePickerIntent The ringtone picker intent that can be
|
||||
* modified by putting extras.
|
||||
*/
|
||||
public void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
|
||||
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
|
||||
onRestoreRingtone());
|
||||
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
|
||||
if (mShowDefault) {
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
||||
RingtoneManager.getDefaultUri(getRingtoneType()));
|
||||
}
|
||||
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, mRingtoneType);
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getTitle());
|
||||
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
|
||||
AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a ringtone is chosen.
|
||||
* <p>
|
||||
* By default, this saves the ringtone URI to the persistent storage as a
|
||||
* string.
|
||||
*
|
||||
* @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
|
||||
*/
|
||||
protected void onSaveRingtone(Uri ringtoneUri) {
|
||||
persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the chooser is about to be shown and the current ringtone
|
||||
* should be marked. Can return null to not mark any ringtone.
|
||||
* <p>
|
||||
* By default, this restores the previous ringtone URI from the persistent
|
||||
* storage.
|
||||
*
|
||||
* @return The ringtone to be marked as the current ringtone.
|
||||
*/
|
||||
protected Uri onRestoreRingtone() {
|
||||
final String uriString = getPersistedString(null);
|
||||
return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValueObj) {
|
||||
String defaultValue = (String) defaultValueObj;
|
||||
|
||||
/*
|
||||
* This method is normally to make sure the internal state and UI
|
||||
* matches either the persisted value or the default value. Since we
|
||||
* don't show the current value in the UI (until the dialog is opened)
|
||||
* and we don't keep local state, if we are restoring the persisted
|
||||
* value we don't need to do anything.
|
||||
*/
|
||||
if (restorePersistedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are setting to the default value, we should persist it.
|
||||
if (!TextUtils.isEmpty(defaultValue)) {
|
||||
onSaveRingtone(Uri.parse(defaultValue));
|
||||
}
|
||||
}
|
||||
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
|
||||
super.onAttachedToHierarchy(preferenceManager);
|
||||
}
|
||||
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (data != null) {
|
||||
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
||||
|
||||
if (callChangeListener(uri != null ? uri.toString() : "")) {
|
||||
onSaveRingtone(uri);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDefaultRingtone(Uri ringtoneUri) {
|
||||
// null URIs are valid (None/silence)
|
||||
return ringtoneUri == null || RingtoneManager.isDefault(ringtoneUri);
|
||||
}
|
||||
|
||||
protected boolean isValidRingtoneUri(Uri ringtoneUri) {
|
||||
if (isDefaultRingtone(ringtoneUri)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return early for android resource URIs
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(ringtoneUri.getScheme())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
|
||||
if (mimeType == null) {
|
||||
Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
|
||||
+ " failed: failure to find mimeType (no access from this context?)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg")
|
||||
|| mimeType.equals("application/x-flac"))) {
|
||||
Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
|
||||
+ " failed: associated mimeType:" + mimeType + " is not an audio type");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate userId from URIs: content://{userId}@...
|
||||
final int userIdFromUri = ContentProvider.getUserIdFromUri(ringtoneUri, mUserId);
|
||||
if (userIdFromUri != mUserId) {
|
||||
final UserManager userManager = mUserContext.getSystemService(UserManager.class);
|
||||
|
||||
if (!userManager.isSameProfileGroup(mUserId, userIdFromUri)) {
|
||||
Log.e(TAG,
|
||||
"isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + userIdFromUri
|
||||
+ " and user " + mUserId + " are not in the same profile group");
|
||||
return false;
|
||||
}
|
||||
|
||||
final int parentUserId;
|
||||
final int profileUserId;
|
||||
if (userManager.isProfile()) {
|
||||
profileUserId = mUserId;
|
||||
parentUserId = userIdFromUri;
|
||||
} else {
|
||||
parentUserId = mUserId;
|
||||
profileUserId = userIdFromUri;
|
||||
}
|
||||
|
||||
final UserHandle parent = userManager.getProfileParent(UserHandle.of(profileUserId));
|
||||
if (parent == null || parent.getIdentifier() != parentUserId) {
|
||||
Log.e(TAG,
|
||||
"isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + profileUserId
|
||||
+ " is not a profile of user " + parentUserId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow parent <-> managed profile sharing, unless restricted
|
||||
if (userManager.hasUserRestrictionForUser(
|
||||
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserHandle.of(parentUserId))) {
|
||||
Log.e(TAG,
|
||||
"isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + parentUserId
|
||||
+ " has restriction: " + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(userManager.isManagedProfile(profileUserId) || userManager.getUserProperties(
|
||||
UserHandle.of(profileUserId)).isMediaSharedWithParent())) {
|
||||
Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
|
||||
+ " failed: user " + profileUserId + " is not a cloned or managed profile");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import com.android.settingslib.CustomDialogPreferenceCompat;
|
||||
|
||||
/**
|
||||
* Based on frameworks/base/core/java/android/preference/SeekBarDialogPreference.java
|
||||
* except uses support lib preferences.
|
||||
*/
|
||||
public class SeekBarDialogPreference extends CustomDialogPreferenceCompat {
|
||||
private final Drawable mMyIcon;
|
||||
|
||||
public SeekBarDialogPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setDialogLayoutResource(R.layout.preference_dialog_seekbar_material);
|
||||
|
||||
createActionButtons();
|
||||
|
||||
// Steal the XML dialogIcon attribute's value
|
||||
mMyIcon = getDialogIcon();
|
||||
|
||||
setDialogIcon(null);
|
||||
}
|
||||
|
||||
public SeekBarDialogPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
// Allow subclasses to override the action buttons
|
||||
public void createActionButtons() {
|
||||
setPositiveButtonText(android.R.string.ok);
|
||||
setNegativeButtonText(android.R.string.cancel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
|
||||
if (mMyIcon != null) {
|
||||
iconView.setImageDrawable(mMyIcon);
|
||||
} else {
|
||||
iconView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
protected static SeekBar getSeekBar(View dialogView) {
|
||||
return (SeekBar) dialogView.findViewById(R.id.seekbar);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Interface for classes whose instances can provide the availability of the preference.
|
||||
*/
|
||||
public interface SelfAvailablePreference {
|
||||
/**
|
||||
* @return the availability of the preference. Please make sure the availability in managed
|
||||
* profile is taken into account.
|
||||
*/
|
||||
boolean isAvailable(Context context);
|
||||
}
|
||||
116
Settings/src/com/android/settings/SetFullBackupPassword.java
Normal file
116
Settings/src/com/android/settings/SetFullBackupPassword.java
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.backup.IBackupManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class SetFullBackupPassword extends Activity {
|
||||
static final String TAG = "SetFullBackupPassword";
|
||||
|
||||
IBackupManager mBackupManager;
|
||||
TextView mCurrentPw, mNewPw, mConfirmNewPw;
|
||||
Button mCancel, mSet;
|
||||
|
||||
OnClickListener mButtonListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == mSet) {
|
||||
final String curPw = mCurrentPw.getText().toString();
|
||||
final String newPw = mNewPw.getText().toString();
|
||||
final String confirmPw = mConfirmNewPw.getText().toString();
|
||||
|
||||
if (!newPw.equals(confirmPw)) {
|
||||
// Mismatch between new pw and its confirmation re-entry
|
||||
Log.i(TAG, "password mismatch");
|
||||
Toast.makeText(SetFullBackupPassword.this,
|
||||
com.android.settingslib.R
|
||||
.string.local_backup_password_toast_confirmation_mismatch,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: should we distinguish cases of has/hasn't set a pw before?
|
||||
|
||||
if (setBackupPassword(curPw, newPw)) {
|
||||
// success
|
||||
Log.i(TAG, "password set successfully");
|
||||
Toast.makeText(SetFullBackupPassword.this,
|
||||
com.android.settingslib.R.string.local_backup_password_toast_success,
|
||||
Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} else {
|
||||
// failure -- bad existing pw, usually
|
||||
Log.i(TAG, "failure; password mismatch?");
|
||||
Toast.makeText(SetFullBackupPassword.this,
|
||||
com.android.settingslib.R
|
||||
.string.local_backup_password_toast_validation_failure,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} else if (v == mCancel) {
|
||||
finish();
|
||||
} else {
|
||||
Log.w(TAG, "Click on unknown view");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
|
||||
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
|
||||
|
||||
setContentView(R.layout.set_backup_pw);
|
||||
|
||||
mCurrentPw = (TextView) findViewById(R.id.current_backup_pw);
|
||||
mNewPw = (TextView) findViewById(R.id.new_backup_pw);
|
||||
mConfirmNewPw = (TextView) findViewById(R.id.confirm_new_backup_pw);
|
||||
|
||||
mCancel = (Button) findViewById(R.id.backup_pw_cancel_button);
|
||||
mSet = (Button) findViewById(R.id.backup_pw_set_button);
|
||||
|
||||
mCancel.setOnClickListener(mButtonListener);
|
||||
mSet.setOnClickListener(mButtonListener);
|
||||
}
|
||||
|
||||
private boolean setBackupPassword(String currentPw, String newPw) {
|
||||
// new password can't be empty
|
||||
if (TextUtils.isEmpty(newPw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return mBackupManager.setBackupPassword(currentPw, newPw);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to communicate with backup manager");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
492
Settings/src/com/android/settings/Settings.java
Normal file
492
Settings/src/com/android/settings/Settings.java
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import static android.provider.Settings.ACTION_PRIVACY_SETTINGS;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.ims.ImsRcsManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.biometrics.face.FaceSettings;
|
||||
import com.android.settings.communal.CommunalPreferenceController;
|
||||
import com.android.settings.enterprise.EnterprisePrivacySettings;
|
||||
import com.android.settings.network.MobileNetworkIntentConverter;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
|
||||
import com.android.settings.security.SecuritySettingsFeatureProvider;
|
||||
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
/**
|
||||
* Top-level Settings activity
|
||||
*/
|
||||
public class Settings extends SettingsActivity {
|
||||
|
||||
/*
|
||||
* Settings subclasses for launching independently.
|
||||
*/
|
||||
|
||||
public static class MemtagPageActivity extends SettingsActivity { /* empty */}
|
||||
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class CreateShortcutActivity extends SettingsActivity { /* empty */ }
|
||||
public static class FaceSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Container for {@link FaceSettings} to use with a pre-defined task affinity. */
|
||||
public static class FaceSettingsInternalActivity extends SettingsActivity { /* empty */ }
|
||||
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class FingerprintSettingsActivityV2 extends SettingsActivity { /* empty */ }
|
||||
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PrivateSpaceBiometricSettingsActivity extends SettingsActivity {
|
||||
/* empty */
|
||||
}
|
||||
public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for Data saver settings. */
|
||||
public static class DataSaverSummaryActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NetworkProviderSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NetworkSelectActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the Wi-Fi network details settings. */
|
||||
public static class WifiDetailsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }
|
||||
public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PhysicalKeyboardActivity extends SettingsActivity { /* empty */ }
|
||||
public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LanguageSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the regional preferences settings. */
|
||||
public static class RegionalPreferencesActivity extends SettingsActivity { /* empty */ }
|
||||
public static class KeyboardSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the navigation mode settings. */
|
||||
public static class NavigationModeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ }
|
||||
public static class SmartAutoRotateSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class MyDeviceInfoActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ModuleLicensesActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ManageAssistActivity extends SettingsActivity { /* empty */ }
|
||||
public static class HighPowerApplicationsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BackgroundCheckSummaryActivity extends SettingsActivity { /* empty */ }
|
||||
public static class StorageUseActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilityDetailsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilityEditShortcutsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilityInversionSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilityContrastSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccessibilityDaltonizerSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for lockscreen settings. */
|
||||
public static class LockScreenSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for bluetooth pairing settings. */
|
||||
public static class BlueToothPairingActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for Reduce Bright Colors. */
|
||||
public static class ReduceBrightColorsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for text reading settings. */
|
||||
public static class TextReadingSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for text color and motion settings. */
|
||||
public static class ColorAndMotionActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for color contrast settings. */
|
||||
public static class ColorContrastActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the security dashboard. */
|
||||
public static class SecurityDashboardActivity extends SettingsActivity {
|
||||
|
||||
private static final String TAG = "SecurityDashboardActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
handleSafetyCenterRedirection();
|
||||
}
|
||||
|
||||
/** Redirects to SafetyCenter if enabled. */
|
||||
@VisibleForTesting
|
||||
public void handleSafetyCenterRedirection() {
|
||||
if (isFinishing()) {
|
||||
// Don't trampoline if already exiting this activity.
|
||||
return;
|
||||
}
|
||||
|
||||
if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
|
||||
finish();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open safety center", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the given fragment is allowed. */
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public boolean isValidFragment(String fragmentName) {
|
||||
return super.isValidFragment(fragmentName)
|
||||
|| (fragmentName != null
|
||||
&& TextUtils.equals(fragmentName, getAlternativeFragmentName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitialFragmentName(Intent intent) {
|
||||
final String alternativeFragmentName = getAlternativeFragmentName();
|
||||
if (alternativeFragmentName != null) {
|
||||
return alternativeFragmentName;
|
||||
}
|
||||
|
||||
return super.getInitialFragmentName(intent);
|
||||
}
|
||||
|
||||
private String getAlternativeFragmentName() {
|
||||
String alternativeFragmentClassname = null;
|
||||
final SecuritySettingsFeatureProvider securitySettingsFeatureProvider =
|
||||
FeatureFactory.getFeatureFactory().getSecuritySettingsFeatureProvider();
|
||||
if (securitySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()) {
|
||||
alternativeFragmentClassname = securitySettingsFeatureProvider
|
||||
.getAlternativeSecuritySettingsFragmentClassname();
|
||||
}
|
||||
return alternativeFragmentClassname;
|
||||
}
|
||||
}
|
||||
/** Activity for the Advanced security settings. */
|
||||
public static class SecurityAdvancedSettings extends SettingsActivity {
|
||||
private static final String TAG = "SecurityAdvancedActivity";
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
handleMoreSettingsRedirection();
|
||||
}
|
||||
|
||||
/** Redirects to More Settings if Safety center is enabled. */
|
||||
@VisibleForTesting
|
||||
public void handleMoreSettingsRedirection() {
|
||||
if (isFinishing()) {
|
||||
// Don't trampoline if already exiting this activity.
|
||||
return;
|
||||
}
|
||||
|
||||
if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
|
||||
try {
|
||||
startActivity(
|
||||
new Intent("com.android.settings.MORE_SECURITY_PRIVACY_SETTINGS"));
|
||||
finish();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open More Settings", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Activity for the More settings page. */
|
||||
public static class MoreSecurityPrivacySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppUsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LocationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ScanningSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiScanningSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the privacy dashboard. */
|
||||
public static class PrivacyDashboardActivity extends SettingsActivity {
|
||||
|
||||
private static final String TAG = "PrivacyDashboardActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
handleSafetyCenterRedirection();
|
||||
}
|
||||
|
||||
/** Redirects to SafetyCenter if enabled. */
|
||||
@VisibleForTesting
|
||||
public void handleSafetyCenterRedirection() {
|
||||
if (isFinishing()) {
|
||||
// Don't trampoline if already exiting this activity.
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_PRIVACY_SETTINGS.equals(getIntent().getAction())
|
||||
&& SafetyCenterManagerWrapper.get().isEnabled(this)) {
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
|
||||
finish();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open safety center", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class PrivacyControlsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PrivacySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class FactoryResetActivity extends SettingsActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolbarEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static class FactoryResetConfirmActivity extends SettingsActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolbarEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static class RunningServicesActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BatterySaverSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BatterySaverScheduleSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccountSyncSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AccountSyncSettingsInAddAccountActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DeviceAdminSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DataUsageSummaryActivity extends SettingsActivity { /* empty */ }
|
||||
public static class MobileDataUsageListActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ConfigureWifiSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class SavedAccessPointsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class TextToSpeechSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiDisplaySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DreamSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage communal settings */
|
||||
public static class CommunalSettingsActivity extends SettingsActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!CommunalPreferenceController.isAvailable(this)) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NotificationAccessDetailsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PremiumSmsAccessActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class TurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppTurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenAccessDetailSettingsActivity extends SettingsActivity {}
|
||||
public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UsbDetailsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class TrustedCredentialsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PaymentSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeBehaviorSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeBlockedEffectsSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeAutomationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ConversationListSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ }
|
||||
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage Cloned Apps page */
|
||||
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage Aspect Ratio app list page */
|
||||
public static class UserAspectRatioAppListActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage Aspect Ratio app page */
|
||||
public static class UserAspectRatioAppActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NotificationReviewPermissionsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ChannelGroupNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ManageDomainUrlsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AutomaticStorageManagerSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class GamesStorageActivity extends SettingsActivity { /* empty */ }
|
||||
public static class GestureNavigationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage 2-/3-button navigation configuration. */
|
||||
public static class ButtonNavigationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class InteractAcrossProfilesSettingsActivity extends SettingsActivity {
|
||||
/* empty */
|
||||
}
|
||||
public static class AppInteractAcrossProfilesSettingsActivity extends SettingsActivity {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
public static class SatelliteSettingActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppMemoryUsageActivity extends SettingsActivity { /* empty */ }
|
||||
public static class OverlaySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ManageExternalStorageActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppManageExternalStorageActivity extends SettingsActivity { /* empty */ }
|
||||
public static class MediaManagementAppsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppMediaManagementAppsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage NFC Tag applications. */
|
||||
public static class ChangeNfcTagAppsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity to manage app battery usage details. */
|
||||
public static class AppBatteryUsageActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
public static class ManageExternalSourcesActivity extends SettingsActivity {/* empty */ }
|
||||
public static class ManageAppExternalSourcesActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WallpaperSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ManagedProfileSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DeletionHelperActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
/** Actviity to manage apps with {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} */
|
||||
public static class AlarmsAndRemindersActivity extends SettingsActivity {/* empty */ }
|
||||
/** App specific version of {@link AlarmsAndRemindersActivity} */
|
||||
public static class AlarmsAndRemindersAppActivity extends SettingsActivity {/* empty */ }
|
||||
|
||||
public static class ApnEditorActivity extends SettingsActivity { /* empty */ }
|
||||
public static class ChooseAccountActivity extends SettingsActivity { /* empty */ }
|
||||
public static class IccLockSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class TestingSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiAPITestActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiInfoActivity extends SettingsActivity { /* empty */ }
|
||||
public static class EnterprisePrivacySettingsActivity extends SettingsActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (FeatureFactory.getFeatureFactory().getEnterprisePrivacyFeatureProvider()
|
||||
.showParentalControls()) {
|
||||
finish();
|
||||
} else if (!EnterprisePrivacySettings.isPageEnabled(this)) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class WebViewAppPickerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class AdvancedConnectedDeviceActivity extends SettingsActivity { /* empty */ }
|
||||
public static class NfcSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BluetoothDeviceDetailActivity extends SettingsActivity { /* empty */ }
|
||||
public static class StylusUsiDetailsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BluetoothBroadcastActivity extends SettingsActivity { /* empty */ }
|
||||
public static class BluetoothFindBroadcastsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class WifiCallingDisclaimerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class MobileNetworkListActivity extends SettingsActivity {}
|
||||
public static class PowerMenuSettingsActivity extends SettingsActivity {}
|
||||
public static class MobileNetworkActivity extends SettingsActivity {
|
||||
|
||||
public static final String TAG = "MobileNetworkActivity";
|
||||
public static final String EXTRA_MMS_MESSAGE = "mms_message";
|
||||
public static final String EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN =
|
||||
"show_capability_discovery_opt_in";
|
||||
|
||||
private MobileNetworkIntentConverter mIntentConverter;
|
||||
|
||||
/**
|
||||
* Override of #onNewIntent() requires Activity to have "singleTop" launch mode within
|
||||
* AndroidManifest.xml
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
Log.d(TAG, "Starting onNewIntent");
|
||||
|
||||
createUiFromIntent(null /* savedState */, convertIntent(intent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
return convertIntent(super.getIntent());
|
||||
}
|
||||
|
||||
private Intent convertIntent(Intent copyFrom) {
|
||||
if (mIntentConverter == null) {
|
||||
mIntentConverter = new MobileNetworkIntentConverter(this);
|
||||
}
|
||||
Intent intent = mIntentConverter.apply(copyFrom);
|
||||
return (intent == null) ? copyFrom : intent;
|
||||
}
|
||||
|
||||
public static boolean doesIntentContainOptInAction(Intent intent) {
|
||||
String intentAction = (intent != null ? intent.getAction() : null);
|
||||
return TextUtils.equals(intentAction,
|
||||
ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN);
|
||||
}
|
||||
}
|
||||
|
||||
/** Actviity to manage apps with {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} */
|
||||
public static class LongBackgroundTasksActivity extends SettingsActivity { /* empty */ }
|
||||
/** App specific version of {@link LongBackgroundTasksActivity} */
|
||||
public static class LongBackgroundTasksAppActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
/**
|
||||
* Activity for BugReportHandlerPicker.
|
||||
*/
|
||||
public static class BugReportHandlerPickerActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
// Top level categories for new IA
|
||||
public static class NetworkDashboardActivity extends SettingsActivity {}
|
||||
public static class ConnectedDeviceDashboardActivity extends SettingsActivity {}
|
||||
public static class PowerUsageSummaryActivity extends SettingsActivity { /* empty */ }
|
||||
public static class StorageDashboardActivity extends SettingsActivity {}
|
||||
public static class AccountDashboardActivity extends SettingsActivity {}
|
||||
public static class SystemDashboardActivity extends SettingsActivity {}
|
||||
|
||||
/**
|
||||
* Activity for MediaControlsSettings
|
||||
*/
|
||||
public static class MediaControlsSettingsActivity extends SettingsActivity {}
|
||||
|
||||
/**
|
||||
* Activity for AppDashboard.
|
||||
*/
|
||||
public static class AppDashboardActivity extends SettingsActivity {}
|
||||
|
||||
public static class AdaptiveBrightnessActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
/**
|
||||
* Activity for OneHandedSettings
|
||||
*/
|
||||
public static class OneHandedSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
public static class PreviouslyConnectedDeviceActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
public static class ScreenTimeoutActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
/** Activity for the Reset mobile network settings. */
|
||||
public static class ResetMobileNetworkSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
}
|
||||
865
Settings/src/com/android/settings/SettingsActivity.java
Normal file
865
Settings/src/com/android/settings/SettingsActivity.java
Normal file
@@ -0,0 +1,865 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink;
|
||||
import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.permission.flags.Flags;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.settings.Settings.WifiSettingsActivity;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||
import com.android.settings.connecteddevice.NfcAndPaymentFragment;
|
||||
import com.android.settings.core.OnActivityResultListener;
|
||||
import com.android.settings.core.SettingsBaseActivity;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.core.gateway.SettingsGateway;
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||
import com.android.settings.homepage.SettingsHomepageActivity;
|
||||
import com.android.settings.homepage.TopLevelSettings;
|
||||
import com.android.settings.nfc.PaymentSettings;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.password.PasswordUtils;
|
||||
import com.android.settings.wfd.WifiDisplaySettings;
|
||||
import com.android.settings.widget.SettingsMainSwitchBar;
|
||||
import com.android.settingslib.core.instrumentation.Instrumentable;
|
||||
import com.android.settingslib.core.instrumentation.SharedPreferencesLogger;
|
||||
import com.android.settingslib.drawer.DashboardCategory;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SettingsActivity extends SettingsBaseActivity
|
||||
implements PreferenceManager.OnPreferenceTreeClickListener,
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||
ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
private static final String LOG_TAG = "SettingsActivity";
|
||||
|
||||
// Constants for state save/restore
|
||||
private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
|
||||
|
||||
/**
|
||||
* When starting this activity, the invoking Intent can contain this extra
|
||||
* string to specify which fragment should be initially displayed.
|
||||
* <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
|
||||
* will call isValidFragment() to confirm that the fragment class name is valid for this
|
||||
* activity.
|
||||
*/
|
||||
public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
|
||||
|
||||
/**
|
||||
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
|
||||
* this extra can also be specified to supply a Bundle of arguments to pass
|
||||
* to that fragment when it is instantiated during the initial creation
|
||||
* of the activity.
|
||||
*/
|
||||
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
|
||||
|
||||
/**
|
||||
* Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
|
||||
*/
|
||||
public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
|
||||
|
||||
// extras that allow any preference activity to be launched as part of a wizard
|
||||
|
||||
// show Back and Next buttons? takes boolean parameter
|
||||
// Back will then return RESULT_CANCELED and Next RESULT_OK
|
||||
protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
|
||||
|
||||
// add a Skip button?
|
||||
private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
|
||||
|
||||
// specify custom text for the Back or Next buttons, or cause a button to not appear
|
||||
// at all by setting it to null
|
||||
protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
|
||||
protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
|
||||
|
||||
/**
|
||||
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
|
||||
* those extra can also be specify to supply the title or title res id to be shown for
|
||||
* that fragment.
|
||||
*/
|
||||
public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
|
||||
/**
|
||||
* The package name used to resolve the title resource id.
|
||||
*/
|
||||
public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME =
|
||||
":settings:show_fragment_title_res_package_name";
|
||||
public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
|
||||
":settings:show_fragment_title_resid";
|
||||
|
||||
public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
|
||||
":settings:show_fragment_as_subsetting";
|
||||
public static final String EXTRA_IS_SECOND_LAYER_PAGE = ":settings:is_second_layer_page";
|
||||
|
||||
/**
|
||||
* Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK.
|
||||
* Set true when the deep link intent is from a slice
|
||||
*/
|
||||
public static final String EXTRA_IS_FROM_SLICE = "is_from_slice";
|
||||
|
||||
public static final String EXTRA_USER_HANDLE = "user_handle";
|
||||
public static final String EXTRA_INITIAL_CALLING_PACKAGE = "initial_calling_package";
|
||||
|
||||
/**
|
||||
* Personal or Work profile tab of {@link ProfileSelectFragment}
|
||||
* <p>0: Personal tab.
|
||||
* <p>1: Work profile tab.
|
||||
*/
|
||||
public static final String EXTRA_SHOW_FRAGMENT_TAB =
|
||||
":settings:show_fragment_tab";
|
||||
|
||||
public static final String META_DATA_KEY_FRAGMENT_CLASS =
|
||||
"com.android.settings.FRAGMENT_CLASS";
|
||||
|
||||
public static final String META_DATA_KEY_HIGHLIGHT_MENU_KEY =
|
||||
"com.android.settings.HIGHLIGHT_MENU_KEY";
|
||||
|
||||
private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
|
||||
|
||||
private String mFragmentClass;
|
||||
private String mHighlightMenuKey;
|
||||
|
||||
private CharSequence mInitialTitle;
|
||||
private int mInitialTitleResId;
|
||||
|
||||
private boolean mBatteryPresent = true;
|
||||
private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
|
||||
boolean batteryPresent = Utils.isBatteryPresent(intent);
|
||||
|
||||
if (mBatteryPresent != batteryPresent) {
|
||||
mBatteryPresent = batteryPresent;
|
||||
updateTilesList();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private SettingsMainSwitchBar mMainSwitch;
|
||||
|
||||
private Button mNextButton;
|
||||
|
||||
// Categories
|
||||
private ArrayList<DashboardCategory> mCategories = new ArrayList<>();
|
||||
|
||||
private DashboardFeatureProvider mDashboardFeatureProvider;
|
||||
|
||||
public SettingsMainSwitchBar getSwitchBar() {
|
||||
return mMainSwitch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
|
||||
new SubSettingLauncher(this)
|
||||
.setDestination(pref.getFragment())
|
||||
.setArguments(pref.getExtras())
|
||||
.setSourceMetricsCategory(caller instanceof Instrumentable
|
||||
? ((Instrumentable) caller).getMetricsCategory()
|
||||
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
|
||||
.setTitleRes(-1)
|
||||
.launch();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||
if (!TextUtils.equals(name, getPackageName() + "_preferences")) {
|
||||
return super.getSharedPreferences(name, mode);
|
||||
}
|
||||
|
||||
String tag = getMetricsTag();
|
||||
|
||||
return new SharedPreferencesLogger(this, tag,
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
|
||||
lookupMetricsCategory());
|
||||
}
|
||||
|
||||
private int lookupMetricsCategory() {
|
||||
int category = SettingsEnums.PAGE_UNKNOWN;
|
||||
Bundle args = null;
|
||||
if (getIntent() != null) {
|
||||
args = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
}
|
||||
|
||||
Fragment fragment = Utils.getTargetFragment(this, getMetricsTag(), args);
|
||||
|
||||
if (fragment instanceof Instrumentable) {
|
||||
category = ((Instrumentable) fragment).getMetricsCategory();
|
||||
}
|
||||
Log.d(LOG_TAG, "MetricsCategory is " + category);
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
private String getMetricsTag() {
|
||||
String tag = null;
|
||||
if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
|
||||
tag = getInitialFragmentName(getIntent());
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(tag)) {
|
||||
Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
|
||||
tag = getClass().getName();
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
// Should happen before any call to getIntent()
|
||||
getMetaData();
|
||||
final Intent intent = getIntent();
|
||||
|
||||
if (shouldShowMultiPaneDeepLink(intent)
|
||||
&& tryStartMultiPaneDeepLink(this, intent, mHighlightMenuKey)) {
|
||||
finish();
|
||||
super.onCreate(savedState);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onCreate(savedState);
|
||||
Log.d(LOG_TAG, "Starting onCreate");
|
||||
createUiFromIntent(savedState, intent);
|
||||
}
|
||||
|
||||
protected void createUiFromIntent(Bundle savedState, Intent intent) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
final FeatureFactory factory = FeatureFactory.getFeatureFactory();
|
||||
mDashboardFeatureProvider = factory.getDashboardFeatureProvider();
|
||||
|
||||
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
|
||||
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
|
||||
}
|
||||
|
||||
// Getting Intent properties can only be done after the super.onCreate(...)
|
||||
final String initialFragmentName = getInitialFragmentName(intent);
|
||||
|
||||
// If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
|
||||
// insets.
|
||||
// If this is in setup flow, don't apply theme. Because light theme needs to be applied
|
||||
// in SettingsBaseActivity#onCreate().
|
||||
if (isSubSettings(intent) && !WizardManagerHelper.isAnySetupWizard(getIntent())) {
|
||||
setTheme(R.style.Theme_SubSettings);
|
||||
}
|
||||
|
||||
setContentView(R.layout.settings_main_prefs);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
if (savedState != null) {
|
||||
// We are restarting from a previous saved state; used that to initialize, instead
|
||||
// of starting fresh.
|
||||
setTitleFromIntent(intent);
|
||||
|
||||
ArrayList<DashboardCategory> categories =
|
||||
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
|
||||
if (categories != null) {
|
||||
mCategories.clear();
|
||||
mCategories.addAll(categories);
|
||||
setTitleFromBackStack();
|
||||
}
|
||||
} else {
|
||||
launchSettingFragment(initialFragmentName, intent);
|
||||
}
|
||||
|
||||
mMainSwitch = findViewById(R.id.switch_bar);
|
||||
if (mMainSwitch != null) {
|
||||
mMainSwitch.setMetricsCategory(lookupMetricsCategory());
|
||||
mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1);
|
||||
}
|
||||
|
||||
// see if we should show Back/Next buttons
|
||||
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
|
||||
|
||||
View buttonBar = findViewById(R.id.button_bar);
|
||||
if (buttonBar != null) {
|
||||
buttonBar.setVisibility(View.VISIBLE);
|
||||
|
||||
Button backButton = findViewById(R.id.back_button);
|
||||
backButton.setOnClickListener(v -> {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
});
|
||||
Button skipButton = findViewById(R.id.skip_button);
|
||||
skipButton.setOnClickListener(v -> {
|
||||
setResult(RESULT_OK, null);
|
||||
finish();
|
||||
});
|
||||
mNextButton = findViewById(R.id.next_button);
|
||||
mNextButton.setOnClickListener(v -> {
|
||||
setResult(RESULT_OK, null);
|
||||
finish();
|
||||
});
|
||||
|
||||
// set our various button parameters
|
||||
if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
|
||||
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
|
||||
if (TextUtils.isEmpty(buttonText)) {
|
||||
mNextButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
mNextButton.setText(buttonText);
|
||||
}
|
||||
}
|
||||
if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
|
||||
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
|
||||
if (TextUtils.isEmpty(buttonText)) {
|
||||
backButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
backButton.setText(buttonText);
|
||||
}
|
||||
}
|
||||
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
|
||||
skipButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_TIMING) {
|
||||
Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void setActionBarStatus() {
|
||||
final boolean isActionBarButtonEnabled = isActionBarButtonEnabled(getIntent());
|
||||
|
||||
final ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(isActionBarButtonEnabled);
|
||||
actionBar.setHomeButtonEnabled(isActionBarButtonEnabled);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionBarButtonEnabled(Intent intent) {
|
||||
if (WizardManagerHelper.isAnySetupWizard(intent)) {
|
||||
return false;
|
||||
}
|
||||
final boolean isSecondLayerPage =
|
||||
intent.getBooleanExtra(EXTRA_IS_SECOND_LAYER_PAGE, false);
|
||||
|
||||
// TODO: move Settings's ActivityEmbeddingUtils to SettingsLib.
|
||||
return !com.android.settingslib.activityembedding.ActivityEmbeddingUtils
|
||||
.shouldHideNavigateUpButton(this, isSecondLayerPage);
|
||||
}
|
||||
|
||||
private boolean isSubSettings(Intent intent) {
|
||||
return this instanceof SubSettings ||
|
||||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
|
||||
}
|
||||
|
||||
private boolean shouldShowMultiPaneDeepLink(Intent intent) {
|
||||
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the activity is task root, starting trampoline is needed in order to show two-pane UI.
|
||||
// If FLAG_ACTIVITY_NEW_TASK is set, the activity will become the start of a new task on
|
||||
// this history stack, so starting trampoline is needed in order to notify the homepage that
|
||||
// the highlight key is changed.
|
||||
if (!isTaskRoot() && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only starts trampoline for deep links. Should return false for all the cases that
|
||||
// Settings app starts SettingsActivity or SubSetting by itself.
|
||||
if (intent.getAction() == null) {
|
||||
// Other apps should send deep link intent which matches intent filter of the Activity.
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the activity's launch mode is "singleInstance", it can't be embedded in Settings since
|
||||
// it will be created in a new task.
|
||||
ActivityInfo info = intent.resolveActivityInfo(getPackageManager(),
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (info.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
|
||||
Log.w(LOG_TAG, "launchMode: singleInstance");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
|
||||
// Slice deep link starts the Intent using SubSettingLauncher. Returns true to show
|
||||
// 2-pane deep link.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSubSettings(intent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (intent.getBooleanExtra(SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE,
|
||||
/* defaultValue */ false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_CREATE_SHORTCUT)) {
|
||||
// Returns false to show full screen for Intent.ACTION_CREATE_SHORTCUT because
|
||||
// - Launcher startActivityForResult for Intent.ACTION_CREATE_SHORTCUT and activity
|
||||
// stack starts from launcher, CreateShortcutActivity will not follows SplitPaitRule
|
||||
// registered by Settings.
|
||||
// - There is no CreateShortcutActivity entry point from Settings app UI.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns the initial calling package name that launches the activity. */
|
||||
public String getInitialCallingPackage() {
|
||||
String callingPackage = PasswordUtils.getCallingAppPackageName(getActivityToken());
|
||||
if (!TextUtils.equals(callingPackage, getPackageName())) {
|
||||
return callingPackage;
|
||||
}
|
||||
|
||||
String initialCallingPackage = getIntent().getStringExtra(EXTRA_INITIAL_CALLING_PACKAGE);
|
||||
return TextUtils.isEmpty(initialCallingPackage) ? callingPackage : initialCallingPackage;
|
||||
}
|
||||
|
||||
/** Returns the initial fragment name that the activity will launch. */
|
||||
@VisibleForTesting
|
||||
public String getInitialFragmentName(Intent intent) {
|
||||
return intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
|
||||
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
|
||||
if (fragments != null) {
|
||||
for (Fragment fragment : fragments) {
|
||||
if (fragment instanceof OnActivityResultListener) {
|
||||
fragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void launchSettingFragment(String initialFragmentName, Intent intent) {
|
||||
if (initialFragmentName != null) {
|
||||
if (SettingsActivityUtil.launchSpaActivity(this, initialFragmentName, intent)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
setTitleFromIntent(intent);
|
||||
|
||||
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
switchToFragment(initialFragmentName, initialArguments, true,
|
||||
mInitialTitleResId, mInitialTitle);
|
||||
} else {
|
||||
// Show search icon as up affordance if we are displaying the main Dashboard
|
||||
mInitialTitleResId = R.string.dashboard_title;
|
||||
switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
|
||||
mInitialTitleResId, mInitialTitle);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTitleFromIntent(Intent intent) {
|
||||
Log.d(LOG_TAG, "Starting to set activity title");
|
||||
final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
|
||||
if (initialTitleResId > 0) {
|
||||
mInitialTitle = null;
|
||||
mInitialTitleResId = initialTitleResId;
|
||||
|
||||
final String initialTitleResPackageName = intent.getStringExtra(
|
||||
EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
|
||||
if (initialTitleResPackageName != null) {
|
||||
try {
|
||||
Context authContext = createPackageContextAsUser(initialTitleResPackageName,
|
||||
0 /* flags */, new UserHandle(UserHandle.myUserId()));
|
||||
mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
|
||||
setTitle(mInitialTitle);
|
||||
mInitialTitleResId = -1;
|
||||
return;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
|
||||
} catch (Resources.NotFoundException resourceNotFound) {
|
||||
Log.w(LOG_TAG,
|
||||
"Could not find title resource in " + initialTitleResPackageName);
|
||||
}
|
||||
} else {
|
||||
setTitle(mInitialTitleResId);
|
||||
}
|
||||
} else {
|
||||
mInitialTitleResId = -1;
|
||||
final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
|
||||
mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
|
||||
setTitle(mInitialTitle);
|
||||
}
|
||||
Log.d(LOG_TAG, "Done setting title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
setTitleFromBackStack();
|
||||
}
|
||||
|
||||
private void setTitleFromBackStack() {
|
||||
final int count = getSupportFragmentManager().getBackStackEntryCount();
|
||||
|
||||
if (count == 0) {
|
||||
if (mInitialTitleResId > 0) {
|
||||
setTitle(mInitialTitleResId);
|
||||
} else {
|
||||
setTitle(mInitialTitle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
FragmentManager.BackStackEntry bse = getSupportFragmentManager().
|
||||
getBackStackEntryAt(count - 1);
|
||||
setTitleFromBackStackEntry(bse);
|
||||
}
|
||||
|
||||
private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
|
||||
final CharSequence title;
|
||||
final int titleRes = bse.getBreadCrumbTitleRes();
|
||||
if (titleRes > 0) {
|
||||
title = getText(titleRes);
|
||||
} else {
|
||||
title = bse.getBreadCrumbTitle();
|
||||
}
|
||||
if (title != null) {
|
||||
setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveState(outState);
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void saveState(Bundle outState) {
|
||||
if (mCategories.size() > 0) {
|
||||
outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setActionBarStatus();
|
||||
|
||||
registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
|
||||
updateTilesList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
unregisterReceiver(mBatteryInfoReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
|
||||
taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings));
|
||||
super.setTaskDescription(taskDescription);
|
||||
}
|
||||
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
// Almost all fragments are wrapped in this,
|
||||
// except for a few that have their own activities.
|
||||
for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) {
|
||||
if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent superIntent = super.getIntent();
|
||||
String startingFragment = getStartingFragmentClass(superIntent);
|
||||
// This is called from super.onCreate, isMultiPane() is not yet reliable
|
||||
// Do not use onIsHidingHeaders either, which relies itself on this method
|
||||
if (startingFragment != null) {
|
||||
Intent modIntent = new Intent(superIntent);
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
|
||||
Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
if (args != null) {
|
||||
args = new Bundle(args);
|
||||
} else {
|
||||
args = new Bundle();
|
||||
}
|
||||
args.putParcelable("intent", superIntent);
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
|
||||
return modIntent;
|
||||
}
|
||||
return superIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the component name in the intent is different from the Settings class and
|
||||
* returns the class name to load as a fragment.
|
||||
*/
|
||||
private String getStartingFragmentClass(Intent intent) {
|
||||
if (mFragmentClass != null) return mFragmentClass;
|
||||
|
||||
String intentClass = intent.getComponent().getClassName();
|
||||
if (intentClass.equals(getClass().getName())) return null;
|
||||
|
||||
if ("com.android.settings.RunningServices".equals(intentClass)
|
||||
|| "com.android.settings.applications.StorageUse".equals(intentClass)) {
|
||||
// Old names of manage apps.
|
||||
intentClass = ManageApplications.class.getName();
|
||||
}
|
||||
|
||||
return intentClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a preference panel fragment to finish itself.
|
||||
*
|
||||
* @param resultCode Optional result code to send back to the original
|
||||
* launching fragment.
|
||||
* @param resultData Optional result data to send back to the original
|
||||
* launching fragment.
|
||||
*/
|
||||
public void finishPreferencePanel(int resultCode, Intent resultData) {
|
||||
setResult(resultCode, resultData);
|
||||
if (resultData != null &&
|
||||
resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a specific Fragment with taking care of validation, Title and BackStack
|
||||
*/
|
||||
private void switchToFragment(String fragmentName, Bundle args, boolean validate,
|
||||
int titleResId, CharSequence title) {
|
||||
Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
|
||||
if (validate && !isValidFragment(fragmentName)) {
|
||||
throw new IllegalArgumentException("Invalid fragment for this activity: "
|
||||
+ fragmentName);
|
||||
}
|
||||
Fragment f = Utils.getTargetFragment(this, fragmentName, args);
|
||||
if (f == null) {
|
||||
return;
|
||||
}
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.main_content, f);
|
||||
if (titleResId > 0) {
|
||||
transaction.setBreadCrumbTitle(titleResId);
|
||||
} else if (title != null) {
|
||||
transaction.setBreadCrumbTitle(title);
|
||||
}
|
||||
transaction.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
Log.d(LOG_TAG, "Executed frag manager pendingTransactions");
|
||||
}
|
||||
|
||||
private void updateTilesList() {
|
||||
// Generally the items that are will be changing from these updates will
|
||||
// not be in the top list of tiles, so run it in the background and the
|
||||
// SettingsBaseActivity will pick up on the updates automatically.
|
||||
AsyncTask.execute(() -> doUpdateTilesList());
|
||||
}
|
||||
|
||||
private void doUpdateTilesList() {
|
||||
PackageManager pm = getPackageManager();
|
||||
final UserManager um = UserManager.get(this);
|
||||
final boolean isAdmin = um.isAdminUser();
|
||||
boolean somethingChanged = false;
|
||||
final String packageName = getPackageName();
|
||||
final StringBuilder changedList = new StringBuilder();
|
||||
somethingChanged = setTileEnabled(changedList,
|
||||
new ComponentName(packageName, WifiSettingsActivity.class.getName()),
|
||||
pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged;
|
||||
|
||||
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
|
||||
Settings.BluetoothSettingsActivity.class.getName()),
|
||||
pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin)
|
||||
|| somethingChanged;
|
||||
|
||||
// Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise
|
||||
// enable DataPlanUsageSummaryActivity.
|
||||
somethingChanged = setTileEnabled(changedList,
|
||||
new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()),
|
||||
Utils.isBandwidthControlEnabled() /* enabled */,
|
||||
isAdmin) || somethingChanged;
|
||||
|
||||
somethingChanged = setTileEnabled(changedList,
|
||||
new ComponentName(packageName,
|
||||
Settings.ConnectedDeviceDashboardActivity.class.getName()),
|
||||
!UserManager.isDeviceInDemoMode(this) /* enabled */,
|
||||
isAdmin) || somethingChanged;
|
||||
|
||||
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
|
||||
Settings.PowerUsageSummaryActivity.class.getName()),
|
||||
mBatteryPresent, isAdmin) || somethingChanged;
|
||||
|
||||
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
|
||||
Settings.DataUsageSummaryActivity.class.getName()),
|
||||
Utils.isBandwidthControlEnabled(), isAdmin)
|
||||
|| somethingChanged;
|
||||
|
||||
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
|
||||
Settings.WifiDisplaySettingsActivity.class.getName()),
|
||||
WifiDisplaySettings.isAvailable(this), isAdmin)
|
||||
|| somethingChanged;
|
||||
|
||||
if (UserHandle.MU_ENABLED && !isAdmin) {
|
||||
// When on restricted users, disable all extra categories (but only the settings ones).
|
||||
final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
|
||||
synchronized (categories) {
|
||||
for (DashboardCategory category : categories) {
|
||||
final int tileCount = category.getTilesCount();
|
||||
for (int i = 0; i < tileCount; i++) {
|
||||
final ComponentName component = category.getTile(i)
|
||||
.getIntent().getComponent();
|
||||
final String name = component.getClassName();
|
||||
final boolean isEnabledForRestricted = ArrayUtils.contains(
|
||||
SettingsGateway.SETTINGS_FOR_RESTRICTED, name);
|
||||
if (packageName.equals(component.getPackageName())
|
||||
&& !isEnabledForRestricted) {
|
||||
somethingChanged =
|
||||
setTileEnabled(changedList, component, false, isAdmin)
|
||||
|| somethingChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final step, refresh categories.
|
||||
if (somethingChanged) {
|
||||
Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
|
||||
+ changedList.toString());
|
||||
mCategoryMixin.updateCategories();
|
||||
} else {
|
||||
Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether or not the enabled state actually changed.
|
||||
*/
|
||||
private boolean setTileEnabled(StringBuilder changedList, ComponentName component,
|
||||
boolean enabled, boolean isAdmin) {
|
||||
if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName())
|
||||
&& !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED,
|
||||
component.getClassName())) {
|
||||
enabled = false;
|
||||
}
|
||||
boolean changed = setTileEnabled(component, enabled);
|
||||
if (changed) {
|
||||
changedList.append(component.toShortString()).append(",");
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void getMetaData() {
|
||||
try {
|
||||
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
|
||||
PackageManager.GET_META_DATA);
|
||||
if (ai == null || ai.metaData == null) return;
|
||||
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
|
||||
mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY);
|
||||
/* TODO(b/327036144) Once the Flags.walletRoleEnabled() is rolled out, we will replace
|
||||
value for the fragment class within the com.android.settings.nfc.PaymentSettings
|
||||
activity with com.android.settings.connecteddevice.NfcAndPaymentFragment so that this
|
||||
code can be removed.
|
||||
*/
|
||||
if (shouldOverrideContactlessPaymentRouting()) {
|
||||
overrideContactlessPaymentRouting();
|
||||
}
|
||||
} catch (NameNotFoundException nnfe) {
|
||||
// No recovery
|
||||
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldOverrideContactlessPaymentRouting() {
|
||||
return Flags.walletRoleEnabled()
|
||||
&& TextUtils.equals(PaymentSettings.class.getName(), mFragmentClass);
|
||||
}
|
||||
|
||||
private void overrideContactlessPaymentRouting() {
|
||||
mFragmentClass = NfcAndPaymentFragment.class.getName();
|
||||
}
|
||||
|
||||
// give subclasses access to the Next button
|
||||
public boolean hasNextButton() {
|
||||
return mNextButton != null;
|
||||
}
|
||||
|
||||
public Button getNextButton() {
|
||||
return mNextButton;
|
||||
}
|
||||
}
|
||||
92
Settings/src/com/android/settings/SettingsActivityUtil.kt
Normal file
92
Settings/src/com/android/settings/SettingsActivityUtil.kt
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.FeatureFlagUtils
|
||||
import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails
|
||||
import com.android.settings.applications.appinfo.DrawOverlayDetails
|
||||
import com.android.settings.applications.appinfo.ExternalSourcesDetails
|
||||
import com.android.settings.applications.appinfo.ManageExternalStorageDetails
|
||||
import com.android.settings.applications.appinfo.MediaManagementAppsDetails
|
||||
import com.android.settings.applications.appinfo.WriteSettingsDetails
|
||||
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails
|
||||
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings
|
||||
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
|
||||
import com.android.settings.spa.SpaAppBridgeActivity.Companion.getDestinationForApp
|
||||
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.BackupTasksAppsListProvider
|
||||
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
|
||||
import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
|
||||
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
|
||||
import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
|
||||
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
|
||||
import com.android.settings.wifi.ChangeWifiStateDetails
|
||||
|
||||
object SettingsActivityUtil {
|
||||
private val FRAGMENT_TO_SPA_DESTINATION_MAP = mapOf(
|
||||
PictureInPictureSettings::class.qualifiedName to
|
||||
PictureInPictureListProvider.getAppListRoute(),
|
||||
)
|
||||
|
||||
private val FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP = mapOf(
|
||||
PictureInPictureDetails::class.qualifiedName to
|
||||
PictureInPictureListProvider.getAppInfoRoutePrefix(),
|
||||
DrawOverlayDetails::class.qualifiedName to
|
||||
DisplayOverOtherAppsAppListProvider.getAppInfoRoutePrefix(),
|
||||
WriteSettingsDetails::class.qualifiedName to
|
||||
ModifySystemSettingsAppListProvider.getAppInfoRoutePrefix(),
|
||||
AlarmsAndRemindersDetails::class.qualifiedName to
|
||||
AlarmsAndRemindersAppListProvider.getAppInfoRoutePrefix(),
|
||||
ExternalSourcesDetails::class.qualifiedName to
|
||||
InstallUnknownAppsListProvider.getAppInfoRoutePrefix(),
|
||||
ManageExternalStorageDetails::class.qualifiedName to
|
||||
AllFilesAccessAppListProvider.getAppInfoRoutePrefix(),
|
||||
MediaManagementAppsDetails::class.qualifiedName to
|
||||
MediaManagementAppsAppListProvider.getAppInfoRoutePrefix(),
|
||||
ChangeWifiStateDetails::class.qualifiedName to
|
||||
WifiControlAppListProvider.getAppInfoRoutePrefix(),
|
||||
NfcTagAppsSettingsProvider::class.qualifiedName to
|
||||
NfcTagAppsSettingsProvider.getAppInfoRoutePrefix(),
|
||||
VoiceActivationAppsListProvider::class.qualifiedName to
|
||||
VoiceActivationAppsListProvider.getAppInfoRoutePrefix(),
|
||||
BackupTasksAppsListProvider::class.qualifiedName to
|
||||
BackupTasksAppsListProvider.getAppInfoRoutePrefix(),
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean {
|
||||
if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
|
||||
getDestination(fragmentName, intent)?.let { destination ->
|
||||
startSpaActivity(destination)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getDestination(fragmentName: String, intent: Intent): String? =
|
||||
FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]
|
||||
?: FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { destinationPrefix ->
|
||||
getDestinationForApp(destinationPrefix, intent)
|
||||
}
|
||||
}
|
||||
138
Settings/src/com/android/settings/SettingsApplication.java
Normal file
138
Settings/src/com/android/settings/SettingsApplication.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.util.FeatureFlagUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
import com.android.settings.core.instrumentation.ElapsedTimeUtils;
|
||||
import com.android.settings.fuelgauge.BatterySettingsStorage;
|
||||
import com.android.settings.homepage.SettingsHomepageActivity;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.overlay.FeatureFactoryImpl;
|
||||
import com.android.settings.spa.SettingsSpaEnvironment;
|
||||
import com.android.settingslib.applications.AppIconCacheManager;
|
||||
import com.android.settingslib.datastore.BackupRestoreStorageManager;
|
||||
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/** Settings application which sets up activity embedding rules for the large screen device. */
|
||||
public class SettingsApplication extends Application {
|
||||
|
||||
private WeakReference<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null);
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
FeatureFactory.setFactory(this, getFeatureFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
BackupRestoreStorageManager.getInstance(this).add(new BatterySettingsStorage(this));
|
||||
|
||||
// Add null checking to avoid test case failed.
|
||||
if (getApplicationContext() != null) {
|
||||
ElapsedTimeUtils.assignSuwFinishedTimeStamp(getApplicationContext());
|
||||
}
|
||||
|
||||
// Set Spa environment.
|
||||
setSpaEnvironment();
|
||||
|
||||
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
|
||||
&& FeatureFlagUtils.isEnabled(this,
|
||||
FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) {
|
||||
if (WizardManagerHelper.isUserSetupComplete(this)) {
|
||||
new ActivityEmbeddingRulesController(this).initRules();
|
||||
} else {
|
||||
new DeviceProvisionedObserver().registerContentObserver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
BackupRestoreStorageManager.getInstance(this).removeAll();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected FeatureFactory getFeatureFactory() {
|
||||
return new FeatureFactoryImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the spa environment instance.
|
||||
* Override this function to set different spa environment for different Settings app.
|
||||
*/
|
||||
protected void setSpaEnvironment() {
|
||||
SpaEnvironmentFactory.INSTANCE.reset(new SettingsSpaEnvironment(this));
|
||||
}
|
||||
|
||||
public void setHomeActivity(SettingsHomepageActivity homeActivity) {
|
||||
mHomeActivity = new WeakReference<>(homeActivity);
|
||||
}
|
||||
|
||||
public SettingsHomepageActivity getHomeActivity() {
|
||||
return mHomeActivity.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
AppIconCacheManager.getInstance().trimMemory(level);
|
||||
}
|
||||
|
||||
private class DeviceProvisionedObserver extends ContentObserver {
|
||||
private final Uri mDeviceProvisionedUri = Settings.Secure.getUriFor(
|
||||
Settings.Secure.USER_SETUP_COMPLETE);
|
||||
|
||||
DeviceProvisionedObserver() {
|
||||
super(null /* handler */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri, int flags) {
|
||||
if (!mDeviceProvisionedUri.equals(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsApplication.this.getContentResolver().unregisterContentObserver(this);
|
||||
new ActivityEmbeddingRulesController(SettingsApplication.this).initRules();
|
||||
}
|
||||
|
||||
public void registerContentObserver() {
|
||||
SettingsApplication.this.getContentResolver().registerContentObserver(
|
||||
mDeviceProvisionedUri,
|
||||
false /* notifyForDescendants */,
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
201
Settings/src/com/android/settings/SettingsDumpService.java
Normal file
201
Settings/src/com/android/settings/SettingsDumpService.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
|
||||
import static android.content.pm.PackageManager.FEATURE_WIFI;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.VolumeInfo;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.applications.ProcStatsData;
|
||||
import com.android.settings.datausage.lib.DataUsageLib;
|
||||
import com.android.settings.network.MobileNetworkRepository;
|
||||
import com.android.settingslib.net.DataUsageController;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class SettingsDumpService extends Service {
|
||||
|
||||
public static final String EXTRA_KEY_SHOW_NETWORK_DUMP = "show_network_dump";
|
||||
|
||||
private static final String TAG = "SettingsDumpService";
|
||||
@VisibleForTesting
|
||||
static final String KEY_SERVICE = "service";
|
||||
@VisibleForTesting
|
||||
static final String KEY_STORAGE = "storage";
|
||||
@VisibleForTesting
|
||||
static final String KEY_DATAUSAGE = "datausage";
|
||||
@VisibleForTesting
|
||||
static final String KEY_MEMORY = "memory";
|
||||
@VisibleForTesting
|
||||
static final String KEY_DEFAULT_BROWSER_APP = "default_browser_app";
|
||||
@VisibleForTesting
|
||||
static final String KEY_ANOMALY_DETECTION = "anomaly_detection";
|
||||
@VisibleForTesting
|
||||
static final Intent BROWSER_INTENT =
|
||||
new Intent("android.intent.action.VIEW", Uri.parse("http://"));
|
||||
|
||||
private boolean mShouldShowNetworkDump = false;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null) {
|
||||
mShouldShowNetworkDump = intent.getBooleanExtra(EXTRA_KEY_SHOW_NETWORK_DUMP, false);
|
||||
}
|
||||
return Service.START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||
if (!mShouldShowNetworkDump) {
|
||||
JSONObject dump = new JSONObject();
|
||||
pw.println(TAG + ": ");
|
||||
pw.increaseIndent();
|
||||
try {
|
||||
dump.put(KEY_SERVICE, "Settings State");
|
||||
dump.put(KEY_STORAGE, dumpStorage());
|
||||
dump.put(KEY_DATAUSAGE, dumpDataUsage());
|
||||
dump.put(KEY_MEMORY, dumpMemory());
|
||||
dump.put(KEY_DEFAULT_BROWSER_APP, dumpDefaultBrowser());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "exception in dump: ", e);
|
||||
}
|
||||
pw.println(dump);
|
||||
pw.flush();
|
||||
pw.decreaseIndent();
|
||||
} else {
|
||||
dumpMobileNetworkSettings(pw);
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject dumpMemory() throws JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
ProcStatsData statsManager = new ProcStatsData(this, false);
|
||||
statsManager.refreshStats(true);
|
||||
ProcStatsData.MemInfo memInfo = statsManager.getMemInfo();
|
||||
|
||||
obj.put("used", String.valueOf(memInfo.realUsedRam));
|
||||
obj.put("free", String.valueOf(memInfo.realFreeRam));
|
||||
obj.put("total", String.valueOf(memInfo.realTotalRam));
|
||||
obj.put("state", statsManager.getMemState());
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private JSONObject dumpDataUsage() throws JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
DataUsageController controller = new DataUsageController(this);
|
||||
SubscriptionManager manager = this.getSystemService(SubscriptionManager.class);
|
||||
TelephonyManager telephonyManager = this.getSystemService(TelephonyManager.class);
|
||||
final PackageManager packageManager = this.getPackageManager();
|
||||
if (telephonyManager.isDataCapable()) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (SubscriptionInfo info : manager.getAvailableSubscriptionInfoList()) {
|
||||
NetworkTemplate template = DataUsageLib.getMobileTemplateForSubId(
|
||||
telephonyManager, info.getSubscriptionId());
|
||||
final JSONObject usage = dumpDataUsage(template, controller);
|
||||
usage.put("subId", info.getSubscriptionId());
|
||||
array.put(usage);
|
||||
}
|
||||
obj.put("cell", array);
|
||||
}
|
||||
if (packageManager.hasSystemFeature(FEATURE_WIFI)) {
|
||||
obj.put("wifi", dumpDataUsage(
|
||||
new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(), controller));
|
||||
}
|
||||
|
||||
if (packageManager.hasSystemFeature(FEATURE_ETHERNET)) {
|
||||
obj.put("ethernet", dumpDataUsage(new NetworkTemplate.Builder(
|
||||
NetworkTemplate.MATCH_ETHERNET).build(), controller));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private JSONObject dumpDataUsage(NetworkTemplate template, DataUsageController controller)
|
||||
throws JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
DataUsageController.DataUsageInfo usage = controller.getDataUsageInfo(template);
|
||||
obj.put("carrier", usage.carrier);
|
||||
obj.put("start", usage.startDate);
|
||||
obj.put("usage", usage.usageLevel);
|
||||
obj.put("warning", usage.warningLevel);
|
||||
obj.put("limit", usage.limitLevel);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private JSONObject dumpStorage() throws JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
StorageManager manager = getSystemService(StorageManager.class);
|
||||
for (VolumeInfo volume : manager.getVolumes()) {
|
||||
JSONObject volObj = new JSONObject();
|
||||
if (volume.isMountedReadable()) {
|
||||
File path = volume.getPath();
|
||||
volObj.put("used", String.valueOf(path.getTotalSpace() - path.getFreeSpace()));
|
||||
volObj.put("total", String.valueOf(path.getTotalSpace()));
|
||||
}
|
||||
volObj.put("path", volume.getInternalPath());
|
||||
volObj.put("state", volume.getState());
|
||||
volObj.put("stateDesc", volume.getStateDescription());
|
||||
volObj.put("description", volume.getDescription());
|
||||
obj.put(volume.getId(), volObj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
String dumpDefaultBrowser() {
|
||||
final ResolveInfo resolveInfo = getPackageManager().resolveActivity(
|
||||
BROWSER_INTENT, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
||||
if (resolveInfo == null || resolveInfo.activityInfo.packageName.equals("android")) {
|
||||
return null;
|
||||
} else {
|
||||
return resolveInfo.activityInfo.packageName;
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpMobileNetworkSettings(IndentingPrintWriter writer) {
|
||||
MobileNetworkRepository.getInstance(this).dump(writer);
|
||||
}
|
||||
}
|
||||
190
Settings/src/com/android/settings/SettingsInitialize.java
Normal file
190
Settings/src/com/android/settings/SettingsInitialize.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import static android.content.pm.PackageManager.GET_ACTIVITIES;
|
||||
import static android.content.pm.PackageManager.GET_META_DATA;
|
||||
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
|
||||
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
|
||||
|
||||
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Flags;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.Settings.CreateShortcutActivity;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
import com.android.settings.homepage.DeepLinkHomepageActivity;
|
||||
import com.android.settings.search.SearchStateReceiver;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Listens to {@link Intent.ACTION_PRE_BOOT_COMPLETED} and {@link Intent.ACTION_USER_INITIALIZED}
|
||||
* performs setup steps for a managed profile (disables the launcher icon of the Settings app,
|
||||
* adds cross-profile intent filters for the appropriate Settings activities), disables the
|
||||
* webview setting for non-admin users, updates the intent flags for any existing shortcuts and
|
||||
* enables DeepLinkHomepageActivity for large screen devices.
|
||||
*/
|
||||
public class SettingsInitialize extends BroadcastReceiver {
|
||||
private static final String TAG = "Settings";
|
||||
private static final String PRIMARY_PROFILE_SETTING =
|
||||
"com.android.settings.PRIMARY_PROFILE_CONTROLLED";
|
||||
private static final String WEBVIEW_IMPLEMENTATION_ACTIVITY = ".WebViewImplementation";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent broadcast) {
|
||||
final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
UserInfo userInfo = um.getUserInfo(UserHandle.myUserId());
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
managedProfileSetup(context, pm, broadcast, userInfo);
|
||||
cloneProfileSetup(context, pm, userInfo);
|
||||
privateProfileSetup(context, pm, userInfo);
|
||||
webviewSettingSetup(context, pm, userInfo);
|
||||
ThreadUtils.postOnBackgroundThread(() -> refreshExistingShortcuts(context));
|
||||
enableTwoPaneDeepLinkActivityIfNecessary(pm, context);
|
||||
}
|
||||
|
||||
private void managedProfileSetup(Context context, final PackageManager pm, Intent broadcast,
|
||||
UserInfo userInfo) {
|
||||
if (userInfo == null || !userInfo.isManagedProfile()) {
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "Received broadcast: " + broadcast.getAction()
|
||||
+ ". Setting up intent forwarding for managed profile.");
|
||||
// Clear any previous intent forwarding we set up
|
||||
pm.clearCrossProfileIntentFilters(userInfo.id);
|
||||
|
||||
// Set up intent forwarding for implicit intents
|
||||
Intent intent = new Intent();
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setPackage(context.getPackageName());
|
||||
|
||||
// Resolves activities for the managed profile (which we're running as)
|
||||
List<ResolveInfo> resolvedIntents = pm.queryIntentActivities(intent,
|
||||
GET_ACTIVITIES | GET_META_DATA | GET_RESOLVED_FILTER | MATCH_DISABLED_COMPONENTS);
|
||||
final int count = resolvedIntents.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ResolveInfo info = resolvedIntents.get(i);
|
||||
if (info.filter != null && info.activityInfo != null
|
||||
&& info.activityInfo.metaData != null) {
|
||||
boolean shouldForward = info.activityInfo.metaData.getBoolean(
|
||||
PRIMARY_PROFILE_SETTING);
|
||||
if (shouldForward) {
|
||||
pm.addCrossProfileIntentFilter(info.filter, userInfo.id,
|
||||
userInfo.profileGroupId, PackageManager.SKIP_CURRENT_PROFILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disableComponentsToHideSettings(context, pm);
|
||||
}
|
||||
|
||||
private void cloneProfileSetup(Context context, PackageManager pm, UserInfo userInfo) {
|
||||
if (userInfo == null || !userInfo.isCloneProfile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableComponentsToHideSettings(context, pm);
|
||||
}
|
||||
|
||||
private void privateProfileSetup(Context context, PackageManager pm, UserInfo userInfo) {
|
||||
if (Flags.allowPrivateProfile()) {
|
||||
if (userInfo == null || !userInfo.isPrivateProfile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableComponentsToHideSettings(context, pm);
|
||||
}
|
||||
}
|
||||
|
||||
private void disableComponentsToHideSettings(Context context, PackageManager pm) {
|
||||
// Disable settings app launcher icon
|
||||
disableComponent(pm, new ComponentName(context, Settings.class));
|
||||
|
||||
//Disable Shortcut picker
|
||||
disableComponent(pm, new ComponentName(context, CreateShortcutActivity.class));
|
||||
}
|
||||
|
||||
private void disableComponent(PackageManager pm, ComponentName componentName) {
|
||||
pm.setComponentEnabledSetting(componentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
// Disable WebView Setting if the current user is not an admin
|
||||
private void webviewSettingSetup(Context context, PackageManager pm, UserInfo userInfo) {
|
||||
if (userInfo == null) {
|
||||
return;
|
||||
}
|
||||
ComponentName settingsComponentName =
|
||||
new ComponentName(SETTINGS_PACKAGE_NAME,
|
||||
SETTINGS_PACKAGE_NAME + WEBVIEW_IMPLEMENTATION_ACTIVITY);
|
||||
pm.setComponentEnabledSetting(settingsComponentName,
|
||||
userInfo.isAdmin() ?
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
// Refresh settings shortcuts to have correct intent flags
|
||||
@VisibleForTesting
|
||||
void refreshExistingShortcuts(Context context) {
|
||||
final ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
|
||||
final List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
|
||||
final List<ShortcutInfo> updates = new ArrayList<>();
|
||||
for (ShortcutInfo info : pinnedShortcuts) {
|
||||
if (info.isImmutable()) {
|
||||
continue;
|
||||
}
|
||||
final Intent shortcutIntent = info.getIntent();
|
||||
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final ShortcutInfo updatedInfo = new ShortcutInfo.Builder(context, info.getId())
|
||||
.setIntent(shortcutIntent)
|
||||
.build();
|
||||
updates.add(updatedInfo);
|
||||
}
|
||||
shortcutManager.updateShortcuts(updates);
|
||||
}
|
||||
|
||||
private void enableTwoPaneDeepLinkActivityIfNecessary(PackageManager pm, Context context) {
|
||||
final ComponentName deepLinkHome = new ComponentName(context,
|
||||
DeepLinkHomepageActivity.class);
|
||||
final ComponentName searchStateReceiver = new ComponentName(context,
|
||||
SearchStateReceiver.class);
|
||||
final int enableState = ActivityEmbeddingUtils.isSettingsSplitEnabled(context)
|
||||
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
pm.setComponentEnabledSetting(deepLinkHome, enableState, PackageManager.DONT_KILL_APP);
|
||||
pm.setComponentEnabledSetting(searchStateReceiver, enableState,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
}
|
||||
127
Settings/src/com/android/settings/SettingsLicenseActivity.java
Normal file
127
Settings/src/com/android/settings/SettingsLicenseActivity.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import com.android.settingslib.license.LicenseHtmlLoaderCompat;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* The "dialog" that shows from "License" in the Settings app.
|
||||
*/
|
||||
public class SettingsLicenseActivity extends FragmentActivity implements
|
||||
LoaderManager.LoaderCallbacks<File> {
|
||||
private static final String TAG = "SettingsLicenseActivity";
|
||||
|
||||
private static final String LICENSE_PATH = "/system/etc/NOTICE.html.gz";
|
||||
|
||||
private static final int LOADER_ID_LICENSE_HTML_LOADER = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
File file = new File(LICENSE_PATH);
|
||||
if (isFileValid(file)) {
|
||||
showHtmlFromUri(Uri.fromFile(file));
|
||||
} else {
|
||||
showHtmlFromDefaultXmlFiles();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<File> onCreateLoader(int id, Bundle args) {
|
||||
return new LicenseHtmlLoaderCompat(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<File> loader, File generatedHtmlFile) {
|
||||
showGeneratedHtmlFile(generatedHtmlFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<File> loader) {
|
||||
}
|
||||
|
||||
private void showHtmlFromDefaultXmlFiles() {
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_LICENSE_HTML_LOADER, Bundle.EMPTY, this);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
|
||||
return FileProvider.getUriForFile(this, Utils.FILE_PROVIDER_AUTHORITY,
|
||||
generatedHtmlFile);
|
||||
}
|
||||
|
||||
private void showGeneratedHtmlFile(File generatedHtmlFile) {
|
||||
if (generatedHtmlFile != null) {
|
||||
showHtmlFromUri(getUriFromGeneratedHtmlFile(generatedHtmlFile));
|
||||
} else {
|
||||
Log.e(TAG, "Failed to generate.");
|
||||
showErrorAndFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private void showHtmlFromUri(Uri uri) {
|
||||
// Kick off external viewer due to WebView security restrictions; we
|
||||
// carefully point it at HTMLViewer, since it offers to decompress
|
||||
// before viewing.
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(uri, "text/html");
|
||||
intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_license_activity_title));
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
}
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setPackage("com.android.htmlviewer");
|
||||
|
||||
try {
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Failed to find viewer", e);
|
||||
showErrorAndFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private void showErrorAndFinish() {
|
||||
Toast.makeText(this, R.string.settings_license_activity_unavailable, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isFileValid(final File file) {
|
||||
return file.exists() && file.length() != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,769 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.core.InstrumentedPreferenceFragment;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
import com.android.settings.support.actionbar.HelpResourceProvider;
|
||||
import com.android.settings.widget.HighlightablePreferenceGroupAdapter;
|
||||
import com.android.settings.widget.LoadingViewController;
|
||||
import com.android.settingslib.CustomDialogPreferenceCompat;
|
||||
import com.android.settingslib.CustomEditTextPreferenceCompat;
|
||||
import com.android.settingslib.core.instrumentation.Instrumentable;
|
||||
import com.android.settingslib.search.Indexable;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Base class for Settings fragments, with some helper functions and dialog management.
|
||||
*/
|
||||
public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
|
||||
implements DialogCreatable, HelpResourceProvider, Indexable {
|
||||
|
||||
private static final String TAG = "SettingsPreferenceFragment";
|
||||
|
||||
private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
|
||||
|
||||
private static final int ORDER_FIRST = -1;
|
||||
|
||||
protected DevicePolicyManager mDevicePolicyManager;
|
||||
private SettingsDialogFragment mDialogFragment;
|
||||
// Cache the content resolver for async callbacks
|
||||
private ContentResolver mContentResolver;
|
||||
|
||||
private RecyclerView.Adapter mCurrentRootAdapter;
|
||||
private boolean mIsDataSetObserverRegistered = false;
|
||||
private RecyclerView.AdapterDataObserver mDataSetObserver =
|
||||
new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
onDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
onDataSetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
ViewGroup mPinnedHeaderFrameLayout;
|
||||
private AppBarLayout mAppBarLayout;
|
||||
private LayoutPreference mHeader;
|
||||
private View mEmptyView;
|
||||
private LinearLayoutManager mLayoutManager;
|
||||
private ArrayMap<String, Preference> mPreferenceCache;
|
||||
private boolean mAnimationAllowed;
|
||||
|
||||
@VisibleForTesting
|
||||
public HighlightablePreferenceGroupAdapter mAdapter;
|
||||
private boolean mPreferenceHighlighted = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
if (shouldSkipForInitialSUW() && !WizardManagerHelper.isDeviceProvisioned(getContext())) {
|
||||
Log.w(TAG, "Skip " + getClass().getSimpleName() + " before SUW completed.");
|
||||
finish();
|
||||
}
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
mDevicePolicyManager = getContext().getSystemService(DevicePolicyManager.class);
|
||||
if (icicle != null) {
|
||||
mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
|
||||
}
|
||||
HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(this /* host */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||
mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header);
|
||||
mAppBarLayout = getActivity().findViewById(R.id.app_bar);
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
|
||||
super.addPreferencesFromResource(preferencesResId);
|
||||
checkAvailablePrefs(getPreferenceScreen());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
|
||||
if (preferenceGroup == null) return;
|
||||
for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
|
||||
Preference pref = preferenceGroup.getPreference(i);
|
||||
if (pref instanceof SelfAvailablePreference
|
||||
&& !((SelfAvailablePreference) pref).isAvailable(getContext())) {
|
||||
pref.setVisible(false);
|
||||
} else if (pref instanceof PreferenceGroup) {
|
||||
checkAvailablePrefs((PreferenceGroup) pref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public View setPinnedHeaderView(int layoutResId) {
|
||||
final LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
final View pinnedHeader =
|
||||
inflater.inflate(layoutResId, mPinnedHeaderFrameLayout, false);
|
||||
setPinnedHeaderView(pinnedHeader);
|
||||
return pinnedHeader;
|
||||
}
|
||||
|
||||
public void setPinnedHeaderView(View pinnedHeader) {
|
||||
mPinnedHeaderFrameLayout.addView(pinnedHeader);
|
||||
mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void showPinnedHeader(boolean show) {
|
||||
mPinnedHeaderFrameLayout.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
if (mAdapter != null) {
|
||||
outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mAdapter.isHighlightRequested());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
highlightPreferenceIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindPreferences() {
|
||||
registerObserverIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnbindPreferences() {
|
||||
unregisterObserverIfNeeded();
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading, boolean animate) {
|
||||
View loadingContainer = getView().findViewById(R.id.loading_container);
|
||||
LoadingViewController.handleLoadingContainer(loadingContainer, getListView(),
|
||||
!loading /* done */,
|
||||
animate);
|
||||
}
|
||||
|
||||
public void registerObserverIfNeeded() {
|
||||
if (!mIsDataSetObserverRegistered) {
|
||||
if (mCurrentRootAdapter != null) {
|
||||
mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
|
||||
}
|
||||
mCurrentRootAdapter = getListView().getAdapter();
|
||||
mCurrentRootAdapter.registerAdapterDataObserver(mDataSetObserver);
|
||||
mIsDataSetObserverRegistered = true;
|
||||
onDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterObserverIfNeeded() {
|
||||
if (mIsDataSetObserverRegistered) {
|
||||
if (mCurrentRootAdapter != null) {
|
||||
mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
|
||||
mCurrentRootAdapter = null;
|
||||
}
|
||||
mIsDataSetObserverRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void highlightPreferenceIfNeeded() {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
if (mAdapter != null) {
|
||||
mAdapter.requestHighlight(getView(), getListView(), mAppBarLayout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns initial expanded child count.
|
||||
* <p/>
|
||||
* Only override this method if the initial expanded child must be determined at run time.
|
||||
*/
|
||||
public int getInitialExpandedChildCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether preference is allowing to be displayed to the user.
|
||||
*
|
||||
* @param preference to check if it can be displayed to the user (not hidding in expand area).
|
||||
* @return {@code true} when preference is allowing to be displayed to the user.
|
||||
* {@code false} when preference is hidden in expand area and not been displayed to the user.
|
||||
*/
|
||||
protected boolean isPreferenceExpanded(Preference preference) {
|
||||
return ((mAdapter == null)
|
||||
|| (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether UI should be skipped in the initial SUW flow.
|
||||
*
|
||||
* @return {@code true} when UI should be skipped in the initial SUW flow.
|
||||
* {@code false} when UI should not be skipped in the initial SUW flow.
|
||||
*/
|
||||
protected boolean shouldSkipForInitialSUW() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onDataSetChanged() {
|
||||
highlightPreferenceIfNeeded();
|
||||
updateEmptyView();
|
||||
}
|
||||
|
||||
public LayoutPreference getHeaderView() {
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
protected void setHeaderView(int resource) {
|
||||
mHeader = new LayoutPreference(getPrefContext(), resource);
|
||||
mHeader.setSelectable(false);
|
||||
addPreferenceToTop(mHeader);
|
||||
}
|
||||
|
||||
protected void setHeaderView(View view) {
|
||||
mHeader = new LayoutPreference(getPrefContext(), view);
|
||||
mHeader.setSelectable(false);
|
||||
addPreferenceToTop(mHeader);
|
||||
}
|
||||
|
||||
private void addPreferenceToTop(LayoutPreference preference) {
|
||||
preference.setOrder(ORDER_FIRST);
|
||||
if (getPreferenceScreen() != null) {
|
||||
getPreferenceScreen().addPreference(preference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
|
||||
if (preferenceScreen != null && !preferenceScreen.isAttached()) {
|
||||
// Without ids generated, the RecyclerView won't animate changes to the preferences.
|
||||
preferenceScreen.setShouldUseGeneratedIds(mAnimationAllowed);
|
||||
}
|
||||
super.setPreferenceScreen(preferenceScreen);
|
||||
if (preferenceScreen != null) {
|
||||
if (mHeader != null) {
|
||||
preferenceScreen.addPreference(mHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateEmptyView() {
|
||||
if (mEmptyView == null) return;
|
||||
if (getPreferenceScreen() != null) {
|
||||
final View listContainer = getActivity().findViewById(android.R.id.list_container);
|
||||
boolean show = (getPreferenceScreen().getPreferenceCount()
|
||||
- (mHeader != null ? 1 : 0)) <= 0
|
||||
|| (listContainer != null && listContainer.getVisibility() != View.VISIBLE);
|
||||
mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEmptyView(View v) {
|
||||
if (mEmptyView != null) {
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
}
|
||||
mEmptyView = v;
|
||||
updateEmptyView();
|
||||
}
|
||||
|
||||
public View getEmptyView() {
|
||||
return mEmptyView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutManager onCreateLayoutManager() {
|
||||
mLayoutManager = new LinearLayoutManager(getContext());
|
||||
return mLayoutManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
final Bundle arguments = getArguments();
|
||||
mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen,
|
||||
arguments == null
|
||||
? null : arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY),
|
||||
mPreferenceHighlighted);
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
protected void setAnimationAllowed(boolean animationAllowed) {
|
||||
mAnimationAllowed = animationAllowed;
|
||||
}
|
||||
|
||||
protected void cacheRemoveAllPrefs(PreferenceGroup group) {
|
||||
mPreferenceCache = new ArrayMap<>();
|
||||
final int N = group.getPreferenceCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
Preference p = group.getPreference(i);
|
||||
if (TextUtils.isEmpty(p.getKey())) {
|
||||
continue;
|
||||
}
|
||||
mPreferenceCache.put(p.getKey(), p);
|
||||
}
|
||||
}
|
||||
|
||||
protected Preference getCachedPreference(String key) {
|
||||
return mPreferenceCache != null ? mPreferenceCache.remove(key) : null;
|
||||
}
|
||||
|
||||
protected void removeCachedPrefs(PreferenceGroup group) {
|
||||
for (Preference p : mPreferenceCache.values()) {
|
||||
group.removePreference(p);
|
||||
}
|
||||
mPreferenceCache = null;
|
||||
}
|
||||
|
||||
protected int getCachedCount() {
|
||||
return mPreferenceCache != null ? mPreferenceCache.size() : 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
public boolean removePreference(String key) {
|
||||
return removePreference(getPreferenceScreen(), key);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean removePreference(PreferenceGroup group, String key) {
|
||||
final int preferenceCount = group.getPreferenceCount();
|
||||
for (int i = 0; i < preferenceCount; i++) {
|
||||
final Preference preference = group.getPreference(i);
|
||||
final String curKey = preference.getKey();
|
||||
|
||||
if (TextUtils.equals(curKey, key)) {
|
||||
return group.removePreference(preference);
|
||||
}
|
||||
|
||||
if (preference instanceof PreferenceGroup) {
|
||||
if (removePreference((PreferenceGroup) preference, key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The name is intentionally made different from Activity#finish(), so that
|
||||
* users won't misunderstand its meaning.
|
||||
*/
|
||||
public final void finishFragment() {
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
|
||||
// Some helpers for functions used by the settings fragments when they were activities
|
||||
|
||||
/**
|
||||
* Returns the ContentResolver from the owning Activity.
|
||||
*/
|
||||
protected ContentResolver getContentResolver() {
|
||||
Context context = getActivity();
|
||||
if (context != null) {
|
||||
mContentResolver = context.getContentResolver();
|
||||
}
|
||||
return mContentResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified system service from the owning Activity.
|
||||
*/
|
||||
protected Object getSystemService(final String name) {
|
||||
return getActivity().getSystemService(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified system service from the owning Activity.
|
||||
*/
|
||||
protected <T> T getSystemService(final Class<T> serviceClass) {
|
||||
return getActivity().getSystemService(serviceClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PackageManager from the owning Activity.
|
||||
*/
|
||||
protected PackageManager getPackageManager() {
|
||||
return getActivity().getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
if (isRemoving()) {
|
||||
if (mDialogFragment != null) {
|
||||
mDialogFragment.dismiss();
|
||||
mDialogFragment = null;
|
||||
}
|
||||
RecyclerView view = getListView();
|
||||
if (view != null) {
|
||||
view.clearOnScrollListeners();
|
||||
}
|
||||
}
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
// Dialog management
|
||||
|
||||
protected void showDialog(int dialogId) {
|
||||
if (mDialogFragment != null) {
|
||||
Log.e(TAG, "Old dialog fragment not null!");
|
||||
}
|
||||
mDialogFragment = SettingsDialogFragment.newInstance(this, dialogId);
|
||||
mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(int dialogId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDialogMetricsCategory(int dialogId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void removeDialog(int dialogId) {
|
||||
// mDialogFragment may not be visible yet in parent fragment's onResume().
|
||||
// To be able to dismiss dialog at that time, don't check
|
||||
// mDialogFragment.isVisible().
|
||||
if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
|
||||
mDialogFragment.dismissAllowingStateLoss();
|
||||
}
|
||||
mDialogFragment = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OnCancelListener of the dialog shown. This method can only be
|
||||
* called after showDialog(int) and before removeDialog(int). The method
|
||||
* does nothing otherwise.
|
||||
*/
|
||||
protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
|
||||
if (mDialogFragment != null) {
|
||||
mDialogFragment.mOnCancelListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OnDismissListener of the dialog shown. This method can only be
|
||||
* called after showDialog(int) and before removeDialog(int). The method
|
||||
* does nothing otherwise.
|
||||
*/
|
||||
protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
|
||||
if (mDialogFragment != null) {
|
||||
mDialogFragment.mOnDismissListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDialogShowing() {
|
||||
// override in subclass to attach a dismiss listener, for instance
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
if (preference.getKey() == null) {
|
||||
// Auto-key preferences that don't have a key, so the dialog can find them.
|
||||
preference.setKey(UUID.randomUUID().toString());
|
||||
}
|
||||
DialogFragment f = null;
|
||||
if (preference instanceof RestrictedListPreference) {
|
||||
f = RestrictedListPreference.RestrictedListPreferenceDialogFragment
|
||||
.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomListPreference) {
|
||||
f = CustomListPreference.CustomListPreferenceDialogFragment
|
||||
.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomDialogPreferenceCompat) {
|
||||
f = CustomDialogPreferenceCompat.CustomPreferenceDialogFragment
|
||||
.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomEditTextPreferenceCompat) {
|
||||
f = CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment
|
||||
.newInstance(preference.getKey());
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
return;
|
||||
}
|
||||
f.setTargetFragment(this, 0);
|
||||
f.show(getFragmentManager(), "dialog_preference");
|
||||
onDialogShowing();
|
||||
}
|
||||
|
||||
public static class SettingsDialogFragment extends InstrumentedDialogFragment {
|
||||
private static final String KEY_DIALOG_ID = "key_dialog_id";
|
||||
private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";
|
||||
|
||||
private Fragment mParentFragment;
|
||||
|
||||
private DialogInterface.OnCancelListener mOnCancelListener;
|
||||
private DialogInterface.OnDismissListener mOnDismissListener;
|
||||
|
||||
public static SettingsDialogFragment newInstance(DialogCreatable fragment, int dialogId) {
|
||||
if (!(fragment instanceof Fragment)) {
|
||||
throw new IllegalArgumentException("fragment argument must be an instance of "
|
||||
+ Fragment.class.getName());
|
||||
}
|
||||
|
||||
final SettingsDialogFragment settingsDialogFragment = new SettingsDialogFragment();
|
||||
settingsDialogFragment.setParentFragment(fragment);
|
||||
settingsDialogFragment.setDialogId(dialogId);
|
||||
|
||||
return settingsDialogFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
if (mParentFragment == null) {
|
||||
return Instrumentable.METRICS_CATEGORY_UNKNOWN;
|
||||
}
|
||||
final int metricsCategory =
|
||||
((DialogCreatable) mParentFragment).getDialogMetricsCategory(mDialogId);
|
||||
if (metricsCategory <= 0) {
|
||||
throw new IllegalStateException("Dialog must provide a metrics category");
|
||||
}
|
||||
return metricsCategory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mParentFragment != null) {
|
||||
outState.putInt(KEY_DIALOG_ID, mDialogId);
|
||||
outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
|
||||
((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
|
||||
mParentFragment = getParentFragment();
|
||||
int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
|
||||
if (mParentFragment == null) {
|
||||
mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
|
||||
}
|
||||
if (!(mParentFragment instanceof DialogCreatable)) {
|
||||
throw new IllegalArgumentException(
|
||||
(mParentFragment != null
|
||||
? mParentFragment.getClass().getName()
|
||||
: mParentFragmentId)
|
||||
+ " must implement "
|
||||
+ DialogCreatable.class.getName());
|
||||
}
|
||||
// This dialog fragment could be created from non-SettingsPreferenceFragment
|
||||
if (mParentFragment instanceof SettingsPreferenceFragment) {
|
||||
// restore mDialogFragment in mParentFragment
|
||||
((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
|
||||
}
|
||||
}
|
||||
return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
if (mOnCancelListener != null) {
|
||||
mOnCancelListener.onCancel(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public int getDialogId() {
|
||||
return mDialogId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
// This dialog fragment could be created from non-SettingsPreferenceFragment
|
||||
if (mParentFragment instanceof SettingsPreferenceFragment) {
|
||||
// in case the dialog is not explicitly removed by removeDialog()
|
||||
if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
|
||||
((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setParentFragment(DialogCreatable fragment) {
|
||||
mParentFragment = (Fragment) fragment;
|
||||
}
|
||||
|
||||
private void setDialogId(int dialogId) {
|
||||
mDialogId = dialogId;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasNextButton() {
|
||||
return ((ButtonBarHandler) getActivity()).hasNextButton();
|
||||
}
|
||||
|
||||
protected Button getNextButton() {
|
||||
return ((ButtonBarHandler) getActivity()).getNextButton();
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
if (getFragmentManager().getBackStackEntryCount() > 0) {
|
||||
getFragmentManager().popBackStack();
|
||||
} else {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected Intent getIntent() {
|
||||
if (getActivity() == null) {
|
||||
return null;
|
||||
}
|
||||
return getActivity().getIntent();
|
||||
}
|
||||
|
||||
protected void setResult(int result, Intent intent) {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
getActivity().setResult(result, intent);
|
||||
}
|
||||
|
||||
protected void setResult(int result) {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
getActivity().setResult(result);
|
||||
}
|
||||
|
||||
protected boolean isFinishingOrDestroyed() {
|
||||
final Activity activity = getActivity();
|
||||
return activity == null || activity.isFinishing() || activity.isDestroyed();
|
||||
}
|
||||
|
||||
protected void replaceEnterprisePreferenceScreenTitle(String overrideKey, int resource) {
|
||||
getActivity().setTitle(mDevicePolicyManager.getResources().getString(
|
||||
overrideKey, () -> getString(resource)));
|
||||
}
|
||||
|
||||
public void replaceEnterpriseStringSummary(
|
||||
String preferenceKey, String overrideKey, int resource) {
|
||||
Preference preference = findPreference(preferenceKey);
|
||||
if (preference == null) {
|
||||
Log.d(TAG, "Could not find enterprise preference " + preferenceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setSummary(
|
||||
mDevicePolicyManager.getResources().getString(overrideKey,
|
||||
() -> getString(resource)));
|
||||
}
|
||||
|
||||
public void replaceEnterpriseStringTitle(
|
||||
String preferenceKey, String overrideKey, int resource) {
|
||||
Preference preference = findPreference(preferenceKey);
|
||||
if (preference == null) {
|
||||
Log.d(TAG, "Could not find enterprise preference " + preferenceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setTitle(
|
||||
mDevicePolicyManager.getResources().getString(overrideKey,
|
||||
() -> getString(resource)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.settings.notification.RedactionInterstitial;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
/**
|
||||
* Setup Wizard's version of RedactionInterstitial screen. It inherits the logic and basic structure
|
||||
* from RedactionInterstitial class, and should remain similar to that behaviorally. This class
|
||||
* should only overload base methods for minor theme and behavior differences specific to Setup
|
||||
* Wizard. Other changes should be done to RedactionInterstitial class instead and let this class
|
||||
* inherit those changes.
|
||||
*/
|
||||
public class SetupRedactionInterstitial extends RedactionInterstitial {
|
||||
|
||||
/**
|
||||
* Set the enabled state of SetupRedactionInterstitial activity to configure whether it is shown
|
||||
* as part of setup wizard's optional steps.
|
||||
*/
|
||||
public static void setEnabled(Context context, boolean enabled) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
ComponentName componentName = new ComponentName(context, SetupRedactionInterstitial.class);
|
||||
packageManager.setComponentEnabledSetting(
|
||||
componentName,
|
||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstance) {
|
||||
// Only allow to start the activity from Setup Wizard.
|
||||
if (!WizardManagerHelper.isAnySetupWizard(getIntent())) {
|
||||
finish();
|
||||
}
|
||||
super.onCreate(savedInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent modIntent = new Intent(super.getIntent());
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT,
|
||||
SetupRedactionInterstitialFragment.class.getName());
|
||||
return modIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return SetupRedactionInterstitialFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
public static class SetupRedactionInterstitialFragment extends RedactionInterstitialFragment {
|
||||
|
||||
// Setup wizard specific UI customizations can be done here
|
||||
}
|
||||
}
|
||||
147
Settings/src/com/android/settings/SetupWizardUtils.java
Normal file
147
Settings/src/com/android/settings/SetupWizardUtils.java
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_FIRST_RUN;
|
||||
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.sysprop.SetupWizardProperties;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class SetupWizardUtils {
|
||||
|
||||
public static String getThemeString(Intent intent) {
|
||||
String theme = intent.getStringExtra(WizardManagerHelper.EXTRA_THEME);
|
||||
if (theme == null) {
|
||||
theme = SetupWizardProperties.theme().orElse("");
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
public static int getTheme(Context context, Intent intent) {
|
||||
String theme = getThemeString(intent);
|
||||
// TODO(yukl): Move to ThemeResolver and add any additional required attributes in
|
||||
// onApplyThemeResource using Theme overlays
|
||||
if (theme != null) {
|
||||
if (WizardManagerHelper.isAnySetupWizard(intent)) {
|
||||
if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
|
||||
switch (theme) {
|
||||
case ThemeHelper.THEME_GLIF_V4_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V4:
|
||||
return R.style.GlifV4Theme_DayNight;
|
||||
case ThemeHelper.THEME_GLIF_V3_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V3:
|
||||
return R.style.GlifV3Theme_DayNight;
|
||||
case ThemeHelper.THEME_GLIF_V2_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V2:
|
||||
return R.style.GlifV2Theme_DayNight;
|
||||
case ThemeHelper.THEME_GLIF_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF:
|
||||
return R.style.GlifTheme_DayNight;
|
||||
}
|
||||
} else {
|
||||
switch (theme) {
|
||||
case ThemeHelper.THEME_GLIF_V4_LIGHT:
|
||||
return R.style.GlifV4Theme_Light;
|
||||
case ThemeHelper.THEME_GLIF_V4:
|
||||
return R.style.GlifV4Theme;
|
||||
case ThemeHelper.THEME_GLIF_V3_LIGHT:
|
||||
return R.style.GlifV3Theme_Light;
|
||||
case ThemeHelper.THEME_GLIF_V3:
|
||||
return R.style.GlifV3Theme;
|
||||
case ThemeHelper.THEME_GLIF_V2_LIGHT:
|
||||
return R.style.GlifV2Theme_Light;
|
||||
case ThemeHelper.THEME_GLIF_V2:
|
||||
return R.style.GlifV2Theme;
|
||||
case ThemeHelper.THEME_GLIF_LIGHT:
|
||||
return R.style.GlifTheme_Light;
|
||||
case ThemeHelper.THEME_GLIF:
|
||||
return R.style.GlifTheme;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (theme) {
|
||||
case ThemeHelper.THEME_GLIF_V4_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V4:
|
||||
return R.style.GlifV4Theme;
|
||||
case ThemeHelper.THEME_GLIF_V3_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V3:
|
||||
return R.style.GlifV3Theme;
|
||||
case ThemeHelper.THEME_GLIF_V2_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF_V2:
|
||||
return R.style.GlifV2Theme;
|
||||
case ThemeHelper.THEME_GLIF_LIGHT:
|
||||
case ThemeHelper.THEME_GLIF:
|
||||
return R.style.GlifTheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
return R.style.GlifTheme;
|
||||
}
|
||||
|
||||
public static int getTransparentTheme(Context context, Intent intent) {
|
||||
int transparentTheme;
|
||||
final int suwTheme = getTheme(context, intent);
|
||||
if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
|
||||
transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
|
||||
} else {
|
||||
transparentTheme = R.style.GlifV2Theme_Light_Transparent;
|
||||
}
|
||||
if (suwTheme == R.style.GlifV3Theme_DayNight) {
|
||||
transparentTheme = R.style.GlifV3Theme_DayNight_Transparent;
|
||||
} else if (suwTheme == R.style.GlifV3Theme_Light) {
|
||||
transparentTheme = R.style.GlifV3Theme_Light_Transparent;
|
||||
} else if (suwTheme == R.style.GlifV2Theme_DayNight) {
|
||||
transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
|
||||
} else if (suwTheme == R.style.GlifV2Theme_Light) {
|
||||
transparentTheme = R.style.GlifV2Theme_Light_Transparent;
|
||||
} else if (suwTheme == R.style.GlifTheme_DayNight) {
|
||||
transparentTheme = R.style.SetupWizardTheme_DayNight_Transparent;
|
||||
} else if (suwTheme == R.style.GlifTheme_Light) {
|
||||
transparentTheme = R.style.SetupWizardTheme_Light_Transparent;
|
||||
} else if (suwTheme == R.style.GlifV3Theme) {
|
||||
transparentTheme = R.style.GlifV3Theme_Transparent;
|
||||
} else if (suwTheme == R.style.GlifV2Theme) {
|
||||
transparentTheme = R.style.GlifV2Theme_Transparent;
|
||||
} else if (suwTheme == R.style.GlifTheme) {
|
||||
transparentTheme = R.style.SetupWizardTheme_Transparent;
|
||||
}
|
||||
return transparentTheme;
|
||||
}
|
||||
|
||||
public static void copySetupExtras(Intent fromIntent, Intent toIntent) {
|
||||
WizardManagerHelper.copyWizardManagerExtras(fromIntent, toIntent);
|
||||
}
|
||||
|
||||
public static Bundle copyLifecycleExtra(Bundle srcBundle, Bundle dstBundle) {
|
||||
for (String key :
|
||||
Arrays.asList(
|
||||
EXTRA_IS_FIRST_RUN,
|
||||
EXTRA_IS_SETUP_FLOW)) {
|
||||
dstBundle.putBoolean(key, srcBundle.getBoolean(key, false));
|
||||
}
|
||||
return dstBundle;
|
||||
}
|
||||
}
|
||||
364
Settings/src/com/android/settings/SidecarFragment.java
Normal file
364
Settings/src/com/android/settings/SidecarFragment.java
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* 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.settings;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* A headless fragment encapsulating a long-running action such as a network RPC surviving rotation.
|
||||
*
|
||||
* <p>Subclasses should implement their own state machine, updating the state on each state change
|
||||
* via {@link #setState(int, int)}. They can define their own states, however, it is suggested that
|
||||
* the pre-defined {@link @State} constants are used and customizations are implemented via
|
||||
* substates. Custom states must be outside the range of pre-defined states.
|
||||
*
|
||||
* <p>It is safe to update the state at any time, but state updates must originate from the main
|
||||
* thread.
|
||||
*
|
||||
* <p>A listener can be attached that receives state updates while it's registered. Note that state
|
||||
* change events can occur at any point in time and hence a registered listener should unregister if
|
||||
* it cannot act upon the state change (typically a non-resumed fragment).
|
||||
*
|
||||
* <p>Listeners can receive state changes for the same state/substate combination, so listeners
|
||||
* should make sure to be idempotent during state change events.
|
||||
*
|
||||
* <p>If a SidecarFragment is only relevant during the lifetime of another fragment (for example, a
|
||||
* sidecar performing a details request for a DetailsFragment), that fragment needs to become the
|
||||
* managing fragment of the sidecar.
|
||||
*
|
||||
* <h2>Managing fragment responsibilities</h2>
|
||||
*
|
||||
* <ol>
|
||||
* <li>Instantiates the sidecar fragment when necessary, preferably in {@link #onStart}.
|
||||
* <li>Removes the sidecar fragment when it's no longer used or when itself is removed. Removal of
|
||||
* the managing fragment can be detected by checking {@link #isRemoving} in {@link #onStop}.
|
||||
* <br>
|
||||
* <li>Registers as a listener in {@link #onResume()}, unregisters in {@link #onPause()}.
|
||||
* <li>Starts the long-running operation by calling into the sidecar.
|
||||
* <li>Receives state updates via {@link Listener#onStateChange(SidecarFragment)} and updates the
|
||||
* UI accordingly.
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Managing fragment example</h2>
|
||||
*
|
||||
* <pre>
|
||||
* public class MainFragment implements SidecarFragment.Listener {
|
||||
* private static final String TAG_SOME_SIDECAR = ...;
|
||||
* private static final String KEY_SOME_SIDECAR_STATE = ...;
|
||||
*
|
||||
* private SomeSidecarFragment mSidecar;
|
||||
*
|
||||
* @Override
|
||||
* public void onStart() {
|
||||
* super.onStart();
|
||||
* Bundle args = ...; // optional args
|
||||
* mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR,
|
||||
* SidecarFragment.class, args);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onResume() {
|
||||
* mSomeSidecar.addListener(this);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onPause() {
|
||||
* mSomeSidecar.removeListener(this):
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class SidecarFragment extends Fragment {
|
||||
|
||||
private static final String TAG = "SidecarFragment";
|
||||
|
||||
/**
|
||||
* Get an instance of this sidecar.
|
||||
*
|
||||
* <p>Will return the existing instance if one is already present. Note that the args will not
|
||||
* be used in this situation, so args must be constant for any particular fragment manager and
|
||||
* tag.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T extends SidecarFragment> T get(
|
||||
FragmentManager fm, String tag, Class<T> clazz, Bundle args) {
|
||||
T fragment = (T) fm.findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
try {
|
||||
fragment = clazz.newInstance();
|
||||
} catch (java.lang.InstantiationException e) {
|
||||
throw new InstantiationException("Unable to create fragment", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Unable to create fragment", e);
|
||||
}
|
||||
if (args != null) {
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
fm.beginTransaction().add(fragment, tag).commit();
|
||||
// No real harm in doing this here - get() should generally only be called from onCreate
|
||||
// which is on the main thread - and it allows us to start running the sidecar on this
|
||||
// instance immediately rather than having to wait until the transaction commits.
|
||||
fm.executePendingTransactions();
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/** State definitions. @see {@link #getState} */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({State.INIT, State.RUNNING, State.SUCCESS, State.ERROR})
|
||||
public @interface State {
|
||||
/** Initial idling state. */
|
||||
int INIT = 0;
|
||||
|
||||
/** The long-running operation is in progress. */
|
||||
int RUNNING = 1;
|
||||
|
||||
/** The long-running operation has succeeded. */
|
||||
int SUCCESS = 2;
|
||||
|
||||
/** The long-running operation has failed. */
|
||||
int ERROR = 3;
|
||||
}
|
||||
|
||||
/** Substate definitions. @see {@link #getSubstate} */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
Substate.UNUSED,
|
||||
Substate.RUNNING_BIND_SERVICE,
|
||||
Substate.RUNNING_GET_ACTIVATION_CODE,
|
||||
})
|
||||
public @interface Substate {
|
||||
// Unknown/unused substate.
|
||||
int UNUSED = 0;
|
||||
int RUNNING_BIND_SERVICE = 1;
|
||||
int RUNNING_GET_ACTIVATION_CODE = 2;
|
||||
|
||||
// Future tags: 3+
|
||||
}
|
||||
|
||||
/** **************************************** */
|
||||
private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
// Used to track whether onCreate has been called yet.
|
||||
private boolean mCreated;
|
||||
|
||||
@State private int mState;
|
||||
@Substate private int mSubstate;
|
||||
|
||||
/** A listener receiving state change events. */
|
||||
public interface Listener {
|
||||
|
||||
/**
|
||||
* Called upon any state or substate change.
|
||||
*
|
||||
* <p>The new state can be queried through {@link #getState} and {@link #getSubstate}.
|
||||
*
|
||||
* <p>Called from the main thread.
|
||||
*
|
||||
* @param fragment the SidecarFragment that changed its state
|
||||
*/
|
||||
void onStateChange(SidecarFragment fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
mCreated = true;
|
||||
setState(State.INIT, Substate.UNUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mCreated = false;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener that will receive subsequent state changes.
|
||||
*
|
||||
* <p>A {@link Listener#onStateChange(SidecarFragment)} event is fired as part of this call
|
||||
* unless {@link #onCreate} has not yet been called (which means that it's unsafe to access this
|
||||
* fragment as it has not been setup or restored completely). In that case, the future call to
|
||||
* onCreate will trigger onStateChange on registered listener.
|
||||
*
|
||||
* <p>Must be called from the main thread.
|
||||
*
|
||||
* @param listener a listener, or null for unregistering the current listener
|
||||
*/
|
||||
public void addListener(Listener listener) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
mListeners.add(listener);
|
||||
if (mCreated) {
|
||||
notifyListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously registered listener.
|
||||
*
|
||||
* @return {@code true} if the listener was removed, {@code false} if there was no such listener
|
||||
* registered.
|
||||
*/
|
||||
public boolean removeListener(Listener listener) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
return mListeners.remove(listener);
|
||||
}
|
||||
|
||||
/** Returns the current state. */
|
||||
@State
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/** Returns the current substate. */
|
||||
@Substate
|
||||
public int getSubstate() {
|
||||
return mSubstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the sidecar to its initial state.
|
||||
*
|
||||
* <p>Implementers can override this method to perform additional reset tasks, but must call the
|
||||
* super method.
|
||||
*/
|
||||
@CallSuper
|
||||
public void reset() {
|
||||
setState(State.INIT, Substate.UNUSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state and substate and notifies the registered listener.
|
||||
*
|
||||
* <p>Must be called from the main thread.
|
||||
*
|
||||
* @param state the state to transition to
|
||||
* @param substate the substate to transition to
|
||||
*/
|
||||
protected void setState(@State int state, @Substate int substate) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
mState = state;
|
||||
mSubstate = substate;
|
||||
notifyAllListeners();
|
||||
printState();
|
||||
}
|
||||
|
||||
private void notifyAllListeners() {
|
||||
for (Listener listener : mListeners) {
|
||||
notifyListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListener(Listener listener) {
|
||||
listener.onStateChange(this);
|
||||
}
|
||||
|
||||
/** Prints the state of the sidecar. */
|
||||
public void printState() {
|
||||
StringBuilder sb =
|
||||
new StringBuilder("SidecarFragment.setState(): Sidecar Class: ")
|
||||
.append(getClass().getCanonicalName());
|
||||
sb.append(", State: ");
|
||||
switch (mState) {
|
||||
case SidecarFragment.State.INIT:
|
||||
sb.append("State.INIT");
|
||||
break;
|
||||
case SidecarFragment.State.RUNNING:
|
||||
sb.append("State.RUNNING");
|
||||
break;
|
||||
case SidecarFragment.State.SUCCESS:
|
||||
sb.append("State.SUCCESS");
|
||||
break;
|
||||
case SidecarFragment.State.ERROR:
|
||||
sb.append("State.ERROR");
|
||||
break;
|
||||
default:
|
||||
sb.append(mState);
|
||||
break;
|
||||
}
|
||||
switch (mSubstate) {
|
||||
case SidecarFragment.Substate.UNUSED:
|
||||
sb.append(", Substate.UNUSED");
|
||||
break;
|
||||
default:
|
||||
sb.append(", ").append(mSubstate);
|
||||
break;
|
||||
}
|
||||
|
||||
Log.v(TAG, sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
Locale.US,
|
||||
"SidecarFragment[mState=%d, mSubstate=%d]: %s",
|
||||
mState,
|
||||
mSubstate,
|
||||
super.toString());
|
||||
}
|
||||
|
||||
/** The State of the sidecar status. */
|
||||
public static final class States {
|
||||
public static final States SUCCESS = States.create(State.SUCCESS, Substate.UNUSED);
|
||||
public static final States ERROR = States.create(State.ERROR, Substate.UNUSED);
|
||||
|
||||
@State public final int state;
|
||||
@Substate public final int substate;
|
||||
|
||||
/** Creates a new sidecar state. */
|
||||
public static States create(@State int state, @Substate int substate) {
|
||||
return new States(state, substate);
|
||||
}
|
||||
|
||||
public States(@State int state, @Substate int substate) {
|
||||
this.state = state;
|
||||
this.substate = substate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof States)) {
|
||||
return false;
|
||||
}
|
||||
States other = (States) o;
|
||||
return this.state == other.state && this.substate == other.substate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return state * 31 + substate;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Settings/src/com/android/settings/SubSettings.java
Normal file
38
Settings/src/com/android/settings/SubSettings.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Stub class for showing sub-settings; we can't use the main Settings class
|
||||
* since for our app it is a special singleTask class.
|
||||
*/
|
||||
public class SubSettings extends SettingsActivity {
|
||||
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
Log.d("SubSettings", "Launching fragment " + fragmentName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
105
Settings/src/com/android/settings/SummaryPreference.java
Normal file
105
Settings/src/com/android/settings/SummaryPreference.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
/**
|
||||
* Provides a summary of a setting page in a preference. Such as memory or data usage.
|
||||
*/
|
||||
public class SummaryPreference extends Preference {
|
||||
|
||||
private static final String TAG = "SummaryPreference";
|
||||
private String mAmount;
|
||||
private String mUnits;
|
||||
|
||||
private boolean mChartEnabled = true;
|
||||
private float mLeftRatio, mMiddleRatio, mRightRatio;
|
||||
private String mStartLabel;
|
||||
private String mEndLabel;
|
||||
|
||||
public SummaryPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setLayoutResource(R.layout.settings_summary_preference);
|
||||
}
|
||||
|
||||
public void setChartEnabled(boolean enabled) {
|
||||
if (mChartEnabled != enabled) {
|
||||
mChartEnabled = enabled;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAmount(String amount) {
|
||||
mAmount = amount;
|
||||
if (mAmount != null && mUnits != null) {
|
||||
setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large),
|
||||
mAmount, mUnits));
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnits(String units) {
|
||||
mUnits = units;
|
||||
if (mAmount != null && mUnits != null) {
|
||||
setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large),
|
||||
mAmount, mUnits));
|
||||
}
|
||||
}
|
||||
|
||||
public void setLabels(String start, String end) {
|
||||
mStartLabel = start;
|
||||
mEndLabel = end;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public void setRatios(float left, float middle, float right) {
|
||||
mLeftRatio = left;
|
||||
mMiddleRatio = middle;
|
||||
mRightRatio = right;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
final ProgressBar colorBar = holder.itemView.findViewById(R.id.color_bar);
|
||||
|
||||
if (mChartEnabled) {
|
||||
colorBar.setVisibility(View.VISIBLE);
|
||||
int progress = (int) (mLeftRatio * 100);
|
||||
colorBar.setProgress(progress);
|
||||
colorBar.setSecondaryProgress(progress + (int) (mMiddleRatio * 100));
|
||||
} else {
|
||||
colorBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mChartEnabled && (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel))) {
|
||||
holder.findViewById(R.id.label_bar).setVisibility(View.VISIBLE);
|
||||
((TextView) holder.findViewById(android.R.id.text1)).setText(mStartLabel);
|
||||
((TextView) holder.findViewById(android.R.id.text2)).setText(mEndLabel);
|
||||
} else {
|
||||
holder.findViewById(R.id.label_bar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Settings/src/com/android/settings/TestingSettings.java
Normal file
59
Settings/src/com/android/settings/TestingSettings.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.network.telephony.MobileNetworkUtils;
|
||||
|
||||
public class TestingSettings extends SettingsPreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.testing_settings);
|
||||
|
||||
if (!isRadioInfoVisible(getContext())) {
|
||||
PreferenceScreen preferenceScreen = (PreferenceScreen)
|
||||
findPreference("radio_info_settings");
|
||||
getPreferenceScreen().removePreference(preferenceScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected boolean isRadioInfoVisible(Context context) {
|
||||
UserManager um = context.getSystemService(UserManager.class);
|
||||
if (um != null) {
|
||||
if (!um.isAdminUser()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !MobileNetworkUtils.isMobileNetworkUserRestricted(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.TESTING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.settings.Settings.TestingSettingsActivity;
|
||||
|
||||
|
||||
public class TestingSettingsBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
public TestingSettingsBroadcastReceiver() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent != null && intent.getAction() != null
|
||||
&& intent.getAction().equals(TelephonyManager.ACTION_SECRET_CODE)) {
|
||||
Intent i = new Intent(Intent.ACTION_MAIN);
|
||||
i.setClass(context, TestingSettingsActivity.class);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extension of FrameLayout that consumes all touch events.
|
||||
*/
|
||||
public class TouchBlockingFrameLayout extends FrameLayout {
|
||||
public TouchBlockingFrameLayout(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.TrustedCredentialsFragment.CertHolder;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
class TrustedCredentialsDialogBuilder extends AlertDialog.Builder {
|
||||
public interface DelegateInterface {
|
||||
List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder);
|
||||
void removeOrInstallCert(CertHolder certHolder);
|
||||
boolean startConfirmCredentialIfNotConfirmed(int userId,
|
||||
IntConsumer onCredentialConfirmedListener);
|
||||
}
|
||||
|
||||
private final DialogEventHandler mDialogEventHandler;
|
||||
|
||||
public TrustedCredentialsDialogBuilder(Activity activity, DelegateInterface delegate) {
|
||||
super(activity);
|
||||
mDialogEventHandler = new DialogEventHandler(activity, delegate);
|
||||
|
||||
initDefaultBuilderParams();
|
||||
}
|
||||
|
||||
public TrustedCredentialsDialogBuilder setCertHolder(CertHolder certHolder) {
|
||||
return setCertHolders(certHolder == null ? new CertHolder[0]
|
||||
: new CertHolder[]{certHolder});
|
||||
}
|
||||
|
||||
public TrustedCredentialsDialogBuilder setCertHolders(@NonNull CertHolder[] certHolders) {
|
||||
mDialogEventHandler.setCertHolders(certHolders);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
AlertDialog dialog = super.create();
|
||||
dialog.setOnShowListener(mDialogEventHandler);
|
||||
mDialogEventHandler.setDialog(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void initDefaultBuilderParams() {
|
||||
setTitle(com.android.internal.R.string.ssl_certificate);
|
||||
setView(mDialogEventHandler.mRootContainer);
|
||||
|
||||
// Enable buttons here. The actual labels and listeners are configured in nextOrDismiss
|
||||
setPositiveButton(R.string.trusted_credentials_trust_label, null);
|
||||
setNegativeButton(android.R.string.ok, null);
|
||||
}
|
||||
|
||||
private static class DialogEventHandler implements DialogInterface.OnShowListener,
|
||||
View.OnClickListener {
|
||||
private static final long OUT_DURATION_MS = 300;
|
||||
private static final long IN_DURATION_MS = 200;
|
||||
|
||||
private final Activity mActivity;
|
||||
private final DevicePolicyManager mDpm;
|
||||
private final UserManager mUserManager;
|
||||
private final DelegateInterface mDelegate;
|
||||
private final LinearLayout mRootContainer;
|
||||
|
||||
private int mCurrentCertIndex = -1;
|
||||
private AlertDialog mDialog;
|
||||
private Button mPositiveButton;
|
||||
private Button mNegativeButton;
|
||||
private boolean mNeedsApproval;
|
||||
private CertHolder[] mCertHolders = new CertHolder[0];
|
||||
private View mCurrentCertLayout = null;
|
||||
|
||||
public DialogEventHandler(Activity activity, DelegateInterface delegate) {
|
||||
mActivity = activity;
|
||||
mDpm = activity.getSystemService(DevicePolicyManager.class);
|
||||
mUserManager = activity.getSystemService(UserManager.class);
|
||||
mDelegate = delegate;
|
||||
|
||||
mRootContainer = new LinearLayout(mActivity);
|
||||
mRootContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
}
|
||||
|
||||
public void setDialog(AlertDialog dialog) {
|
||||
mDialog = dialog;
|
||||
}
|
||||
|
||||
public void setCertHolders(CertHolder[] certHolder) {
|
||||
mCertHolders = certHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
// Config the display content only when the dialog is shown because the
|
||||
// positive/negative buttons don't exist until the dialog is shown
|
||||
nextOrDismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == mPositiveButton) {
|
||||
if (mNeedsApproval) {
|
||||
onClickTrust();
|
||||
} else {
|
||||
onClickOk();
|
||||
}
|
||||
} else if (view == mNegativeButton) {
|
||||
onClickEnableOrDisable();
|
||||
}
|
||||
}
|
||||
|
||||
private void onClickOk() {
|
||||
nextOrDismiss();
|
||||
}
|
||||
|
||||
private void onClickTrust() {
|
||||
CertHolder certHolder = getCurrentCertInfo();
|
||||
if (!mDelegate.startConfirmCredentialIfNotConfirmed(certHolder.getUserId(),
|
||||
this::onCredentialConfirmed)) {
|
||||
mDpm.approveCaCert(certHolder.getAlias(), certHolder.getUserId(), true);
|
||||
nextOrDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void onClickEnableOrDisable() {
|
||||
final CertHolder certHolder = getCurrentCertInfo();
|
||||
DialogInterface.OnClickListener onConfirm = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mDelegate.removeOrInstallCert(certHolder);
|
||||
nextOrDismiss();
|
||||
}
|
||||
};
|
||||
if (certHolder.isSystemCert()) {
|
||||
// Removing system certs is reversible, so skip confirmation.
|
||||
onConfirm.onClick(null, -1);
|
||||
} else {
|
||||
new AlertDialog.Builder(mActivity)
|
||||
.setMessage(R.string.trusted_credentials_remove_confirmation)
|
||||
.setPositiveButton(android.R.string.ok, onConfirm)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void onCredentialConfirmed(int userId) {
|
||||
if (mDialog.isShowing() && mNeedsApproval && getCurrentCertInfo() != null
|
||||
&& getCurrentCertInfo().getUserId() == userId) {
|
||||
// Treat it as user just clicks "trust" for this cert
|
||||
onClickTrust();
|
||||
}
|
||||
}
|
||||
|
||||
private CertHolder getCurrentCertInfo() {
|
||||
return mCurrentCertIndex < mCertHolders.length ? mCertHolders[mCurrentCertIndex] : null;
|
||||
}
|
||||
|
||||
private void nextOrDismiss() {
|
||||
mCurrentCertIndex++;
|
||||
// find next non-null cert or dismiss
|
||||
while (mCurrentCertIndex < mCertHolders.length && getCurrentCertInfo() == null) {
|
||||
mCurrentCertIndex++;
|
||||
}
|
||||
|
||||
if (mCurrentCertIndex >= mCertHolders.length) {
|
||||
mDialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
updateViewContainer();
|
||||
updatePositiveButton();
|
||||
updateNegativeButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if current user or parent user is guarded by screenlock
|
||||
*/
|
||||
private boolean isUserSecure(int userId) {
|
||||
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mActivity);
|
||||
if (lockPatternUtils.isSecure(userId)) {
|
||||
return true;
|
||||
}
|
||||
UserInfo parentUser = mUserManager.getProfileParent(userId);
|
||||
if (parentUser == null) {
|
||||
return false;
|
||||
}
|
||||
return lockPatternUtils.isSecure(parentUser.id);
|
||||
}
|
||||
|
||||
private void updatePositiveButton() {
|
||||
final CertHolder certHolder = getCurrentCertInfo();
|
||||
mNeedsApproval = !certHolder.isSystemCert()
|
||||
&& isUserSecure(certHolder.getUserId())
|
||||
&& !mDpm.isCaCertApproved(certHolder.getAlias(), certHolder.getUserId());
|
||||
|
||||
final boolean isProfileOrDeviceOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
|
||||
mActivity, UserHandle.of(certHolder.getUserId())) != null;
|
||||
|
||||
// Show trust button only when it requires consumer user (non-PO/DO) to approve
|
||||
CharSequence displayText = mActivity.getText(!isProfileOrDeviceOwner && mNeedsApproval
|
||||
? R.string.trusted_credentials_trust_label
|
||||
: android.R.string.ok);
|
||||
mPositiveButton = updateButton(DialogInterface.BUTTON_POSITIVE, displayText);
|
||||
}
|
||||
|
||||
private void updateNegativeButton() {
|
||||
final CertHolder certHolder = getCurrentCertInfo();
|
||||
final boolean showRemoveButton = !mUserManager.hasUserRestriction(
|
||||
UserManager.DISALLOW_CONFIG_CREDENTIALS,
|
||||
new UserHandle(certHolder.getUserId()));
|
||||
CharSequence displayText = mActivity.getText(getButtonLabel(certHolder));
|
||||
mNegativeButton = updateButton(DialogInterface.BUTTON_NEGATIVE, displayText);
|
||||
mNegativeButton.setVisibility(showRemoveButton ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* mDialog.setButton doesn't trigger text refresh since mDialog has been shown.
|
||||
* It's invoked only in case mDialog is refreshed.
|
||||
* setOnClickListener is invoked to avoid dismiss dialog onClick
|
||||
*/
|
||||
private Button updateButton(int buttonType, CharSequence displayText) {
|
||||
mDialog.setButton(buttonType, displayText, (DialogInterface.OnClickListener) null);
|
||||
Button button = mDialog.getButton(buttonType);
|
||||
button.setText(displayText);
|
||||
button.setOnClickListener(this);
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
private void updateViewContainer() {
|
||||
CertHolder certHolder = getCurrentCertInfo();
|
||||
LinearLayout nextCertLayout = getCertLayout(certHolder);
|
||||
|
||||
// Displaying first cert doesn't require animation
|
||||
if (mCurrentCertLayout == null) {
|
||||
mCurrentCertLayout = nextCertLayout;
|
||||
mRootContainer.addView(mCurrentCertLayout);
|
||||
} else {
|
||||
animateViewTransition(nextCertLayout);
|
||||
}
|
||||
}
|
||||
|
||||
private LinearLayout getCertLayout(final CertHolder certHolder) {
|
||||
final ArrayList<View> views = new ArrayList<View>();
|
||||
final ArrayList<String> titles = new ArrayList<String>();
|
||||
List<X509Certificate> certificates = mDelegate.getX509CertsFromCertHolder(certHolder);
|
||||
if (certificates != null) {
|
||||
for (X509Certificate certificate : certificates) {
|
||||
SslCertificate sslCert = new SslCertificate(certificate);
|
||||
views.add(sslCert.inflateCertificateView(mActivity));
|
||||
titles.add(sslCert.getIssuedTo().getCName());
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(mActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
titles);
|
||||
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
Spinner spinner = new Spinner(mActivity);
|
||||
spinner.setAdapter(arrayAdapter);
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
for (int i = 0; i < views.size(); i++) {
|
||||
views.get(i).setVisibility(i == position ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout certLayout = new LinearLayout(mActivity);
|
||||
certLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
// Prevent content overlapping with spinner
|
||||
certLayout.setClipChildren(true);
|
||||
certLayout.addView(spinner);
|
||||
for (int i = 0; i < views.size(); ++i) {
|
||||
View certificateView = views.get(i);
|
||||
// Show first cert by default
|
||||
certificateView.setVisibility(i == 0 ? View.VISIBLE : View.GONE);
|
||||
certLayout.addView(certificateView);
|
||||
}
|
||||
|
||||
return certLayout;
|
||||
}
|
||||
|
||||
private static int getButtonLabel(CertHolder certHolder) {
|
||||
return certHolder.isSystemCert() ? ( certHolder.isDeleted()
|
||||
? R.string.trusted_credentials_enable_label
|
||||
: R.string.trusted_credentials_disable_label )
|
||||
: R.string.trusted_credentials_remove_label;
|
||||
}
|
||||
|
||||
/* Animation code */
|
||||
private void animateViewTransition(final View nextCertView) {
|
||||
animateOldContent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addAndAnimateNewContent(nextCertView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animateOldContent(Runnable callback) {
|
||||
// Fade out
|
||||
mCurrentCertLayout.animate()
|
||||
.alpha(0)
|
||||
.setDuration(OUT_DURATION_MS)
|
||||
.setInterpolator(AnimationUtils.loadInterpolator(mActivity,
|
||||
android.R.interpolator.fast_out_linear_in))
|
||||
.withEndAction(callback)
|
||||
.start();
|
||||
}
|
||||
|
||||
private void addAndAnimateNewContent(View nextCertLayout) {
|
||||
mCurrentCertLayout = nextCertLayout;
|
||||
mRootContainer.removeAllViews();
|
||||
mRootContainer.addView(nextCertLayout);
|
||||
|
||||
mRootContainer.addOnLayoutChangeListener( new View.OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
||||
int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
mRootContainer.removeOnLayoutChangeListener(this);
|
||||
|
||||
// Animate slide in from the right
|
||||
final int containerWidth = mRootContainer.getWidth();
|
||||
mCurrentCertLayout.setTranslationX(containerWidth);
|
||||
mCurrentCertLayout.animate()
|
||||
.translationX(0)
|
||||
.setInterpolator(AnimationUtils.loadInterpolator(mActivity,
|
||||
android.R.interpolator.linear_out_slow_in))
|
||||
.setDuration(IN_DURATION_MS)
|
||||
.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
1024
Settings/src/com/android/settings/TrustedCredentialsFragment.java
Normal file
1024
Settings/src/com/android/settings/TrustedCredentialsFragment.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.security.IKeyChainService;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main fragment to display trusted credentials settings.
|
||||
*/
|
||||
public class TrustedCredentialsSettings extends DashboardFragment {
|
||||
|
||||
private static final String TAG = "TrustedCredentialsSettings";
|
||||
|
||||
public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER";
|
||||
|
||||
static final ImmutableList<Tab> TABS = ImmutableList.of(Tab.SYSTEM, Tab.USER);
|
||||
|
||||
private static final String USER_ACTION = "com.android.settings.TRUSTED_CREDENTIALS_USER";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.TRUSTED_CREDENTIALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getActivity().setTitle(R.string.trusted_credentials);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.placeholder_preference_screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
View tabContainer = view.findViewById(R.id.tab_container);
|
||||
tabContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
ViewPager2 viewPager = tabContainer.findViewById(R.id.view_pager);
|
||||
viewPager.setAdapter(new FragmentAdapter(this));
|
||||
viewPager.setUserInputEnabled(false);
|
||||
|
||||
Intent intent = getActivity().getIntent();
|
||||
if (intent != null && USER_ACTION.equals(intent.getAction())) {
|
||||
viewPager.setCurrentItem(TABS.indexOf(Tab.USER), false);
|
||||
}
|
||||
|
||||
TabLayout tabLayout = tabContainer.findViewById(R.id.tabs);
|
||||
new TabLayoutMediator(tabLayout, viewPager, false, false,
|
||||
(tab, position) -> tab.setText(TABS.get(position).mLabel)).attach();
|
||||
}
|
||||
|
||||
private static class FragmentAdapter extends FragmentStateAdapter {
|
||||
FragmentAdapter(Fragment fragment) {
|
||||
super(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
TrustedCredentialsFragment fragment = new TrustedCredentialsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TrustedCredentialsFragment.ARG_POSITION, position);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return TrustedCredentialsSettings.TABS.size();
|
||||
}
|
||||
}
|
||||
|
||||
enum Tab {
|
||||
SYSTEM(R.string.trusted_credentials_system_tab, true),
|
||||
USER(R.string.trusted_credentials_user_tab, false);
|
||||
|
||||
private final int mLabel;
|
||||
final boolean mSwitch;
|
||||
|
||||
Tab(int label, boolean withSwitch) {
|
||||
mLabel = label;
|
||||
mSwitch = withSwitch;
|
||||
}
|
||||
|
||||
List<String> getAliases(IKeyChainService service) throws RemoteException {
|
||||
switch (this) {
|
||||
case SYSTEM: {
|
||||
return service.getSystemCaAliases().getList();
|
||||
}
|
||||
case USER:
|
||||
return service.getUserCaAliases().getList();
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
boolean deleted(IKeyChainService service, String alias) throws RemoteException {
|
||||
switch (this) {
|
||||
case SYSTEM:
|
||||
return !service.containsCaAlias(alias);
|
||||
case USER:
|
||||
return false;
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
610
Settings/src/com/android/settings/UserCredentialsSettings.java
Normal file
610
Settings/src/com/android/settings/UserCredentialsSettings.java
Normal file
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.annotation.LayoutRes;
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.security.Credentials;
|
||||
import android.security.IKeyChainService;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChain.KeyChainConnection;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
import com.android.settings.wifi.helper.SavedWifiHelper;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class UserCredentialsSettings extends SettingsPreferenceFragment
|
||||
implements View.OnClickListener {
|
||||
private static final String TAG = "UserCredentialsSettings";
|
||||
|
||||
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
|
||||
|
||||
@VisibleForTesting
|
||||
protected SavedWifiHelper mSavedWifiHelper;
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.USER_CREDENTIALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
final Credential item = (Credential) view.getTag();
|
||||
if (item == null) return;
|
||||
if (item.isInUse()) {
|
||||
item.setUsedByNames(mSavedWifiHelper.getCertificateNetworkNames(item.alias));
|
||||
}
|
||||
showCredentialDialogFragment(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getActivity().setTitle(R.string.user_credentials);
|
||||
mSavedWifiHelper = SavedWifiHelper.getInstance(getContext(), getSettingsLifecycle());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void showCredentialDialogFragment(Credential item) {
|
||||
CredentialDialogFragment.show(this, item);
|
||||
}
|
||||
|
||||
protected void announceRemoval(String alias) {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
getListView().announceForAccessibility(getString(R.string.user_credential_removed, alias));
|
||||
}
|
||||
|
||||
protected void refreshItems() {
|
||||
if (isAdded()) {
|
||||
new AliasLoader().execute();
|
||||
}
|
||||
}
|
||||
|
||||
/** The fragment to show the credential information. */
|
||||
public static class CredentialDialogFragment extends InstrumentedDialogFragment
|
||||
implements DialogInterface.OnShowListener {
|
||||
private static final String TAG = "CredentialDialogFragment";
|
||||
private static final String ARG_CREDENTIAL = "credential";
|
||||
|
||||
public static void show(Fragment target, Credential item) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_CREDENTIAL, item);
|
||||
|
||||
if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
|
||||
final DialogFragment frag = new CredentialDialogFragment();
|
||||
frag.setTargetFragment(target, /* requestCode */ -1);
|
||||
frag.setArguments(args);
|
||||
frag.show(target.getFragmentManager(), TAG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
|
||||
|
||||
View root = getActivity().getLayoutInflater()
|
||||
.inflate(R.layout.user_credential_dialog, null);
|
||||
ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
|
||||
View contentView = getCredentialView(item, R.layout.user_credential, null,
|
||||
infoContainer, /* expanded */ true);
|
||||
infoContainer.addView(contentView);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
||||
.setView(root)
|
||||
.setTitle(R.string.user_credential_title)
|
||||
.setPositiveButton(R.string.done, null);
|
||||
|
||||
final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
|
||||
final int myUserId = UserHandle.myUserId();
|
||||
if (!RestrictedLockUtilsInternal.hasBaseUserRestriction(getContext(), restriction,
|
||||
myUserId)) {
|
||||
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
|
||||
@Override public void onClick(DialogInterface dialog, int id) {
|
||||
final EnforcedAdmin admin = RestrictedLockUtilsInternal
|
||||
.checkIfRestrictionEnforced(getContext(), restriction, myUserId);
|
||||
if (admin != null) {
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
|
||||
admin);
|
||||
} else {
|
||||
new RemoveCredentialsTask(getContext(), getTargetFragment())
|
||||
.execute(item);
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
};
|
||||
builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
|
||||
}
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(this);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override for the negative button enablement on demand.
|
||||
*/
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
|
||||
if (item.isInUse()) {
|
||||
((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_USER_CREDENTIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all certificates and keys under a given alias.
|
||||
*
|
||||
* If the {@link Credential} is for a system alias, all active grants to the alias will be
|
||||
* removed using {@link KeyChain}. If the {@link Credential} is for Wi-Fi alias, all
|
||||
* credentials and keys will be removed using {@link KeyStore}.
|
||||
*/
|
||||
private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
|
||||
private Context context;
|
||||
private Fragment targetFragment;
|
||||
|
||||
public RemoveCredentialsTask(Context context, Fragment targetFragment) {
|
||||
this.context = context;
|
||||
this.targetFragment = targetFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Credential[] doInBackground(Credential... credentials) {
|
||||
for (final Credential credential : credentials) {
|
||||
if (credential.isSystem()) {
|
||||
removeGrantsAndDelete(credential);
|
||||
} else {
|
||||
deleteWifiCredential(credential);
|
||||
}
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
private void deleteWifiCredential(final Credential credential) {
|
||||
try {
|
||||
final KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||
keyStore.load(
|
||||
new AndroidKeyStoreLoadStoreParameter(
|
||||
KeyProperties.NAMESPACE_WIFI));
|
||||
keyStore.deleteEntry(credential.getAlias());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to delete keys from keystore.");
|
||||
}
|
||||
}
|
||||
|
||||
private void removeGrantsAndDelete(final Credential credential) {
|
||||
final KeyChainConnection conn;
|
||||
try {
|
||||
conn = KeyChain.bind(getContext());
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Connecting to KeyChain", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
IKeyChainService keyChain = conn.getService();
|
||||
keyChain.removeKeyPair(credential.alias);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Removing credentials", e);
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Credential... credentials) {
|
||||
if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
|
||||
final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
|
||||
for (final Credential credential : credentials) {
|
||||
target.announceRemoval(credential.alias);
|
||||
}
|
||||
target.refreshItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a background connection to KeyStore to list user credentials.
|
||||
* The credentials are stored in a {@link CredentialAdapter} attached to the main
|
||||
* {@link ListView} in the fragment.
|
||||
*/
|
||||
private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
|
||||
/**
|
||||
* @return a list of credentials ordered:
|
||||
* <ol>
|
||||
* <li>first by purpose;</li>
|
||||
* <li>then by alias.</li>
|
||||
* </ol>
|
||||
*/
|
||||
@Override
|
||||
protected List<Credential> doInBackground(Void... params) {
|
||||
// Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
|
||||
final int myUserId = UserHandle.myUserId();
|
||||
final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
|
||||
final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
|
||||
|
||||
try {
|
||||
KeyStore processKeystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||
processKeystore.load(null);
|
||||
KeyStore wifiKeystore = null;
|
||||
if (myUserId == 0) {
|
||||
wifiKeystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||
wifiKeystore.load(new AndroidKeyStoreLoadStoreParameter(
|
||||
KeyProperties.NAMESPACE_WIFI));
|
||||
}
|
||||
|
||||
List<Credential> credentials = new ArrayList<>();
|
||||
credentials.addAll(getCredentialsForUid(processKeystore, systemUid).values());
|
||||
if (wifiKeystore != null) {
|
||||
credentials.addAll(getCredentialsForUid(wifiKeystore, wifiUid).values());
|
||||
}
|
||||
return credentials;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load credentials from Keystore.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
|
||||
try {
|
||||
final SortedMap<String, Credential> aliasMap = new TreeMap<>();
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
Credential c = new Credential(alias, uid);
|
||||
if (!c.isSystem()) {
|
||||
c.setInUse(mSavedWifiHelper.isCertificateInUse(alias));
|
||||
}
|
||||
Key key = null;
|
||||
try {
|
||||
key = keyStore.getKey(alias, null);
|
||||
} catch (NoSuchAlgorithmException | UnrecoverableKeyException e) {
|
||||
Log.e(TAG, "Error tying to retrieve key: " + alias, e);
|
||||
continue;
|
||||
}
|
||||
if (key != null) {
|
||||
// So we have a key
|
||||
if (key instanceof SecretKey) {
|
||||
// We don't display any symmetric key entries.
|
||||
continue;
|
||||
}
|
||||
// At this point we have determined that we have an asymmetric key.
|
||||
// so we have at least a USER_KEY and USER_CERTIFICATE.
|
||||
c.storedTypes.add(Credential.Type.USER_KEY);
|
||||
|
||||
Certificate[] certs = keyStore.getCertificateChain(alias);
|
||||
if (certs != null) {
|
||||
c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
|
||||
if (certs.length > 1) {
|
||||
c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// So there is no key but we have an alias. This must mean that we have
|
||||
// some certificate.
|
||||
if (keyStore.isCertificateEntry(alias)) {
|
||||
c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
|
||||
} else {
|
||||
// This is a weired inconsistent case that should not exist.
|
||||
// Pure trusted certificate entries should be stored in CA_CERTIFICATE,
|
||||
// but if isCErtificateEntry returns null this means that only the
|
||||
// USER_CERTIFICATE is populated which should never be the case without
|
||||
// a private key. It can still be retrieved with
|
||||
// keystore.getCertificate().
|
||||
c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
|
||||
}
|
||||
}
|
||||
aliasMap.put(alias, c);
|
||||
}
|
||||
return aliasMap;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new RuntimeException("Failed to load credential from Android Keystore.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<Credential> credentials) {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (credentials == null || credentials.size() == 0) {
|
||||
// Create a "no credentials installed" message for the empty case.
|
||||
TextView emptyTextView = (TextView) getActivity().findViewById(android.R.id.empty);
|
||||
emptyTextView.setText(R.string.user_credential_none_installed);
|
||||
setEmptyView(emptyTextView);
|
||||
} else {
|
||||
setEmptyView(null);
|
||||
}
|
||||
|
||||
getListView().setAdapter(
|
||||
new CredentialAdapter(credentials, UserCredentialsSettings.this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to display {@link Credential}s in a list.
|
||||
*/
|
||||
private static class CredentialAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
|
||||
|
||||
private final List<Credential> mItems;
|
||||
private final View.OnClickListener mListener;
|
||||
|
||||
public CredentialAdapter(List<Credential> items, @Nullable View.OnClickListener listener) {
|
||||
mItems = items;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
return new ViewHolder(inflater.inflate(LAYOUT_RESOURCE, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder h, int position) {
|
||||
getCredentialView(mItems.get(position), LAYOUT_RESOURCE, h.itemView, null, false);
|
||||
h.itemView.setTag(mItems.get(position));
|
||||
h.itemView.setOnClickListener(mListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ViewHolder(View item) {
|
||||
super(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping from View IDs in {@link R} to the types of credentials they describe.
|
||||
*/
|
||||
private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
|
||||
static {
|
||||
credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_KEY);
|
||||
credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
|
||||
credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
|
||||
}
|
||||
|
||||
protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
|
||||
@Nullable View view, ViewGroup parent, boolean expanded) {
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
|
||||
}
|
||||
|
||||
((TextView) view.findViewById(R.id.alias)).setText(item.alias);
|
||||
updatePurposeView(view.findViewById(R.id.purpose), item);
|
||||
|
||||
view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
|
||||
if (expanded) {
|
||||
updateUsedByViews(view.findViewById(R.id.credential_being_used_by_title),
|
||||
view.findViewById(R.id.credential_being_used_by_content), item);
|
||||
|
||||
for (int i = 0; i < credentialViewTypes.size(); i++) {
|
||||
final View detail = view.findViewById(credentialViewTypes.keyAt(i));
|
||||
detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
|
||||
? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected static void updatePurposeView(TextView purpose, Credential item) {
|
||||
int subTextResId = R.string.credential_for_vpn_and_apps;
|
||||
if (!item.isSystem()) {
|
||||
subTextResId = (item.isInUse())
|
||||
? R.string.credential_for_wifi_in_use
|
||||
: R.string.credential_for_wifi;
|
||||
}
|
||||
purpose.setText(subTextResId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected static void updateUsedByViews(TextView title, TextView content, Credential item) {
|
||||
List<String> usedByNames = item.getUsedByNames();
|
||||
if (usedByNames.size() > 0) {
|
||||
title.setVisibility(View.VISIBLE);
|
||||
content.setText(String.join("\n", usedByNames));
|
||||
content.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
title.setVisibility(View.GONE);
|
||||
content.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
static class AliasEntry {
|
||||
public String alias;
|
||||
public int uid;
|
||||
}
|
||||
|
||||
static class Credential implements Parcelable {
|
||||
static enum Type {
|
||||
CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
|
||||
USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
|
||||
USER_KEY(Credentials.USER_PRIVATE_KEY, Credentials.USER_SECRET_KEY);
|
||||
|
||||
final String[] prefix;
|
||||
|
||||
Type(String... prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
|
||||
* prefixes from {@link CredentialItem.storedTypes}.
|
||||
*/
|
||||
final String alias;
|
||||
|
||||
/**
|
||||
* UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
|
||||
* also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
|
||||
*/
|
||||
final int uid;
|
||||
|
||||
/**
|
||||
* Indicate whether or not this credential is in use.
|
||||
*/
|
||||
boolean mIsInUse;
|
||||
|
||||
/**
|
||||
* The list of networks which use this credential.
|
||||
*/
|
||||
List<String> mUsedByNames = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Should contain some non-empty subset of:
|
||||
* <ul>
|
||||
* <li>{@link Credentials.CA_CERTIFICATE}</li>
|
||||
* <li>{@link Credentials.USER_CERTIFICATE}</li>
|
||||
* <li>{@link Credentials.USER_KEY}</li>
|
||||
* </ul>
|
||||
*/
|
||||
final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
|
||||
|
||||
Credential(final String alias, final int uid) {
|
||||
this.alias = alias;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
Credential(Parcel in) {
|
||||
this(in.readString(), in.readInt());
|
||||
|
||||
long typeBits = in.readLong();
|
||||
for (Type i : Type.values()) {
|
||||
if ((typeBits & (1L << i.ordinal())) != 0L) {
|
||||
storedTypes.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(alias);
|
||||
out.writeInt(uid);
|
||||
|
||||
long typeBits = 0;
|
||||
for (Type i : storedTypes) {
|
||||
typeBits |= 1L << i.ordinal();
|
||||
}
|
||||
out.writeLong(typeBits);
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Credential> CREATOR
|
||||
= new Parcelable.Creator<Credential>() {
|
||||
public Credential createFromParcel(Parcel in) {
|
||||
return new Credential(in);
|
||||
}
|
||||
|
||||
public Credential[] newArray(int size) {
|
||||
return new Credential[size];
|
||||
}
|
||||
};
|
||||
|
||||
public boolean isSystem() {
|
||||
return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public EnumSet<Type> getStoredTypes() {
|
||||
return storedTypes;
|
||||
}
|
||||
|
||||
public void setInUse(boolean inUse) {
|
||||
mIsInUse = inUse;
|
||||
}
|
||||
|
||||
public boolean isInUse() {
|
||||
return mIsInUse;
|
||||
}
|
||||
|
||||
public void setUsedByNames(List<String> names) {
|
||||
mUsedByNames = new ArrayList<>(names);
|
||||
}
|
||||
|
||||
public List<String> getUsedByNames() {
|
||||
return new ArrayList<String>(mUsedByNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
1398
Settings/src/com/android/settings/Utils.java
Normal file
1398
Settings/src/com/android/settings/Utils.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.accessibility;
|
||||
|
||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
|
||||
/** Settings fragment containing bluetooth audio routing. */
|
||||
public class AccessibilityAudioRoutingFragment extends RestrictedDashboardFragment {
|
||||
private static final String TAG = "AccessibilityAudioRoutingFragment";
|
||||
|
||||
public AccessibilityAudioRoutingFragment() {
|
||||
super(DISALLOW_CONFIG_BLUETOOTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.HEARING_AID_AUDIO_ROUTING;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_audio_routing_fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.accessibility_audio_routing_fragment);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.text.Html;
|
||||
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Preference controller for accessibility button footer.
|
||||
*/
|
||||
public class AccessibilityButtonFooterPreferenceController extends
|
||||
AccessibilityFooterPreferenceController {
|
||||
|
||||
public AccessibilityButtonFooterPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLearnMoreText() {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_button_gesture_footer_learn_more_content_description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIntroductionTitle() {
|
||||
return mContext.getString(R.string.accessibility_button_about_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
// Need to update footerPreference's data before super.displayPreference(), then it will use
|
||||
// data to update related property of footerPreference.
|
||||
final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(mContext)
|
||||
? R.string.accessibility_button_gesture_description
|
||||
: R.string.accessibility_button_description;
|
||||
final CharSequence footerText = Html.fromHtml(
|
||||
MessageFormat.format(mContext.getString(titleResource), 1, 2, 3),
|
||||
Html.FROM_HTML_MODE_COMPACT);
|
||||
final AccessibilityFooterPreference footerPreference =
|
||||
screen.findPreference(getPreferenceKey());
|
||||
footerPreference.setTitle(footerText);
|
||||
super.displayPreference(screen);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
|
||||
/** Settings fragment containing accessibility button properties. */
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public class AccessibilityButtonFragment extends DashboardFragment {
|
||||
|
||||
private static final String TAG = "AccessibilityButtonFragment";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
|
||||
? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title;
|
||||
getActivity().setTitle(titleResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_button_settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCESSIBILITY_BUTTON_SETTINGS;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.accessibility_button_settings);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Preference controller that controls the button or gesture in accessibility button page. */
|
||||
public class AccessibilityButtonGesturePreferenceController extends BasePreferenceController
|
||||
implements Preference.OnPreferenceChangeListener {
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
Mode.BUTTON,
|
||||
Mode.GESTURE,
|
||||
})
|
||||
private @interface Mode {
|
||||
int BUTTON = 1;
|
||||
int GESTURE = 2;
|
||||
}
|
||||
|
||||
public AccessibilityButtonGesturePreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
|
||||
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Integer value = Ints.tryParse((String) newValue);
|
||||
if (value != null) {
|
||||
putCurrentAccessibilityButtonMode(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
super.updateState(preference);
|
||||
final ListPreference listPreference = (ListPreference) preference;
|
||||
|
||||
listPreference.setValue(String.valueOf(getCurrentAccessibilityButtonMode()));
|
||||
}
|
||||
|
||||
@Mode
|
||||
private int getCurrentAccessibilityButtonMode() {
|
||||
return Settings.Secure.getInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, Mode.BUTTON);
|
||||
}
|
||||
|
||||
private void putCurrentAccessibilityButtonMode(@Mode int mode) {
|
||||
Settings.Secure.putInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Preference controller that controls the preferred location in accessibility button page. */
|
||||
public class AccessibilityButtonLocationPreferenceController extends BasePreferenceController
|
||||
implements Preference.OnPreferenceChangeListener {
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
Location.FLOATING_MENU,
|
||||
Location.NAVIGATION_BAR,
|
||||
})
|
||||
private @interface Location {
|
||||
int FLOATING_MENU = 1;
|
||||
int NAVIGATION_BAR = 0;
|
||||
}
|
||||
|
||||
public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
|
||||
? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Integer value = Ints.tryParse((String) newValue);
|
||||
if (value != null) {
|
||||
putCurrentAccessibilityButtonMode(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
super.updateState(preference);
|
||||
final ListPreference listPreference = (ListPreference) preference;
|
||||
|
||||
listPreference.setValue(String.valueOf(getCurrentAccessibilityButtonMode()));
|
||||
}
|
||||
|
||||
@Location
|
||||
private int getCurrentAccessibilityButtonMode() {
|
||||
return Settings.Secure.getInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, Location.FLOATING_MENU);
|
||||
}
|
||||
|
||||
private void putCurrentAccessibilityButtonMode(@Location int location) {
|
||||
Settings.Secure.putInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Preference controller for accessibility button preference.
|
||||
*/
|
||||
public class AccessibilityButtonPreferenceController extends BasePreferenceController {
|
||||
|
||||
public AccessibilityButtonPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
final Preference preference = screen.findPreference(getPreferenceKey());
|
||||
preference.setTitle(getPreferenceTitleResource());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
|
||||
data.key = getPreferenceKey();
|
||||
final Resources res = mContext.getResources();
|
||||
data.title = res.getString(getPreferenceTitleResource());
|
||||
data.screenTitle = res.getString(R.string.accessibility_shortcuts_settings_title);
|
||||
rawData.add(data);
|
||||
}
|
||||
|
||||
private int getPreferenceTitleResource() {
|
||||
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
|
||||
? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnPause;
|
||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
||||
import com.android.settingslib.widget.IllustrationPreference;
|
||||
|
||||
/** Preference controller that controls the preview effect in accessibility button page. */
|
||||
public class AccessibilityButtonPreviewPreferenceController extends BasePreferenceController
|
||||
implements LifecycleObserver, OnResume, OnPause {
|
||||
|
||||
private static final int SMALL_SIZE = 0;
|
||||
private static final float DEFAULT_OPACITY = 0.55f;
|
||||
private static final int DEFAULT_SIZE = 0;
|
||||
|
||||
private final ContentResolver mContentResolver;
|
||||
@VisibleForTesting
|
||||
final ContentObserver mContentObserver;
|
||||
private AccessibilityLayerDrawable mAccessibilityPreviewDrawable;
|
||||
@VisibleForTesting
|
||||
IllustrationPreference mIllustrationPreference;
|
||||
|
||||
private AccessibilityManager.TouchExplorationStateChangeListener
|
||||
mTouchExplorationStateChangeListener;
|
||||
|
||||
public AccessibilityButtonPreviewPreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mContentResolver = context.getContentResolver();
|
||||
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
updatePreviewPreference();
|
||||
}
|
||||
};
|
||||
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
|
||||
updatePreviewPreference();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mIllustrationPreference = screen.findPreference(getPreferenceKey());
|
||||
|
||||
updatePreviewPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
final AccessibilityManager am = mContext.getSystemService(AccessibilityManager.class);
|
||||
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
|
||||
|
||||
mContentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE),
|
||||
/* notifyForDescendants= */ false, mContentObserver);
|
||||
mContentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
|
||||
/* notifyForDescendants= */ false, mContentObserver);
|
||||
mContentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
|
||||
/* notifyForDescendants= */ false, mContentObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
final AccessibilityManager am = mContext.getSystemService(AccessibilityManager.class);
|
||||
am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
|
||||
|
||||
mContentResolver.unregisterContentObserver(mContentObserver);
|
||||
}
|
||||
|
||||
private void updatePreviewPreference() {
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(mContext)) {
|
||||
final int size = Settings.Secure.getInt(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, DEFAULT_SIZE);
|
||||
final int opacity = (int) (Settings.Secure.getFloat(mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY) * 100);
|
||||
final int floatingMenuIconId = (size == SMALL_SIZE)
|
||||
? R.drawable.a11y_button_preview_small_floating_menu
|
||||
: R.drawable.a11y_button_preview_large_floating_menu;
|
||||
mIllustrationPreference.setImageDrawable(
|
||||
getAccessibilityPreviewDrawable(floatingMenuIconId, opacity));
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(mContext)) {
|
||||
mIllustrationPreference.setImageDrawable(mContext.getDrawable(
|
||||
AccessibilityUtil.isTouchExploreEnabled(mContext)
|
||||
? R.drawable.a11y_button_preview_three_finger
|
||||
: R.drawable.a11y_button_preview_two_finger));
|
||||
} else {
|
||||
mIllustrationPreference.setImageDrawable(
|
||||
mContext.getDrawable(R.drawable.a11y_button_navigation));
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable getAccessibilityPreviewDrawable(int resId, int opacity) {
|
||||
if (mAccessibilityPreviewDrawable == null) {
|
||||
mAccessibilityPreviewDrawable = AccessibilityLayerDrawable.createLayerDrawable(
|
||||
mContext, resId, opacity);
|
||||
} else {
|
||||
mAccessibilityPreviewDrawable.updateLayerDrawable(mContext, resId, opacity);
|
||||
// Only change alpha (opacity) value did not change drawable id. It needs to force to
|
||||
// redraw.
|
||||
mAccessibilityPreviewDrawable.invalidateSelf();
|
||||
}
|
||||
return mAccessibilityPreviewDrawable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Preference controller for accessibility control timeout footer.
|
||||
*/
|
||||
public class AccessibilityControlTimeoutFooterPreferenceController extends
|
||||
AccessibilityFooterPreferenceController {
|
||||
|
||||
public AccessibilityControlTimeoutFooterPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLearnMoreText() {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_control_timeout_footer_learn_more_content_description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIntroductionTitle() {
|
||||
return mContext.getString(R.string.accessibility_control_timeout_about_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHelpResource() {
|
||||
return R.string.help_url_timeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.accessibility;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
|
||||
/** Settings fragment containing accessibility control timeout preference. */
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public final class AccessibilityControlTimeoutPreferenceFragment extends DashboardFragment {
|
||||
|
||||
static final String TAG = "AccessibilityControlTimeoutPreferenceFragment";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCESSIBILITY_TIMEOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_control_timeout_settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return R.string.help_url_timeout;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.accessibility_control_timeout_settings);
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.accessibility;
|
||||
|
||||
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
|
||||
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
|
||||
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.Activity;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
import com.android.settingslib.accessibility.AccessibilityUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
|
||||
|
||||
private final static String TAG = "A11yDetailsSettings";
|
||||
private AppOpsManager mAppOps;
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCESSIBILITY_DETAILS_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mAppOps = getActivity().getSystemService(AppOpsManager.class);
|
||||
|
||||
// In case the Intent doesn't have component name, go to a11y services list.
|
||||
final String extraComponentName = getActivity().getIntent().getStringExtra(
|
||||
Intent.EXTRA_COMPONENT_NAME);
|
||||
if (extraComponentName == null) {
|
||||
Log.w(TAG, "Open accessibility services list due to no component name.");
|
||||
openAccessibilitySettingsAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
final ComponentName componentName = ComponentName.unflattenFromString(extraComponentName);
|
||||
if (openSystemAccessibilitySettingsAndFinish(componentName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (openAccessibilityDetailsSettingsAndFinish(componentName)) {
|
||||
return;
|
||||
}
|
||||
// Fall back to open accessibility services list.
|
||||
openAccessibilitySettingsAndFinish();
|
||||
}
|
||||
|
||||
private boolean openSystemAccessibilitySettingsAndFinish(
|
||||
@Nullable ComponentName componentName) {
|
||||
final LaunchFragmentArguments launchArguments =
|
||||
getSystemAccessibilitySettingsLaunchArguments(componentName);
|
||||
if (launchArguments == null) {
|
||||
return false;
|
||||
}
|
||||
openSubSettings(launchArguments.mDestination, launchArguments.mArguments);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LaunchFragmentArguments getSystemAccessibilitySettingsLaunchArguments(
|
||||
@Nullable ComponentName componentName) {
|
||||
if (MAGNIFICATION_COMPONENT_NAME.equals(componentName)) {
|
||||
final String destination = ToggleScreenMagnificationPreferenceFragment.class.getName();
|
||||
return new LaunchFragmentArguments(destination, /* arguments= */ null);
|
||||
}
|
||||
|
||||
if (ACCESSIBILITY_BUTTON_COMPONENT_NAME.equals(componentName)) {
|
||||
final String destination = AccessibilityButtonFragment.class.getName();
|
||||
return new LaunchFragmentArguments(destination, /* arguments= */ null);
|
||||
}
|
||||
|
||||
if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(componentName)) {
|
||||
final String destination = AccessibilityHearingAidsFragment.class.getName();
|
||||
return new LaunchFragmentArguments(destination, /* arguments= */ null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void openAccessibilitySettingsAndFinish() {
|
||||
openSubSettings(AccessibilitySettings.class.getName(), /* arguments= */ null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean openAccessibilityDetailsSettingsAndFinish(
|
||||
@Nullable ComponentName componentName) {
|
||||
// In case the A11yServiceInfo doesn't exist, go to ally services list.
|
||||
final AccessibilityServiceInfo info = getAccessibilityServiceInfo(componentName);
|
||||
if (info == null) {
|
||||
Log.w(TAG, "openAccessibilityDetailsSettingsAndFinish : invalid component name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// In case this accessibility service isn't permitted, go to a11y services list.
|
||||
if (!isServiceAllowed(info.getResolveInfo().serviceInfo.applicationInfo.uid,
|
||||
componentName.getPackageName())) {
|
||||
Log.w(TAG,
|
||||
"openAccessibilityDetailsSettingsAndFinish: target accessibility service is"
|
||||
+ "prohibited by Device Admin or App Op.");
|
||||
return false;
|
||||
}
|
||||
openSubSettings(ToggleAccessibilityServicePreferenceFragment.class.getName(),
|
||||
buildArguments(info));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openSubSettings(@NonNull String destination, @Nullable Bundle arguments) {
|
||||
new SubSettingLauncher(getActivity())
|
||||
.setDestination(destination)
|
||||
.setSourceMetricsCategory(getMetricsCategory())
|
||||
.setArguments(arguments)
|
||||
.launch();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isServiceAllowed(int uid, String packageName) {
|
||||
final DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
|
||||
final List<String> permittedServices = dpm.getPermittedAccessibilityServices(
|
||||
UserHandle.myUserId());
|
||||
if (permittedServices != null && !permittedServices.contains(packageName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !RestrictedLockUtilsInternal.isEnhancedConfirmationRestricted(getContext(),
|
||||
packageName, AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
|
||||
}
|
||||
|
||||
private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) {
|
||||
if (componentName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
|
||||
getActivity()).getInstalledAccessibilityServiceList();
|
||||
final int serviceInfoCount = serviceInfos.size();
|
||||
for (int i = 0; i < serviceInfoCount; i++) {
|
||||
AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
|
||||
ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
|
||||
if (componentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
|
||||
&& componentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
|
||||
return serviceInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bundle buildArguments(AccessibilityServiceInfo info) {
|
||||
final ResolveInfo resolveInfo = info.getResolveInfo();
|
||||
final String title = resolveInfo.loadLabel(getActivity().getPackageManager()).toString();
|
||||
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
||||
final String packageName = serviceInfo.packageName;
|
||||
final ComponentName componentName = new ComponentName(packageName, serviceInfo.name);
|
||||
|
||||
final Set<ComponentName> enabledServices =
|
||||
AccessibilityUtils.getEnabledServicesFromSettings(getActivity());
|
||||
final boolean serviceEnabled = enabledServices.contains(componentName);
|
||||
String description = info.loadDescription(getActivity().getPackageManager());
|
||||
|
||||
if (serviceEnabled && info.crashed) {
|
||||
// Update the summaries for services that have crashed.
|
||||
description = getString(R.string.accessibility_description_state_stopped);
|
||||
}
|
||||
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
|
||||
componentName.flattenToString());
|
||||
extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled);
|
||||
extras.putString(AccessibilitySettings.EXTRA_TITLE, title);
|
||||
extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo);
|
||||
extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description);
|
||||
|
||||
final String settingsClassName = info.getSettingsActivityName();
|
||||
if (!TextUtils.isEmpty(settingsClassName)) {
|
||||
extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE,
|
||||
getString(R.string.accessibility_menu_item_settings));
|
||||
extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME,
|
||||
new ComponentName(packageName, settingsClassName).flattenToString());
|
||||
}
|
||||
|
||||
final String tileServiceClassName = info.getTileServiceName();
|
||||
if (!TextUtils.isEmpty(tileServiceClassName)) {
|
||||
extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME,
|
||||
new ComponentName(packageName, tileServiceClassName).flattenToString());
|
||||
}
|
||||
|
||||
final int metricsCategory = FeatureFactory.getFeatureFactory()
|
||||
.getAccessibilityMetricsFeatureProvider()
|
||||
.getDownloadedFeatureMetricsCategory(componentName);
|
||||
extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
|
||||
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
|
||||
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
|
||||
|
||||
final String htmlDescription = info.loadHtmlDescription(getActivity().getPackageManager());
|
||||
extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
|
||||
|
||||
final CharSequence intro = info.loadIntro(getActivity().getPackageManager());
|
||||
extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro);
|
||||
|
||||
// We will log nonA11yTool status from PolicyWarningUIController; others none.
|
||||
extras.putLong(AccessibilitySettings.EXTRA_TIME_FOR_LOGGING,
|
||||
getActivity().getIntent().getLongExtra(
|
||||
AccessibilitySettings.EXTRA_TIME_FOR_LOGGING, 0));
|
||||
return extras;
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
private static class LaunchFragmentArguments {
|
||||
final String mDestination;
|
||||
final Bundle mArguments;
|
||||
LaunchFragmentArguments(@NonNull String destination, @Nullable Bundle arguments) {
|
||||
mDestination = Objects.requireNonNull(destination);
|
||||
mArguments = arguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.accessibility;
|
||||
|
||||
import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.server.accessibility.Flags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.utils.AnnotationSpan;
|
||||
import com.android.settingslib.widget.LottieColorUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieDrawable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class for creating the edit dialog.
|
||||
*/
|
||||
public class AccessibilityDialogUtils {
|
||||
private static final String TAG = "AccessibilityDialogUtils";
|
||||
|
||||
/** Denotes the dialog emuns for show dialog. */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface DialogEnums {
|
||||
|
||||
/** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
|
||||
int EDIT_SHORTCUT = 1;
|
||||
|
||||
/** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
|
||||
int MAGNIFICATION_EDIT_SHORTCUT = 1001;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
|
||||
* enable service.
|
||||
*/
|
||||
int ENABLE_WARNING_FROM_TOGGLE = 1002;
|
||||
|
||||
/** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
|
||||
int ENABLE_WARNING_FROM_SHORTCUT = 1003;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
|
||||
* toggle.
|
||||
*/
|
||||
int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
|
||||
* disable service.
|
||||
*/
|
||||
int DISABLE_WARNING_FROM_TOGGLE = 1005;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Magnification > Toggle user service in button
|
||||
* navigation.
|
||||
*/
|
||||
int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
|
||||
* navigation.
|
||||
*/
|
||||
int GESTURE_NAVIGATION_TUTORIAL = 1007;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
|
||||
* launch tutorial.
|
||||
*/
|
||||
int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
|
||||
|
||||
/**
|
||||
* OPEN: Settings > Accessibility > Display size and text > Click 'Reset settings' button.
|
||||
*/
|
||||
int DIALOG_RESET_SETTINGS = 1009;
|
||||
}
|
||||
|
||||
/**
|
||||
* IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
|
||||
* type.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
DialogType.EDIT_SHORTCUT_GENERIC,
|
||||
DialogType.EDIT_SHORTCUT_GENERIC_SUW,
|
||||
DialogType.EDIT_SHORTCUT_MAGNIFICATION,
|
||||
DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW,
|
||||
})
|
||||
|
||||
public @interface DialogType {
|
||||
int EDIT_SHORTCUT_GENERIC = 0;
|
||||
int EDIT_SHORTCUT_GENERIC_SUW = 1;
|
||||
int EDIT_SHORTCUT_MAGNIFICATION = 2;
|
||||
int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to show the edit shortcut dialog.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param dialogType The type of edit shortcut dialog
|
||||
* @param dialogTitle The title of edit shortcut dialog
|
||||
* @param listener The listener to determine the action of edit shortcut dialog
|
||||
* @return A edit shortcut dialog for showing
|
||||
*/
|
||||
public static AlertDialog showEditShortcutDialog(Context context, int dialogType,
|
||||
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
|
||||
final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener);
|
||||
alertDialog.show();
|
||||
setScrollIndicators(alertDialog);
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the shortcut content in edit shortcut dialog.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param editShortcutDialog Need to be a type of edit shortcut dialog
|
||||
* @return True if the update is successful
|
||||
*/
|
||||
public static boolean updateShortcutInDialog(Context context,
|
||||
Dialog editShortcutDialog) {
|
||||
final View container = editShortcutDialog.findViewById(R.id.container_layout);
|
||||
if (container != null) {
|
||||
initSoftwareShortcut(context, container);
|
||||
initHardwareShortcut(context, container);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static AlertDialog createDialog(Context context, int dialogType,
|
||||
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
|
||||
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setView(createEditDialogContentView(context, dialogType))
|
||||
.setTitle(dialogTitle)
|
||||
.setPositiveButton(R.string.save, listener)
|
||||
.setNegativeButton(R.string.cancel,
|
||||
(DialogInterface dialog, int which) -> dialog.dismiss())
|
||||
.create();
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scroll indicators for dialog view. The indicators appears while content view is
|
||||
* out of vision for vertical scrolling.
|
||||
*/
|
||||
private static void setScrollIndicators(AlertDialog dialog) {
|
||||
final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
|
||||
setScrollIndicators(scrollView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scroll indicators for dialog view. The indicators appear while content view is
|
||||
* out of vision for vertical scrolling.
|
||||
*
|
||||
* @param view The view contains customized dialog content. Usually it is {@link ScrollView} or
|
||||
* {@link AbsListView}
|
||||
*/
|
||||
private static void setScrollIndicators(@NonNull View view) {
|
||||
view.setScrollIndicators(
|
||||
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
|
||||
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a content View for the edit shortcut dialog.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param dialogType The type of edit shortcut dialog
|
||||
* @return A content view suitable for viewing
|
||||
*/
|
||||
private static View createEditDialogContentView(Context context, int dialogType) {
|
||||
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
View contentView = null;
|
||||
|
||||
switch (dialogType) {
|
||||
case DialogType.EDIT_SHORTCUT_GENERIC:
|
||||
contentView = inflater.inflate(
|
||||
R.layout.accessibility_edit_shortcut, null);
|
||||
initSoftwareShortcut(context, contentView);
|
||||
initHardwareShortcut(context, contentView);
|
||||
break;
|
||||
case DialogType.EDIT_SHORTCUT_GENERIC_SUW:
|
||||
contentView = inflater.inflate(
|
||||
R.layout.accessibility_edit_shortcut, null);
|
||||
initSoftwareShortcutForSUW(context, contentView);
|
||||
initHardwareShortcut(context, contentView);
|
||||
break;
|
||||
case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
|
||||
contentView = inflater.inflate(
|
||||
R.layout.accessibility_edit_shortcut_magnification, null);
|
||||
initSoftwareShortcut(context, contentView);
|
||||
initHardwareShortcut(context, contentView);
|
||||
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
|
||||
initTwoFingerDoubleTapMagnificationShortcut(context, contentView);
|
||||
}
|
||||
initMagnifyShortcut(context, contentView);
|
||||
initAdvancedWidget(contentView);
|
||||
break;
|
||||
case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW:
|
||||
contentView = inflater.inflate(
|
||||
R.layout.accessibility_edit_shortcut_magnification, null);
|
||||
initSoftwareShortcutForSUW(context, contentView);
|
||||
initHardwareShortcut(context, contentView);
|
||||
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
|
||||
initTwoFingerDoubleTapMagnificationShortcut(context, contentView);
|
||||
}
|
||||
initMagnifyShortcut(context, contentView);
|
||||
initAdvancedWidget(contentView);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
private static void setupShortcutWidget(View view, CharSequence titleText,
|
||||
CharSequence summaryText, @DrawableRes int imageResId) {
|
||||
setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText);
|
||||
setupShortcutWidgetWithImageResource(view, imageResId);
|
||||
}
|
||||
|
||||
private static void setupShortcutWidgetWithImageRawResource(Context context,
|
||||
View view, CharSequence titleText,
|
||||
CharSequence summaryText, @RawRes int imageRawResId) {
|
||||
setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText);
|
||||
setupShortcutWidgetWithImageRawResource(context, view, imageRawResId);
|
||||
}
|
||||
|
||||
private static void setupShortcutWidgetWithTitleAndSummary(View view, CharSequence titleText,
|
||||
CharSequence summaryText) {
|
||||
final CheckBox checkBox = view.findViewById(R.id.checkbox);
|
||||
checkBox.setText(titleText);
|
||||
|
||||
final TextView summary = view.findViewById(R.id.summary);
|
||||
if (TextUtils.isEmpty(summaryText)) {
|
||||
summary.setVisibility(View.GONE);
|
||||
} else {
|
||||
summary.setText(summaryText);
|
||||
summary.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
summary.setFocusable(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupShortcutWidgetWithImageResource(View view,
|
||||
@DrawableRes int imageResId) {
|
||||
final ImageView imageView = view.findViewById(R.id.image);
|
||||
imageView.setImageResource(imageResId);
|
||||
}
|
||||
|
||||
private static void setupShortcutWidgetWithImageRawResource(Context context, View view,
|
||||
@RawRes int imageRawResId) {
|
||||
final LottieAnimationView lottieView = view.findViewById(R.id.image);
|
||||
lottieView.setFailureListener(
|
||||
result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawResId,
|
||||
result));
|
||||
lottieView.setAnimation(imageRawResId);
|
||||
lottieView.setRepeatCount(LottieDrawable.INFINITE);
|
||||
LottieColorUtils.applyDynamicColors(context, lottieView);
|
||||
lottieView.playAnimation();
|
||||
}
|
||||
|
||||
private static void initSoftwareShortcutForSUW(Context context, View view) {
|
||||
final View dialogView = view.findViewById(R.id.software_shortcut);
|
||||
final CharSequence title = context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_title_software);
|
||||
final TextView summary = dialogView.findViewById(R.id.summary);
|
||||
final int lineHeight = summary.getLineHeight();
|
||||
|
||||
setupShortcutWidget(dialogView, title,
|
||||
retrieveSoftwareShortcutSummaryForSUW(context, lineHeight),
|
||||
retrieveSoftwareShortcutImageResId(context));
|
||||
}
|
||||
|
||||
private static void initSoftwareShortcut(Context context, View view) {
|
||||
final View dialogView = view.findViewById(R.id.software_shortcut);
|
||||
final TextView summary = dialogView.findViewById(R.id.summary);
|
||||
final int lineHeight = summary.getLineHeight();
|
||||
|
||||
setupShortcutWidget(dialogView,
|
||||
retrieveTitle(context),
|
||||
retrieveSoftwareShortcutSummary(context, lineHeight),
|
||||
retrieveSoftwareShortcutImageResId(context));
|
||||
}
|
||||
|
||||
private static void initHardwareShortcut(Context context, View view) {
|
||||
final View dialogView = view.findViewById(R.id.hardware_shortcut);
|
||||
final CharSequence title = context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_title_hardware);
|
||||
final CharSequence summary = context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_summary_hardware);
|
||||
setupShortcutWidget(dialogView, title, summary,
|
||||
R.drawable.a11y_shortcut_type_hardware);
|
||||
}
|
||||
|
||||
private static void initMagnifyShortcut(Context context, View view) {
|
||||
final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
|
||||
final CharSequence title = context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
|
||||
String summary = context.getString(
|
||||
R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
|
||||
// Format the number '3' in the summary.
|
||||
final Object[] arguments = {3};
|
||||
summary = MessageFormat.format(summary, arguments);
|
||||
|
||||
setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary,
|
||||
R.raw.a11y_shortcut_type_triple_tap);
|
||||
}
|
||||
|
||||
private static void initTwoFingerDoubleTapMagnificationShortcut(Context context, View view) {
|
||||
// TODO(b/306153204): Update shortcut string and image when UX provides them
|
||||
final View dialogView = view.findViewById(R.id.two_finger_triple_tap_shortcut);
|
||||
final CharSequence title = context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap);
|
||||
String summary = context.getString(
|
||||
R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap);
|
||||
// Format the number '2' in the summary.
|
||||
final Object[] arguments = {2};
|
||||
summary = MessageFormat.format(summary, arguments);
|
||||
|
||||
setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary,
|
||||
R.raw.a11y_shortcut_type_triple_tap);
|
||||
|
||||
dialogView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private static void initAdvancedWidget(View view) {
|
||||
final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
|
||||
final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
|
||||
advanced.setOnClickListener((View v) -> {
|
||||
advanced.setVisibility(View.GONE);
|
||||
tripleTap.setVisibility(View.VISIBLE);
|
||||
});
|
||||
}
|
||||
|
||||
private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context,
|
||||
int lineHeight) {
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
sb.append(getSummaryStringWithIcon(context, lineHeight));
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static CharSequence retrieveTitle(Context context) {
|
||||
int resId;
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
resId = R.string.accessibility_shortcut_edit_dialog_title_software_by_gesture;
|
||||
} else {
|
||||
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
|
||||
}
|
||||
return context.getText(resId);
|
||||
}
|
||||
|
||||
private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) {
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
sb.append(getCustomizeAccessibilityButtonLink(context));
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
|
||||
? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
|
||||
: R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
|
||||
sb.append(context.getText(resId));
|
||||
sb.append("\n\n");
|
||||
sb.append(getCustomizeAccessibilityButtonLink(context));
|
||||
} else {
|
||||
sb.append(getSummaryStringWithIcon(context, lineHeight));
|
||||
sb.append("\n\n");
|
||||
sb.append(getCustomizeAccessibilityButtonLink(context));
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static int retrieveSoftwareShortcutImageResId(Context context) {
|
||||
int resId;
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
resId = R.drawable.a11y_shortcut_type_software_floating;
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
resId = AccessibilityUtil.isTouchExploreEnabled(context)
|
||||
? R.drawable.a11y_shortcut_type_software_gesture_talkback
|
||||
: R.drawable.a11y_shortcut_type_software_gesture;
|
||||
} else {
|
||||
resId = R.drawable.a11y_shortcut_type_software;
|
||||
}
|
||||
return resId;
|
||||
}
|
||||
|
||||
private static CharSequence getCustomizeAccessibilityButtonLink(Context context) {
|
||||
final View.OnClickListener linkListener = v -> new SubSettingLauncher(context)
|
||||
.setDestination(AccessibilityButtonFragment.class.getName())
|
||||
.setSourceMetricsCategory(
|
||||
SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS)
|
||||
.launch();
|
||||
final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
|
||||
AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
|
||||
return AnnotationSpan.linkify(context.getText(
|
||||
R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo);
|
||||
}
|
||||
|
||||
private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
|
||||
final String summary = context
|
||||
.getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
|
||||
final SpannableString spannableMessage = SpannableString.valueOf(summary);
|
||||
|
||||
// Icon
|
||||
final int indexIconStart = summary.indexOf("%s");
|
||||
final int indexIconEnd = indexIconStart + 2;
|
||||
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
|
||||
final ImageSpan imageSpan = new ImageSpan(icon);
|
||||
imageSpan.setContentDescription("");
|
||||
icon.setBounds(0, 0, lineHeight, lineHeight);
|
||||
spannableMessage.setSpan(
|
||||
imageSpan, indexIconStart, indexIconEnd,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return spannableMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color associated with the specified attribute in the context's theme.
|
||||
*/
|
||||
@ColorInt
|
||||
private static int getThemeAttrColor(final Context context, final int attributeColor) {
|
||||
final int colorResId = getAttrResourceId(context, attributeColor);
|
||||
return ContextCompat.getColor(context, colorResId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the resolved resource assigned to the given attribute.
|
||||
*/
|
||||
private static int getAttrResourceId(final Context context, final int attributeColor) {
|
||||
final int[] attrs = {attributeColor};
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
|
||||
final int colorResId = typedArray.getResourceId(0, 0);
|
||||
typedArray.recycle();
|
||||
return colorResId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog with the given view.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param dialogTitle The title of the dialog
|
||||
* @param customView The customized view
|
||||
* @param positiveButtonText The text of the positive button
|
||||
* @param positiveListener This listener will be invoked when the positive button in the dialog
|
||||
* is clicked
|
||||
* @param negativeButtonText The text of the negative button
|
||||
* @param negativeListener This listener will be invoked when the negative button in the dialog
|
||||
* is clicked
|
||||
* @return the {@link Dialog} with the given view
|
||||
*/
|
||||
public static Dialog createCustomDialog(Context context, CharSequence dialogTitle,
|
||||
View customView, CharSequence positiveButtonText,
|
||||
DialogInterface.OnClickListener positiveListener, CharSequence negativeButtonText,
|
||||
DialogInterface.OnClickListener negativeListener) {
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setView(customView)
|
||||
.setTitle(dialogTitle)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(positiveButtonText, positiveListener)
|
||||
.setNegativeButton(negativeButtonText, negativeListener)
|
||||
.create();
|
||||
if (customView instanceof ScrollView || customView instanceof AbsListView) {
|
||||
setScrollIndicators(customView);
|
||||
}
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single choice {@link ListView} with given {@link ItemInfo} list.
|
||||
*
|
||||
* @param context A context.
|
||||
* @param itemInfoList A {@link ItemInfo} list.
|
||||
* @param itemListener The listener will be invoked when the item is clicked.
|
||||
*/
|
||||
@NonNull
|
||||
public static ListView createSingleChoiceListView(@NonNull Context context,
|
||||
@NonNull List<? extends ItemInfo> itemInfoList,
|
||||
@Nullable AdapterView.OnItemClickListener itemListener) {
|
||||
final ListView list = new ListView(context);
|
||||
// Set an id to save its state.
|
||||
list.setId(android.R.id.list);
|
||||
list.setDivider(/* divider= */ null);
|
||||
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
final ItemInfoArrayAdapter
|
||||
adapter = new ItemInfoArrayAdapter(context, itemInfoList);
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(itemListener);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
|
||||
/** A custom preference acting as footer of a page. Disables the movement method by default. */
|
||||
public final class AccessibilityFooterPreference extends FooterPreference {
|
||||
|
||||
private boolean mLinkEnabled;
|
||||
|
||||
public AccessibilityFooterPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public AccessibilityFooterPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
final TextView title = holder.itemView.findViewById(android.R.id.title);
|
||||
if (mLinkEnabled) {
|
||||
// When a TextView has a movement method, it will set the view to focusable and
|
||||
// clickable. This makes View.onTouchEvent always return true and consumes the touch
|
||||
// event, essentially nullifying any return values of MovementMethod.onTouchEvent.
|
||||
// To still allow propagating touch events to the parent when this view doesn't have
|
||||
// links, we only set the movement method here if the text contains links.
|
||||
title.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Groups of related title and link content by making the container focusable,
|
||||
// then make all the children inside not focusable.
|
||||
title.setFocusable(false);
|
||||
} else {
|
||||
title.setMovementMethod(/* movement= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title field supports movement method.
|
||||
*/
|
||||
public void setLinkEnabled(boolean enabled) {
|
||||
if (mLinkEnabled != enabled) {
|
||||
mLinkEnabled = enabled;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the title field supports movement method.
|
||||
*/
|
||||
public boolean isLinkEnabled() {
|
||||
return mLinkEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.HelpUtils;
|
||||
|
||||
/**
|
||||
* Preference controller that controls the help link and customizes the preference title in {@link
|
||||
* AccessibilityFooterPreference}.
|
||||
*/
|
||||
public class AccessibilityFooterPreferenceController extends BasePreferenceController {
|
||||
|
||||
private int mHelpResource;
|
||||
private String mLearnMoreText;
|
||||
private String mIntroductionTitle;
|
||||
|
||||
public AccessibilityFooterPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
|
||||
final AccessibilityFooterPreference footerPreference =
|
||||
screen.findPreference(getPreferenceKey());
|
||||
updateFooterPreferences(footerPreference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups a help item in the {@link AccessibilityFooterPreference} with specific content
|
||||
* description.
|
||||
*/
|
||||
public void setupHelpLink(int helpResource, String learnMoreText) {
|
||||
mHelpResource = helpResource;
|
||||
mLearnMoreText = learnMoreText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides this if showing a help item in the {@link AccessibilityFooterPreference}, by
|
||||
* returning the resource id.
|
||||
*
|
||||
* @return the resource id for the help url
|
||||
*/
|
||||
protected int getHelpResource() {
|
||||
return mHelpResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides this if showing a help item in the {@link AccessibilityFooterPreference} with
|
||||
* specific learn more title.
|
||||
*
|
||||
* @return learn more title for the help url
|
||||
*/
|
||||
protected String getLearnMoreText() {
|
||||
return mLearnMoreText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the announcement the specific features introduction in the {@link
|
||||
* AccessibilityFooterPreference}.
|
||||
*/
|
||||
public void setIntroductionTitle(String introductionTitle) {
|
||||
mIntroductionTitle = introductionTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides this if announcement the specific features introduction in the {@link
|
||||
* AccessibilityFooterPreference}.
|
||||
*
|
||||
* @return the extended content description for specific features introduction
|
||||
*/
|
||||
protected String getIntroductionTitle() {
|
||||
return mIntroductionTitle;
|
||||
}
|
||||
|
||||
private void updateFooterPreferences(AccessibilityFooterPreference footerPreference) {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getIntroductionTitle()).append("\n\n").append(footerPreference.getTitle());
|
||||
footerPreference.setContentDescription(sb);
|
||||
|
||||
final Intent helpIntent;
|
||||
if (getHelpResource() != 0) {
|
||||
// Returns may be null if content is wrong or empty.
|
||||
helpIntent = HelpUtils.getHelpIntent(mContext, mContext.getString(getHelpResource()),
|
||||
mContext.getClass().getName());
|
||||
} else {
|
||||
helpIntent = null;
|
||||
}
|
||||
|
||||
if (helpIntent != null) {
|
||||
footerPreference.setLearnMoreAction(view -> {
|
||||
view.startActivityForResult(helpIntent, 0);
|
||||
});
|
||||
footerPreference.setLearnMoreText(getLearnMoreText());
|
||||
footerPreference.setLinkEnabled(true);
|
||||
} else {
|
||||
footerPreference.setLinkEnabled(false);
|
||||
}
|
||||
|
||||
// Grouping subcomponents to make more accessible.
|
||||
footerPreference.setSelectable(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.accessibility;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextSwitcher;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.AnimRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.util.Preconditions;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.android.server.accessibility.Flags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settingslib.widget.LottieColorUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieDrawable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class for creating the dialog that guides users for gesture navigation for
|
||||
* accessibility services.
|
||||
*/
|
||||
public final class AccessibilityGestureNavigationTutorial {
|
||||
private static final String TAG = "AccessibilityGestureNavigationTutorial";
|
||||
|
||||
/** IntDef enum for dialog type. */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON,
|
||||
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE,
|
||||
DialogType.GESTURE_NAVIGATION_SETTINGS,
|
||||
})
|
||||
|
||||
private @interface DialogType {
|
||||
int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0;
|
||||
int LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE = 1;
|
||||
int GESTURE_NAVIGATION_SETTINGS = 2;
|
||||
}
|
||||
|
||||
private AccessibilityGestureNavigationTutorial() {}
|
||||
|
||||
private static final DialogInterface.OnClickListener mOnClickListener =
|
||||
(DialogInterface dialog, int which) -> dialog.dismiss();
|
||||
|
||||
/**
|
||||
* Displays a dialog that guides users to use accessibility features with accessibility
|
||||
* gestures under system gesture navigation mode.
|
||||
*/
|
||||
public static AlertDialog showGestureNavigationTutorialDialog(Context context,
|
||||
DialogInterface.OnDismissListener onDismissListener) {
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setView(createTutorialDialogContentView(context,
|
||||
DialogType.GESTURE_NAVIGATION_SETTINGS))
|
||||
.setPositiveButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
|
||||
.setOnDismissListener(onDismissListener)
|
||||
.create();
|
||||
|
||||
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
alertDialog.setCanceledOnTouchOutside(false);
|
||||
alertDialog.show();
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
static AlertDialog showAccessibilityGestureTutorialDialog(Context context) {
|
||||
return createDialog(context, DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE);
|
||||
}
|
||||
|
||||
static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes) {
|
||||
return createAccessibilityTutorialDialog(context, shortcutTypes, mOnClickListener);
|
||||
}
|
||||
|
||||
static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes,
|
||||
@Nullable DialogInterface.OnClickListener actionButtonListener) {
|
||||
|
||||
final int category = SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS;
|
||||
final DialogInterface.OnClickListener linkButtonListener =
|
||||
(dialog, which) -> new SubSettingLauncher(context)
|
||||
.setDestination(AccessibilityButtonFragment.class.getName())
|
||||
.setSourceMetricsCategory(category)
|
||||
.launch();
|
||||
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setPositiveButton(R.string.accessibility_tutorial_dialog_button,
|
||||
actionButtonListener)
|
||||
.setNegativeButton(R.string.accessibility_tutorial_dialog_link_button,
|
||||
linkButtonListener)
|
||||
.create();
|
||||
|
||||
final List<TutorialPage> tutorialPages =
|
||||
createShortcutTutorialPages(context, shortcutTypes);
|
||||
Preconditions.checkArgument(!tutorialPages.isEmpty(),
|
||||
/* errorMessage= */ "Unexpected tutorial pages size");
|
||||
|
||||
final TutorialPageChangeListener.OnPageSelectedCallback callback = index -> {
|
||||
final int pageType = tutorialPages.get(index).getType();
|
||||
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(
|
||||
pageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE);
|
||||
};
|
||||
|
||||
alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, callback));
|
||||
|
||||
// Showing first page won't invoke onPageSelectedCallback. Need to check the first tutorial
|
||||
// page type manually to set correct visibility of the link button.
|
||||
alertDialog.setOnShowListener(dialog -> {
|
||||
final int firstPageType = tutorialPages.get(0).getType();
|
||||
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(
|
||||
firstPageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE);
|
||||
});
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
static AlertDialog createAccessibilityTutorialDialogForSetupWizard(Context context,
|
||||
int shortcutTypes) {
|
||||
return createAccessibilityTutorialDialogForSetupWizard(context, shortcutTypes,
|
||||
mOnClickListener);
|
||||
}
|
||||
|
||||
static AlertDialog createAccessibilityTutorialDialogForSetupWizard(Context context,
|
||||
int shortcutTypes, @Nullable DialogInterface.OnClickListener actionButtonListener) {
|
||||
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setPositiveButton(R.string.accessibility_tutorial_dialog_button,
|
||||
actionButtonListener)
|
||||
.create();
|
||||
|
||||
final List<TutorialPage> tutorialPages =
|
||||
createShortcutTutorialPages(context, shortcutTypes);
|
||||
Preconditions.checkArgument(!tutorialPages.isEmpty(),
|
||||
/* errorMessage= */ "Unexpected tutorial pages size");
|
||||
|
||||
alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, null));
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a content View for a dialog to confirm that they want to enable a service.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param dialogType The type of tutorial dialog
|
||||
* @return A content view suitable for viewing
|
||||
*/
|
||||
private static View createTutorialDialogContentView(Context context, int dialogType) {
|
||||
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
View content = null;
|
||||
|
||||
switch (dialogType) {
|
||||
case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON:
|
||||
content = inflater.inflate(
|
||||
R.layout.tutorial_dialog_launch_service_by_accessibility_button, null);
|
||||
break;
|
||||
case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE:
|
||||
content = inflater.inflate(
|
||||
R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null);
|
||||
setupGestureNavigationTextWithImage(context, content);
|
||||
break;
|
||||
case DialogType.GESTURE_NAVIGATION_SETTINGS:
|
||||
content = inflater.inflate(
|
||||
R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null);
|
||||
setupGestureNavigationTextWithImage(context, content);
|
||||
break;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private static void setupGestureNavigationTextWithImage(Context context, View view) {
|
||||
final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context);
|
||||
|
||||
final ImageView imageView = view.findViewById(R.id.image);
|
||||
final int gestureSettingsImageResId =
|
||||
isTouchExploreEnabled ? R.drawable.a11y_gesture_navigation_three_finger_preview
|
||||
: R.drawable.a11y_gesture_navigation_two_finger_preview;
|
||||
imageView.setImageResource(gestureSettingsImageResId);
|
||||
|
||||
final TextView textView = view.findViewById(R.id.gesture_tutorial_message);
|
||||
textView.setText(isTouchExploreEnabled
|
||||
? R.string.accessibility_tutorial_dialog_message_gesture_settings_talkback
|
||||
: R.string.accessibility_tutorial_dialog_message_gesture_settings);
|
||||
}
|
||||
|
||||
private static AlertDialog createDialog(Context context, int dialogType) {
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setView(createTutorialDialogContentView(context, dialogType))
|
||||
.setPositiveButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
|
||||
.create();
|
||||
|
||||
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
alertDialog.setCanceledOnTouchOutside(false);
|
||||
alertDialog.show();
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
private static class TutorialPagerAdapter extends PagerAdapter {
|
||||
private final List<TutorialPage> mTutorialPages;
|
||||
private TutorialPagerAdapter(List<TutorialPage> tutorialPages) {
|
||||
this.mTutorialPages = tutorialPages;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
final View itemView = mTutorialPages.get(position).getIllustrationView();
|
||||
container.addView(itemView);
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTutorialPages.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
|
||||
return view == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position,
|
||||
@NonNull Object object) {
|
||||
final View itemView = mTutorialPages.get(position).getIllustrationView();
|
||||
container.removeView(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageView createImageView(Context context, int imageRes) {
|
||||
final ImageView imageView = new ImageView(context);
|
||||
imageView.setImageResource(imageRes);
|
||||
imageView.setAdjustViewBounds(true);
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
private static View createIllustrationView(Context context, @DrawableRes int imageRes) {
|
||||
final View illustrationFrame = inflateAndInitIllustrationFrame(context);
|
||||
final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image);
|
||||
lottieView.setImageResource(imageRes);
|
||||
|
||||
return illustrationFrame;
|
||||
}
|
||||
|
||||
private static View createIllustrationViewWithImageRawResource(Context context,
|
||||
@RawRes int imageRawRes) {
|
||||
final View illustrationFrame = inflateAndInitIllustrationFrame(context);
|
||||
final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image);
|
||||
lottieView.setFailureListener(
|
||||
result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawRes,
|
||||
result));
|
||||
lottieView.setAnimation(imageRawRes);
|
||||
lottieView.setRepeatCount(LottieDrawable.INFINITE);
|
||||
LottieColorUtils.applyDynamicColors(context, lottieView);
|
||||
lottieView.playAnimation();
|
||||
|
||||
return illustrationFrame;
|
||||
}
|
||||
|
||||
private static View inflateAndInitIllustrationFrame(Context context) {
|
||||
final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
|
||||
|
||||
return inflater.inflate(R.layout.accessibility_lottie_animation_view, /* root= */ null);
|
||||
}
|
||||
|
||||
private static View createShortcutNavigationContentView(Context context,
|
||||
List<TutorialPage> tutorialPages,
|
||||
TutorialPageChangeListener.OnPageSelectedCallback onPageSelectedCallback) {
|
||||
|
||||
final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
|
||||
final View contentView = inflater.inflate(
|
||||
R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null);
|
||||
|
||||
final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container);
|
||||
indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE);
|
||||
for (TutorialPage page : tutorialPages) {
|
||||
indicatorContainer.addView(page.getIndicatorIcon());
|
||||
}
|
||||
tutorialPages.get(/* firstIndex */ 0).getIndicatorIcon().setEnabled(true);
|
||||
|
||||
final TextSwitcher title = contentView.findViewById(R.id.title);
|
||||
title.setFactory(() -> makeTitleView(context));
|
||||
title.setText(tutorialPages.get(/* firstIndex */ 0).getTitle());
|
||||
|
||||
final TextSwitcher instruction = contentView.findViewById(R.id.instruction);
|
||||
instruction.setFactory(() -> makeInstructionView(context));
|
||||
instruction.setText(tutorialPages.get(/* firstIndex */ 0).getInstruction());
|
||||
|
||||
final ViewPager viewPager = contentView.findViewById(R.id.view_pager);
|
||||
viewPager.setAdapter(new TutorialPagerAdapter(tutorialPages));
|
||||
viewPager.setContentDescription(context.getString(R.string.accessibility_tutorial_pager,
|
||||
/* firstPage */ 1, tutorialPages.size()));
|
||||
viewPager.setImportantForAccessibility(tutorialPages.size() > 1
|
||||
? View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
TutorialPageChangeListener listener = new TutorialPageChangeListener(context, viewPager,
|
||||
title, instruction, tutorialPages);
|
||||
listener.setOnPageSelectedCallback(onPageSelectedCallback);
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
private static View makeTitleView(Context context) {
|
||||
final TextView textView = new TextView(context);
|
||||
// Sets the text color, size, style, hint color, and highlight color from the specified
|
||||
// TextAppearance resource.
|
||||
TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogTitle);
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
return textView;
|
||||
}
|
||||
|
||||
private static View makeInstructionView(Context context) {
|
||||
final TextView textView = new TextView(context);
|
||||
TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogDescription);
|
||||
return textView;
|
||||
}
|
||||
|
||||
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
|
||||
final int type = UserShortcutType.SOFTWARE;
|
||||
final CharSequence title = getSoftwareTitle(context);
|
||||
final View image = createSoftwareImage(context);
|
||||
final CharSequence instruction = getSoftwareInstruction(context);
|
||||
final ImageView indicatorIcon =
|
||||
createImageView(context, R.drawable.ic_accessibility_page_indicator);
|
||||
indicatorIcon.setEnabled(false);
|
||||
|
||||
return new TutorialPage(type, title, image, indicatorIcon, instruction);
|
||||
}
|
||||
|
||||
private static TutorialPage createHardwareTutorialPage(@NonNull Context context) {
|
||||
final int type = UserShortcutType.HARDWARE;
|
||||
final CharSequence title =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_title_volume);
|
||||
final View image =
|
||||
createIllustrationView(context, R.drawable.a11y_shortcut_type_hardware);
|
||||
final ImageView indicatorIcon =
|
||||
createImageView(context, R.drawable.ic_accessibility_page_indicator);
|
||||
final CharSequence instruction =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_message_volume);
|
||||
indicatorIcon.setEnabled(false);
|
||||
|
||||
return new TutorialPage(type, title, image, indicatorIcon, instruction);
|
||||
}
|
||||
|
||||
private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) {
|
||||
final int type = UserShortcutType.TRIPLETAP;
|
||||
final CharSequence title =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_title_triple);
|
||||
final View image =
|
||||
createIllustrationViewWithImageRawResource(context,
|
||||
R.raw.a11y_shortcut_type_triple_tap);
|
||||
final CharSequence instruction =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_message_triple);
|
||||
final ImageView indicatorIcon =
|
||||
createImageView(context, R.drawable.ic_accessibility_page_indicator);
|
||||
indicatorIcon.setEnabled(false);
|
||||
|
||||
return new TutorialPage(type, title, image, indicatorIcon, instruction);
|
||||
}
|
||||
|
||||
private static TutorialPage createTwoFingerTripleTapTutorialPage(@NonNull Context context) {
|
||||
// TODO(b/308088945): Update tutorial string and image when UX provides them
|
||||
final int type = UserShortcutType.TWOFINGERTRIPLETAP;
|
||||
final CharSequence title =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_title_two_finger_double);
|
||||
final View image =
|
||||
createIllustrationViewWithImageRawResource(context,
|
||||
R.raw.a11y_shortcut_type_triple_tap);
|
||||
final CharSequence instruction =
|
||||
context.getText(R.string.accessibility_tutorial_dialog_message_two_finger_triple);
|
||||
final ImageView indicatorIcon =
|
||||
createImageView(context, R.drawable.ic_accessibility_page_indicator);
|
||||
indicatorIcon.setEnabled(false);
|
||||
|
||||
return new TutorialPage(type, title, image, indicatorIcon, instruction);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<TutorialPage> createShortcutTutorialPages(@NonNull Context context,
|
||||
int shortcutTypes) {
|
||||
final List<TutorialPage> tutorialPages = new ArrayList<>();
|
||||
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
|
||||
tutorialPages.add(createSoftwareTutorialPage(context));
|
||||
}
|
||||
|
||||
if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
|
||||
tutorialPages.add(createHardwareTutorialPage(context));
|
||||
}
|
||||
|
||||
if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
|
||||
tutorialPages.add(createTripleTapTutorialPage(context));
|
||||
}
|
||||
|
||||
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
|
||||
if ((shortcutTypes & UserShortcutType.TWOFINGERTRIPLETAP)
|
||||
== UserShortcutType.TWOFINGERTRIPLETAP) {
|
||||
tutorialPages.add(createTwoFingerTripleTapTutorialPage(context));
|
||||
}
|
||||
}
|
||||
|
||||
return tutorialPages;
|
||||
}
|
||||
|
||||
private static View createSoftwareImage(Context context) {
|
||||
int resId;
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
resId = R.drawable.a11y_shortcut_type_software_floating;
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
resId = AccessibilityUtil.isTouchExploreEnabled(context)
|
||||
? R.drawable.a11y_shortcut_type_software_gesture_talkback
|
||||
: R.drawable.a11y_shortcut_type_software_gesture;
|
||||
} else {
|
||||
resId = R.drawable.a11y_shortcut_type_software;
|
||||
}
|
||||
return createIllustrationView(context, resId);
|
||||
}
|
||||
|
||||
private static CharSequence getSoftwareTitle(Context context) {
|
||||
int resId;
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
resId = R.string.accessibility_tutorial_dialog_title_button;
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
resId = R.string.accessibility_tutorial_dialog_title_gesture;
|
||||
} else {
|
||||
resId = R.string.accessibility_tutorial_dialog_title_button;
|
||||
}
|
||||
return context.getText(resId);
|
||||
}
|
||||
|
||||
private static CharSequence getSoftwareInstruction(Context context) {
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
|
||||
final int resId = R.string.accessibility_tutorial_dialog_message_floating_button;
|
||||
sb.append(context.getText(resId));
|
||||
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
|
||||
final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
|
||||
? R.string.accessibility_tutorial_dialog_message_gesture_talkback
|
||||
: R.string.accessibility_tutorial_dialog_message_gesture;
|
||||
sb.append(context.getText(resId));
|
||||
} else {
|
||||
final int resId = R.string.accessibility_tutorial_dialog_message_button;
|
||||
sb.append(getSoftwareInstructionWithIcon(context, context.getText(resId)));
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) {
|
||||
final String message = text.toString();
|
||||
final SpannableString spannableInstruction = SpannableString.valueOf(message);
|
||||
final int indexIconStart = message.indexOf("%s");
|
||||
final int indexIconEnd = indexIconStart + 2;
|
||||
final ImageView iconView = new ImageView(context);
|
||||
iconView.setImageDrawable(context.getDrawable(R.drawable.ic_accessibility_new));
|
||||
final Drawable icon = iconView.getDrawable().mutate();
|
||||
final ImageSpan imageSpan = new ImageSpan(icon);
|
||||
imageSpan.setContentDescription("");
|
||||
icon.setBounds(/* left= */ 0, /* top= */ 0,
|
||||
icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
|
||||
spannableInstruction.setSpan(imageSpan, indexIconStart, indexIconEnd,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
return spannableInstruction;
|
||||
}
|
||||
|
||||
private static class TutorialPage {
|
||||
private final int mType;
|
||||
private final CharSequence mTitle;
|
||||
private final View mIllustrationView;
|
||||
private final ImageView mIndicatorIcon;
|
||||
private final CharSequence mInstruction;
|
||||
|
||||
TutorialPage(int type, CharSequence title, View illustrationView, ImageView indicatorIcon,
|
||||
CharSequence instruction) {
|
||||
this.mType = type;
|
||||
this.mTitle = title;
|
||||
this.mIllustrationView = illustrationView;
|
||||
this.mIndicatorIcon = indicatorIcon;
|
||||
this.mInstruction = instruction;
|
||||
|
||||
setupIllustrationChildViewsGravity();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public View getIllustrationView() {
|
||||
return mIllustrationView;
|
||||
}
|
||||
|
||||
public ImageView getIndicatorIcon() {
|
||||
return mIndicatorIcon;
|
||||
}
|
||||
|
||||
public CharSequence getInstruction() {
|
||||
return mInstruction;
|
||||
}
|
||||
|
||||
private void setupIllustrationChildViewsGravity() {
|
||||
final View backgroundView = mIllustrationView.findViewById(R.id.image_background);
|
||||
initViewGravity(backgroundView);
|
||||
|
||||
final View lottieView = mIllustrationView.findViewById(R.id.image);
|
||||
initViewGravity(lottieView);
|
||||
}
|
||||
|
||||
private void initViewGravity(@NonNull View view) {
|
||||
final FrameLayout.LayoutParams layoutParams =
|
||||
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.gravity = Gravity.CENTER;
|
||||
|
||||
view.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TutorialPageChangeListener implements ViewPager.OnPageChangeListener {
|
||||
private int mLastTutorialPagePosition = 0;
|
||||
private final Context mContext;
|
||||
private final TextSwitcher mTitle;
|
||||
private final TextSwitcher mInstruction;
|
||||
private final List<TutorialPage> mTutorialPages;
|
||||
private final ViewPager mViewPager;
|
||||
private OnPageSelectedCallback mOnPageSelectedCallback;
|
||||
|
||||
TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title,
|
||||
ViewGroup instruction, List<TutorialPage> tutorialPages) {
|
||||
this.mContext = context;
|
||||
this.mViewPager = viewPager;
|
||||
this.mTitle = (TextSwitcher) title;
|
||||
this.mInstruction = (TextSwitcher) instruction;
|
||||
this.mTutorialPages = tutorialPages;
|
||||
this.mOnPageSelectedCallback = null;
|
||||
|
||||
this.mViewPager.addOnPageChangeListener(this);
|
||||
}
|
||||
|
||||
public void setOnPageSelectedCallback(
|
||||
OnPageSelectedCallback callback) {
|
||||
this.mOnPageSelectedCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset,
|
||||
int positionOffsetPixels) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
final boolean isPreviousPosition =
|
||||
mLastTutorialPagePosition > position;
|
||||
@AnimRes
|
||||
final int inAnimationResId = isPreviousPosition
|
||||
? android.R.anim.slide_in_left
|
||||
: com.android.internal.R.anim.slide_in_right;
|
||||
|
||||
@AnimRes
|
||||
final int outAnimationResId = isPreviousPosition
|
||||
? android.R.anim.slide_out_right
|
||||
: com.android.internal.R.anim.slide_out_left;
|
||||
|
||||
mTitle.setInAnimation(mContext, inAnimationResId);
|
||||
mTitle.setOutAnimation(mContext, outAnimationResId);
|
||||
mTitle.setText(mTutorialPages.get(position).getTitle());
|
||||
|
||||
mInstruction.setInAnimation(mContext, inAnimationResId);
|
||||
mInstruction.setOutAnimation(mContext, outAnimationResId);
|
||||
mInstruction.setText(mTutorialPages.get(position).getInstruction());
|
||||
|
||||
for (TutorialPage page : mTutorialPages) {
|
||||
page.getIndicatorIcon().setEnabled(false);
|
||||
}
|
||||
mTutorialPages.get(position).getIndicatorIcon().setEnabled(true);
|
||||
mLastTutorialPagePosition = position;
|
||||
|
||||
final int currentPageNumber = position + 1;
|
||||
mViewPager.setContentDescription(
|
||||
mContext.getString(R.string.accessibility_tutorial_pager,
|
||||
currentPageNumber, mTutorialPages.size()));
|
||||
|
||||
if (mOnPageSelectedCallback != null) {
|
||||
mOnPageSelectedCallback.onPageSelected(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/** The interface that provides a callback method after tutorial page is selected. */
|
||||
private interface OnPageSelectedCallback {
|
||||
|
||||
/** The callback method after tutorial page is selected. */
|
||||
void onPageSelected(int index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.accessibility;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothHapClient;
|
||||
import android.bluetooth.BluetoothHearingAid;
|
||||
import android.bluetooth.BluetoothLeAudio;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settingslib.bluetooth.BluetoothCallback;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.HearingAidInfo;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Controller that shows and updates the bluetooth device name
|
||||
*/
|
||||
public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
|
||||
implements LifecycleObserver, OnStart, OnStop, BluetoothCallback,
|
||||
LocalBluetoothProfileManager.ServiceListener {
|
||||
private static final String TAG = "AccessibilityHearingAidPreferenceController";
|
||||
private Preference mHearingAidPreference;
|
||||
|
||||
private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateState(mHearingAidPreference);
|
||||
}
|
||||
};
|
||||
|
||||
private final LocalBluetoothManager mLocalBluetoothManager;
|
||||
private final LocalBluetoothProfileManager mProfileManager;
|
||||
private final HearingAidHelper mHelper;
|
||||
private FragmentManager mFragmentManager;
|
||||
|
||||
public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager(
|
||||
context);
|
||||
mProfileManager = mLocalBluetoothManager.getProfileManager();
|
||||
mHelper = new HearingAidHelper(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mHearingAidPreference = screen.findPreference(getPreferenceKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return mHelper.isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
|
||||
filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
|
||||
filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
|
||||
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
|
||||
mContext.registerReceiver(mHearingAidChangedReceiver, filter);
|
||||
mLocalBluetoothManager.getEventManager().registerCallback(this);
|
||||
// Can't get connected hearing aids when hearing aids related profiles are not ready. The
|
||||
// profiles will be ready after the services are connected. Needs to add listener and
|
||||
// updates the information when all hearing aids related services are connected.
|
||||
if (!mHelper.isAllHearingAidRelatedProfilesReady()) {
|
||||
mProfileManager.addServiceListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mContext.unregisterReceiver(mHearingAidChangedReceiver);
|
||||
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
|
||||
mProfileManager.removeServiceListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlePreferenceTreeClick(Preference preference) {
|
||||
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
|
||||
launchHearingAidPage();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
|
||||
if (device == null) {
|
||||
return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
|
||||
}
|
||||
|
||||
final int connectedNum = getConnectedHearingAidDeviceNum();
|
||||
final CharSequence name = device.getName();
|
||||
if (connectedNum > 1) {
|
||||
return mContext.getString(R.string.accessibility_hearingaid_more_device_summary, name);
|
||||
}
|
||||
|
||||
// Check if another side of LE audio hearing aid is connected as a pair
|
||||
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
|
||||
if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_left_and_right_side_device_summary,
|
||||
name);
|
||||
}
|
||||
|
||||
// Check if another side of ASHA hearing aid is connected as a pair
|
||||
final CachedBluetoothDevice subDevice = device.getSubDevice();
|
||||
if (subDevice != null && subDevice.isConnected()) {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
|
||||
}
|
||||
|
||||
final int side = device.getDeviceSide();
|
||||
if (side == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
|
||||
} else if (side == HearingAidInfo.DeviceSide.SIDE_LEFT) {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_left_side_device_summary, name);
|
||||
} else if (side == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_right_side_device_summary, name);
|
||||
}
|
||||
|
||||
// Invalid side
|
||||
return mContext.getString(
|
||||
R.string.accessibility_hearingaid_active_device_summary, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
|
||||
if (activeDevice == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
|
||||
HearingAidUtils.launchHearingAidPairingDialog(
|
||||
mFragmentManager, activeDevice, getMetricsCategory());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected() {
|
||||
if (mHelper.isAllHearingAidRelatedProfilesReady()) {
|
||||
updateState(mHearingAidPreference);
|
||||
mProfileManager.removeServiceListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public void setFragmentManager(FragmentManager fragmentManager) {
|
||||
mFragmentManager = fragmentManager;
|
||||
}
|
||||
|
||||
private int getConnectedHearingAidDeviceNum() {
|
||||
return mHelper.getConnectedHearingAidDeviceList().size();
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
void setPreference(Preference preference) {
|
||||
mHearingAidPreference = preference;
|
||||
}
|
||||
|
||||
private void launchHearingAidPage() {
|
||||
new SubSettingLauncher(mContext)
|
||||
.setDestination(AccessibilityHearingAidsFragment.class.getName())
|
||||
.setSourceMetricsCategory(getMetricsCategory())
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibility;
|
||||
|
||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
|
||||
/** Accessibility settings for hearing aids. */
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPreferenceFragment {
|
||||
private static final String TAG = "AccessibilityHearingAidsFragment";
|
||||
private static final String KEY_HEARING_OPTIONS_CATEGORY = "hearing_options_category";
|
||||
private static final int SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX = 20;
|
||||
private String mFeatureName;
|
||||
|
||||
public AccessibilityHearingAidsFragment() {
|
||||
super(DISALLOW_CONFIG_BLUETOOTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
use(AvailableHearingDevicePreferenceController.class).init(this);
|
||||
use(SavedHearingDevicePreferenceController.class).init(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mFeatureName = getContext().getString(R.string.accessibility_hearingaid_title);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
final PreferenceCategory controlCategory = findPreference(KEY_HEARING_OPTIONS_CATEGORY);
|
||||
// To move the shortcut preference under controlCategory need to remove the original added.
|
||||
mShortcutPreference.setOrder(SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX);
|
||||
getPreferenceScreen().removePreference(mShortcutPreference);
|
||||
controlCategory.addPreference(mShortcutPreference);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_hearing_aids;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentName getComponentName() {
|
||||
return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CharSequence getLabelName() {
|
||||
return mFeatureName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentName getTileComponentName() {
|
||||
// Don't have quick settings tile for now.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CharSequence getTileTooltipContent(int type) {
|
||||
// Don't have quick settings tile for now.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean showGeneralCategory() {
|
||||
// Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY.
|
||||
// In order to modify that, we need to use our own PreferenceCategory for this page.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CharSequence getShortcutTitle() {
|
||||
return getText(R.string.accessibility_hearing_device_shortcut_title);
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.accessibility_hearing_aids);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** LayerDrawable that contains device icon as background and given icon as foreground. */
|
||||
public class AccessibilityLayerDrawable extends LayerDrawable {
|
||||
|
||||
private AccessibilityLayerDrawableState mState;
|
||||
|
||||
/**
|
||||
* Creates a new layer drawable with the list of specified layers.
|
||||
*
|
||||
* @param layers a list of drawables to use as layers in this new drawable,
|
||||
* must be non-null
|
||||
*/
|
||||
private AccessibilityLayerDrawable(@NonNull Drawable[] layers) {
|
||||
super(layers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link LayerDrawable} that contains device icon as background and given menu icon
|
||||
* with given {@code opacity} value as foreground.
|
||||
*
|
||||
* @param context the valid context used to get the icon
|
||||
* @param resId the resource ID of the given icon
|
||||
* @param opacity the opacity to apply to the given icon
|
||||
* @return the drawable that combines the device icon and the given icon
|
||||
*/
|
||||
public static AccessibilityLayerDrawable createLayerDrawable(Context context, int resId,
|
||||
int opacity) {
|
||||
final Drawable bg = context.getDrawable(R.drawable.a11y_button_preview_base);
|
||||
final AccessibilityLayerDrawable basicDrawable = new AccessibilityLayerDrawable(
|
||||
new Drawable[]{bg, null});
|
||||
|
||||
basicDrawable.updateLayerDrawable(context, resId, opacity);
|
||||
return basicDrawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the drawable with given {@code resId} drawable and {@code opacity}(alpha)
|
||||
* value at index 1 layer.
|
||||
*
|
||||
* @param context the valid context used to get the icon
|
||||
* @param resId the resource ID of the given icon
|
||||
* @param opacity the opacity to apply to the given icon
|
||||
*/
|
||||
public void updateLayerDrawable(Context context, int resId, int opacity) {
|
||||
final Drawable icon = context.getDrawable(resId);
|
||||
icon.setAlpha(opacity);
|
||||
this.setDrawable(/* index= */ 1, icon);
|
||||
this.setConstantState(context, resId, opacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/** Stores the constant state and data to the given drawable. */
|
||||
private void setConstantState(Context context, int resId, int opacity) {
|
||||
mState = new AccessibilityLayerDrawableState(context, resId, opacity);
|
||||
}
|
||||
|
||||
/** {@link ConstantState} to store the data of {@link AccessibilityLayerDrawable}. */
|
||||
@VisibleForTesting
|
||||
static class AccessibilityLayerDrawableState extends ConstantState {
|
||||
|
||||
private final Context mContext;
|
||||
private final int mResId;
|
||||
private final int mOpacity;
|
||||
|
||||
AccessibilityLayerDrawableState(Context context, int resId, int opacity) {
|
||||
mContext = context;
|
||||
mResId = resId;
|
||||
mOpacity = opacity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return createLayerDrawable(mContext, mResId, mOpacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final AccessibilityLayerDrawableState that = (AccessibilityLayerDrawableState) o;
|
||||
return mResId == that.mResId
|
||||
&& mOpacity == that.mOpacity
|
||||
&& Objects.equals(mContext, that.mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mContext, mResId, mOpacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.ComponentName;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provider for Accessibility metrics related features.
|
||||
*/
|
||||
public interface AccessibilityMetricsFeatureProvider {
|
||||
|
||||
/**
|
||||
* Returns {@link android.app.settings.SettingsEnums} value according to the {@code
|
||||
* componentName}.
|
||||
*
|
||||
* @param componentName the component name of the downloaded service or activity
|
||||
* @return value in {@link android.app.settings.SettingsEnums}
|
||||
*/
|
||||
int getDownloadedFeatureMetricsCategory(@Nullable ComponentName componentName);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
|
||||
/**
|
||||
* Provider implementation for Accessibility metrics related features.
|
||||
*/
|
||||
public class AccessibilityMetricsFeatureProviderImpl implements
|
||||
AccessibilityMetricsFeatureProvider {
|
||||
|
||||
@Override
|
||||
public int getDownloadedFeatureMetricsCategory(ComponentName componentName) {
|
||||
return SettingsEnums.ACCESSIBILITY_SERVICE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibility;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/** Provides utility methods to accessibility quick settings only. */
|
||||
final class AccessibilityQuickSettingUtils {
|
||||
|
||||
private static final String ACCESSIBILITY_PERF = "accessibility_prefs";
|
||||
private static final String KEY_TILE_SERVICE_SHOWN = "tile_service_shown";
|
||||
private static final char COMPONENT_NAME_SEPARATOR = ':';
|
||||
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
|
||||
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
|
||||
|
||||
/**
|
||||
* Opts in component name into {@link AccessibilityQuickSettingUtils#KEY_TILE_SERVICE_SHOWN}
|
||||
* colon-separated string in {@link SharedPreferences}.
|
||||
*
|
||||
* @param context The current context.
|
||||
* @param componentName The component name that need to be opted in SharedPreferences.
|
||||
*/
|
||||
public static void optInValueToSharedPreferences(Context context,
|
||||
@NonNull ComponentName componentName) {
|
||||
final String targetString = getFromSharedPreferences(context);
|
||||
if (hasValueInSharedPreferences(targetString, componentName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
|
||||
if (!TextUtils.isEmpty(targetString)) {
|
||||
joiner.add(targetString);
|
||||
}
|
||||
joiner.add(componentName.flattenToString());
|
||||
|
||||
SharedPreferences.Editor editor = getSharedPreferences(context).edit();
|
||||
editor.putString(KEY_TILE_SERVICE_SHOWN, joiner.toString()).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if component name existed in {@link
|
||||
* AccessibilityQuickSettingUtils#KEY_TILE_SERVICE_SHOWN} string in {@link SharedPreferences}.
|
||||
*
|
||||
* @param context The current context.
|
||||
* @param componentName The component name that need to be checked existed in SharedPreferences.
|
||||
* @return {@code true} if componentName existed in SharedPreferences.
|
||||
*/
|
||||
public static boolean hasValueInSharedPreferences(Context context,
|
||||
@NonNull ComponentName componentName) {
|
||||
final String targetString = getFromSharedPreferences(context);
|
||||
return hasValueInSharedPreferences(targetString, componentName);
|
||||
}
|
||||
|
||||
private static boolean hasValueInSharedPreferences(String targetString,
|
||||
@NonNull ComponentName componentName) {
|
||||
if (TextUtils.isEmpty(targetString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sStringColonSplitter.setString(targetString);
|
||||
|
||||
while (sStringColonSplitter.hasNext()) {
|
||||
final String name = sStringColonSplitter.next();
|
||||
if (TextUtils.equals(componentName.flattenToString(), name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getFromSharedPreferences(Context context) {
|
||||
return getSharedPreferences(context).getString(KEY_TILE_SERVICE_SHOWN, "");
|
||||
}
|
||||
|
||||
private static SharedPreferences getSharedPreferences(Context context) {
|
||||
return context.getSharedPreferences(ACCESSIBILITY_PERF, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private AccessibilityQuickSettingUtils(){}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibility;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
import com.android.settingslib.PrimarySwitchPreference;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnCreate;
|
||||
import com.android.settingslib.core.lifecycle.events.OnDestroy;
|
||||
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
|
||||
|
||||
/** PrimarySwitchPreferenceController that shows quick settings tooltip on first use. */
|
||||
public abstract class AccessibilityQuickSettingsPrimarySwitchPreferenceController
|
||||
extends TogglePreferenceController
|
||||
implements LifecycleObserver, OnCreate, OnDestroy, OnSaveInstanceState {
|
||||
private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
|
||||
private final Handler mHandler;
|
||||
private PrimarySwitchPreference mPreference;
|
||||
private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
|
||||
private boolean mNeedsQSTooltipReshow = false;
|
||||
|
||||
/** Returns the accessibility tile component name. */
|
||||
abstract ComponentName getTileComponentName();
|
||||
|
||||
/** Returns the accessibility tile tooltip content. */
|
||||
abstract CharSequence getTileTooltipContent();
|
||||
|
||||
public AccessibilityQuickSettingsPrimarySwitchPreferenceController(Context context,
|
||||
String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mHandler = new Handler(context.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// Restore the tooltip.
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
|
||||
mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
|
||||
if (isTooltipWindowShowing) {
|
||||
mTooltipWindow.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
|
||||
if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
|
||||
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
if (mNeedsQSTooltipReshow) {
|
||||
mHandler.post(this::showQuickSettingsTooltipIfNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChecked(boolean isChecked) {
|
||||
if (isChecked) {
|
||||
showQuickSettingsTooltipIfNeeded();
|
||||
}
|
||||
return isChecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSliceHighlightMenuRes() {
|
||||
return R.string.menu_key_accessibility;
|
||||
}
|
||||
|
||||
private void showQuickSettingsTooltipIfNeeded() {
|
||||
if (mPreference == null) {
|
||||
// Returns if no preference found by slice highlight menu.
|
||||
return;
|
||||
}
|
||||
|
||||
final ComponentName tileComponentName = getTileComponentName();
|
||||
if (tileComponentName == null) {
|
||||
// Returns if no tile service assigned.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
|
||||
mContext, tileComponentName)) {
|
||||
// Returns if quick settings tooltip only show once.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO (287728819): Move tooltip showing to SystemUI
|
||||
// Since the lifecycle of controller is independent of that of the preference, doing
|
||||
// null check on switch is a temporary solution for the case that switch view
|
||||
// is not ready when we would like to show the tooltip. If the switch is not ready,
|
||||
// we give up showing the tooltip and also do not reshow it in the future.
|
||||
if (mPreference.getSwitch() != null) {
|
||||
mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
|
||||
mTooltipWindow.setup(getTileTooltipContent(),
|
||||
R.drawable.accessibility_auto_added_qs_tooltip_illustration);
|
||||
mTooltipWindow.showAtTopCenter(mPreference.getSwitch());
|
||||
}
|
||||
AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName);
|
||||
mNeedsQSTooltipReshow = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.AccessibilityDelegate;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* UI container for the accessibility quick settings tooltip.
|
||||
*
|
||||
* <p> The popup window shows the information about the operation of the quick settings. In
|
||||
* addition, the arrow is pointing to the top center of the device to display one-off menu within
|
||||
* {@code mCloseDelayTimeMillis} time.</p>
|
||||
*/
|
||||
public class AccessibilityQuickSettingsTooltipWindow extends PopupWindow {
|
||||
|
||||
private final Context mContext;
|
||||
private Handler mHandler;
|
||||
private long mCloseDelayTimeMillis;
|
||||
|
||||
public AccessibilityQuickSettingsTooltipWindow(Context context) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
final AccessibilityAction clickAction = new AccessibilityAction(
|
||||
AccessibilityNodeInfo.ACTION_CLICK,
|
||||
mContext.getString(R.string.accessibility_quick_settings_tooltip_dismiss));
|
||||
info.addAction(clickAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||
if (action == AccessibilityNodeInfo.ACTION_CLICK) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return super.performAccessibilityAction(host, action, args);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Sets up {@link #AccessibilityQuickSettingsTooltipWindow}'s layout and content.
|
||||
*
|
||||
* @param text text to be displayed
|
||||
* @param imageResId the resource ID of the image drawable
|
||||
*/
|
||||
public void setup(CharSequence text, @DrawableRes int imageResId) {
|
||||
this.setup(text, imageResId, /* closeDelayTimeMillis= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up {@link #AccessibilityQuickSettingsTooltipWindow}'s layout and content.
|
||||
*
|
||||
* <p> The system will attempt to close popup window to the target duration of the threads if
|
||||
* close delay time is positive number. </p>
|
||||
*
|
||||
* @param text text to be displayed
|
||||
* @param imageResId the resource ID of the image drawable
|
||||
* @param closeDelayTimeMillis how long the popup window be auto-closed
|
||||
*/
|
||||
public void setup(CharSequence text, @DrawableRes int imageResId, long closeDelayTimeMillis) {
|
||||
this.mCloseDelayTimeMillis = closeDelayTimeMillis;
|
||||
|
||||
setBackgroundDrawable(new ColorDrawable(mContext.getColor(android.R.color.transparent)));
|
||||
final LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
|
||||
final View popupView =
|
||||
inflater.inflate(R.layout.accessibility_qs_tooltip, /* root= */ null);
|
||||
popupView.setFocusable(/* focusable= */ true);
|
||||
popupView.setAccessibilityDelegate(mAccessibilityDelegate);
|
||||
setContentView(popupView);
|
||||
|
||||
final ImageView imageView = getContentView().findViewById(R.id.qs_illustration);
|
||||
imageView.setImageResource(imageResId);
|
||||
final TextView textView = getContentView().findViewById(R.id.qs_content);
|
||||
textView.setText(text);
|
||||
setWidth(getWindowWidthWith(textView));
|
||||
setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
setFocusable(/* focusable= */ true);
|
||||
setOutsideTouchable(/* touchable= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the content view in a popup window at the top and center position.
|
||||
*
|
||||
* @param targetView a target view to get the {@link View#getWindowToken()} token from.
|
||||
*/
|
||||
public void showAtTopCenter(View targetView) {
|
||||
showAtLocation(targetView, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the popup window.
|
||||
*
|
||||
* <p> Remove any pending posts of callbacks and sent messages for closing popup window. </p>
|
||||
*/
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
if (mHandler != null) {
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the content view in a popup window at the specified location.
|
||||
*
|
||||
* <p> The system will attempt to close popup window to the target duration of the threads if
|
||||
* close delay time is positive number. </p>
|
||||
*
|
||||
* @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
|
||||
* @param gravity the gravity which controls the placement of the popup window
|
||||
* @param x the popup's x location offset
|
||||
* @param y the popup's y location offset
|
||||
*/
|
||||
@Override
|
||||
public void showAtLocation(View parent, int gravity, int x, int y) {
|
||||
super.showAtLocation(parent, gravity, x, y);
|
||||
scheduleAutoCloseAction();
|
||||
}
|
||||
|
||||
private void scheduleAutoCloseAction() {
|
||||
if (mCloseDelayTimeMillis <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHandler == null) {
|
||||
mHandler = new Handler(mContext.getMainLooper());
|
||||
}
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
mHandler.postDelayed(this::dismiss, mCloseDelayTimeMillis);
|
||||
}
|
||||
|
||||
private int getWindowWidthWith(TextView textView) {
|
||||
final int availableWindowWidth = getAvailableWindowWidth();
|
||||
final int widthSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(availableWindowWidth, View.MeasureSpec.AT_MOST);
|
||||
final int heightSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
textView.measure(widthSpec, heightSpec);
|
||||
return textView.getMeasuredWidth();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getAvailableWindowWidth() {
|
||||
final Resources res = mContext.getResources();
|
||||
final int padding = res.getDimensionPixelSize(R.dimen.accessibility_qs_tooltip_margin);
|
||||
final int screenWidth = res.getDisplayMetrics().widthPixels;
|
||||
return screenWidth - padding * 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provider for Accessibility Search related features.
|
||||
*/
|
||||
public interface AccessibilitySearchFeatureProvider {
|
||||
|
||||
/**
|
||||
* Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
|
||||
*
|
||||
* @param context a valid context {@link Context} instance
|
||||
* @return a list of {@link SearchIndexableRaw} references. Can be null.
|
||||
*/
|
||||
List<SearchIndexableRaw> getSearchIndexableRawData(Context context);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provider implementation for Accessibility Search related features.
|
||||
*/
|
||||
public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
|
||||
|
||||
@Override
|
||||
public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.accessibility;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.BidiFormatter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for creating the dialog that asks users for explicit permission for an
|
||||
* accessibility service to access user data before the service is enabled
|
||||
*/
|
||||
public class AccessibilityServiceWarning {
|
||||
private static final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
|
||||
// Filter obscured touches by consuming them.
|
||||
if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
|
||||
|| ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* The interface to execute the uninstallation action.
|
||||
*/
|
||||
interface UninstallActionPerformer {
|
||||
void uninstallPackage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Dialog} to be shown to confirm that they want to enable a service.
|
||||
* @deprecated Use {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning}
|
||||
*/
|
||||
@Deprecated
|
||||
public static Dialog createCapabilitiesDialog(@NonNull Context context,
|
||||
@NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener,
|
||||
@NonNull UninstallActionPerformer performer) {
|
||||
final AlertDialog ad = new AlertDialog.Builder(context)
|
||||
.setView(createEnableDialogContentView(context, info, listener, performer))
|
||||
.create();
|
||||
|
||||
Window window = ad.getWindow();
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
window.setAttributes(params);
|
||||
ad.create();
|
||||
ad.setCanceledOnTouchOutside(true);
|
||||
|
||||
return ad;
|
||||
}
|
||||
|
||||
private static View createEnableDialogContentView(Context context,
|
||||
@NonNull AccessibilityServiceInfo info, View.OnClickListener listener,
|
||||
UninstallActionPerformer performer) {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
|
||||
null);
|
||||
|
||||
final Drawable icon;
|
||||
if (info.getResolveInfo().getIconResource() == 0) {
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_accessibility_generic);
|
||||
} else {
|
||||
icon = info.getResolveInfo().loadIcon(context.getPackageManager());
|
||||
}
|
||||
|
||||
ImageView permissionDialogIcon = content.findViewById(
|
||||
R.id.permissionDialog_icon);
|
||||
permissionDialogIcon.setImageDrawable(icon);
|
||||
|
||||
TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_title);
|
||||
permissionDialogTitle.setText(context.getString(R.string.enable_service_title,
|
||||
getServiceName(context, info)));
|
||||
|
||||
Button permissionAllowButton = content.findViewById(
|
||||
R.id.permission_enable_allow_button);
|
||||
Button permissionDenyButton = content.findViewById(
|
||||
R.id.permission_enable_deny_button);
|
||||
permissionAllowButton.setOnClickListener(listener);
|
||||
permissionAllowButton.setOnTouchListener(filterTouchListener);
|
||||
permissionDenyButton.setOnClickListener(listener);
|
||||
|
||||
final Button uninstallButton = content.findViewById(
|
||||
R.id.permission_enable_uninstall_button);
|
||||
// Shows an uninstall button to help users quickly remove the non-system App due to the
|
||||
// required permissions.
|
||||
if (!AccessibilityUtil.isSystemApp(info)) {
|
||||
uninstallButton.setVisibility(View.VISIBLE);
|
||||
uninstallButton.setOnClickListener(v -> performer.uninstallPackage());
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/** Returns a {@link Dialog} to be shown to confirm that they want to disable a service. */
|
||||
public static Dialog createDisableDialog(Context context,
|
||||
AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
|
||||
CharSequence serviceName = getServiceName(context, info);
|
||||
|
||||
return new AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.disable_service_title, serviceName))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.accessibility_dialog_button_stop, listener)
|
||||
.setNegativeButton(R.string.accessibility_dialog_button_cancel, listener)
|
||||
.create();
|
||||
}
|
||||
|
||||
// Get the service name and bidi wrap it to protect from bidi side effects.
|
||||
private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
|
||||
final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
|
||||
final CharSequence label =
|
||||
info.getResolveInfo().loadLabel(context.getPackageManager());
|
||||
return BidiFormatter.getInstance(locale).unicodeWrap(label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.accessibility;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.AccessibilityShortcutInfo;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.InputDevice;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||
import com.android.internal.accessibility.util.AccessibilityUtils;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Activity with the accessibility settings. */
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public class AccessibilitySettings extends DashboardFragment implements
|
||||
InputManager.InputDeviceListener {
|
||||
|
||||
private static final String TAG = "AccessibilitySettings";
|
||||
|
||||
// Preference categories
|
||||
private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
|
||||
private static final String CATEGORY_CAPTIONS = "captions_category";
|
||||
private static final String CATEGORY_AUDIO = "audio_category";
|
||||
private static final String CATEGORY_SPEECH = "speech_category";
|
||||
private static final String CATEGORY_DISPLAY = "display_category";
|
||||
private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
|
||||
private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category";
|
||||
@VisibleForTesting
|
||||
static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
|
||||
|
||||
private static final String[] CATEGORIES = new String[]{
|
||||
CATEGORY_SCREEN_READER, CATEGORY_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY,
|
||||
CATEGORY_SPEECH, CATEGORY_INTERACTION_CONTROL,
|
||||
CATEGORY_KEYBOARD_OPTIONS, CATEGORY_DOWNLOADED_SERVICES
|
||||
};
|
||||
|
||||
// Extras passed to sub-fragments.
|
||||
static final String EXTRA_PREFERENCE_KEY = "preference_key";
|
||||
static final String EXTRA_CHECKED = "checked";
|
||||
static final String EXTRA_TITLE = "title";
|
||||
static final String EXTRA_RESOLVE_INFO = "resolve_info";
|
||||
static final String EXTRA_SUMMARY = "summary";
|
||||
static final String EXTRA_INTRO = "intro";
|
||||
static final String EXTRA_SETTINGS_TITLE = "settings_title";
|
||||
static final String EXTRA_COMPONENT_NAME = "component_name";
|
||||
static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
|
||||
static final String EXTRA_TILE_SERVICE_COMPONENT_NAME = "tile_service_component_name";
|
||||
static final String EXTRA_LAUNCHED_FROM_SUW = "from_suw";
|
||||
static final String EXTRA_ANIMATED_IMAGE_RES = "animated_image_res";
|
||||
static final String EXTRA_HTML_DESCRIPTION = "html_description";
|
||||
static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool";
|
||||
static final String EXTRA_METRICS_CATEGORY = "metrics_category";
|
||||
|
||||
// Timeout before we update the services if packages are added/removed
|
||||
// since the AccessibilityManagerService has to do that processing first
|
||||
// to generate the AccessibilityServiceInfo we need for proper
|
||||
// presentation.
|
||||
private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
private final Runnable mUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (getActivity() != null) {
|
||||
onContentChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
|
||||
@Override
|
||||
public void onPackageAdded(String packageName, int uid) {
|
||||
sendUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageModified(@NonNull String packageName) {
|
||||
sendUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageAppeared(String packageName, int reason) {
|
||||
sendUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageDisappeared(String packageName, int reason) {
|
||||
sendUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
sendUpdate();
|
||||
}
|
||||
|
||||
private void sendUpdate() {
|
||||
mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS);
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
final AccessibilitySettingsContentObserver mSettingsContentObserver;
|
||||
|
||||
private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
|
||||
new ArrayMap<>();
|
||||
@VisibleForTesting
|
||||
final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap =
|
||||
new ArrayMap<>();
|
||||
private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
|
||||
new ArrayMap<>();
|
||||
|
||||
private boolean mNeedPreferencesUpdate = false;
|
||||
private boolean mIsForeground = true;
|
||||
|
||||
public AccessibilitySettings() {
|
||||
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
|
||||
final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features =
|
||||
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
|
||||
final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
|
||||
for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) {
|
||||
final String key = feature.getSettingKey();
|
||||
if (key != null) {
|
||||
shortcutFeatureKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Observe changes from accessibility selection menu
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_STICKY_KEYS);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SLOW_KEYS);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS);
|
||||
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
|
||||
mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys,
|
||||
key -> onContentChanged());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCESSIBILITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return R.string.help_uri_accessibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
use(AccessibilityHearingAidPreferenceController.class)
|
||||
.setFragmentManager(getFragmentManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
initializeAllPreferences();
|
||||
updateAllPreferences();
|
||||
registerContentMonitors();
|
||||
registerInputDeviceListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateAllPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (mNeedPreferencesUpdate) {
|
||||
updateAllPreferences();
|
||||
mNeedPreferencesUpdate = false;
|
||||
}
|
||||
mIsForeground = true;
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mIsForeground = false;
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
unregisterContentMonitors();
|
||||
unRegisterInputDeviceListener();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the summary for the current state of this accessibilityService.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param info The accessibilityService's info
|
||||
* @param serviceEnabled Whether the accessibility service is enabled.
|
||||
* @return The service summary
|
||||
*/
|
||||
public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
|
||||
boolean serviceEnabled) {
|
||||
if (serviceEnabled && info.crashed) {
|
||||
return context.getText(R.string.accessibility_summary_state_stopped);
|
||||
}
|
||||
|
||||
final CharSequence serviceState;
|
||||
final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info);
|
||||
if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE_TOGGLE) {
|
||||
final ComponentName componentName = new ComponentName(
|
||||
info.getResolveInfo().serviceInfo.packageName,
|
||||
info.getResolveInfo().serviceInfo.name);
|
||||
final boolean shortcutEnabled = AccessibilityUtil.getUserShortcutTypesFromSettings(
|
||||
context, componentName) != AccessibilityUtil.UserShortcutType.EMPTY;
|
||||
serviceState = shortcutEnabled
|
||||
? context.getText(R.string.accessibility_summary_shortcut_enabled)
|
||||
: context.getText(R.string.generic_accessibility_feature_shortcut_off);
|
||||
} else {
|
||||
serviceState = serviceEnabled
|
||||
? context.getText(R.string.generic_accessibility_service_on)
|
||||
: context.getText(R.string.generic_accessibility_service_off);
|
||||
}
|
||||
|
||||
final CharSequence serviceSummary = info.loadSummary(context.getPackageManager());
|
||||
final String stateSummaryCombo = context.getString(
|
||||
R.string.preference_summary_default_combination,
|
||||
serviceState, serviceSummary);
|
||||
|
||||
return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description for the current state of this accessibilityService.
|
||||
*
|
||||
* @param context A valid context
|
||||
* @param info The accessibilityService's info
|
||||
* @param serviceEnabled Whether the accessibility service is enabled.
|
||||
* @return The service description
|
||||
*/
|
||||
public static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info,
|
||||
boolean serviceEnabled) {
|
||||
if (serviceEnabled && info.crashed) {
|
||||
return context.getText(R.string.accessibility_description_state_stopped);
|
||||
}
|
||||
|
||||
return info.loadDescription(context.getPackageManager());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onContentChanged() {
|
||||
// If the fragment is visible then update preferences immediately, else set the flag then
|
||||
// wait for the fragment to show up to update preferences.
|
||||
if (mIsForeground) {
|
||||
updateAllPreferences();
|
||||
} else {
|
||||
mNeedPreferencesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeAllPreferences() {
|
||||
for (int i = 0; i < CATEGORIES.length; i++) {
|
||||
PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
|
||||
mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateAllPreferences() {
|
||||
updateServicePreferences();
|
||||
updatePreferencesState();
|
||||
updateSystemPreferences();
|
||||
}
|
||||
|
||||
private void registerContentMonitors() {
|
||||
final Context context = getActivity();
|
||||
|
||||
mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */
|
||||
false);
|
||||
mSettingsContentObserver.register(getContentResolver());
|
||||
}
|
||||
|
||||
private void registerInputDeviceListener() {
|
||||
InputManager mIm = getSystemService(InputManager.class);
|
||||
if (mIm == null) {
|
||||
return;
|
||||
}
|
||||
mIm.registerInputDeviceListener(this, null);
|
||||
}
|
||||
|
||||
private void unRegisterInputDeviceListener() {
|
||||
InputManager mIm = getSystemService(InputManager.class);
|
||||
if (mIm == null) {
|
||||
return;
|
||||
}
|
||||
mIm.unregisterInputDeviceListener(this);
|
||||
}
|
||||
|
||||
private void unregisterContentMonitors() {
|
||||
mSettingsPackageMonitor.unregister();
|
||||
mSettingsContentObserver.unregister(getContentResolver());
|
||||
}
|
||||
|
||||
protected void updateServicePreferences() {
|
||||
// Since services category is auto generated we have to do a pass
|
||||
// to generate it since services can come and go and then based on
|
||||
// the global accessibility state to decided whether it is enabled.
|
||||
final ArrayList<Preference> servicePreferences =
|
||||
new ArrayList<>(mServicePreferenceToPreferenceCategoryMap.keySet());
|
||||
for (int i = 0; i < servicePreferences.size(); i++) {
|
||||
Preference service = servicePreferences.get(i);
|
||||
PreferenceCategory category = mServicePreferenceToPreferenceCategoryMap.get(service);
|
||||
category.removePreference(service);
|
||||
}
|
||||
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER,
|
||||
R.array.config_preinstalled_screen_reader_services);
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_CAPTIONS,
|
||||
R.array.config_preinstalled_captions_services);
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_AUDIO,
|
||||
R.array.config_preinstalled_audio_services);
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
|
||||
R.array.config_preinstalled_display_services);
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_SPEECH,
|
||||
R.array.config_preinstalled_speech_services);
|
||||
initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
|
||||
R.array.config_preinstalled_interaction_control_services);
|
||||
|
||||
// ACCESSIBILITY_MENU_IN_SYSTEM is a default pre-bundled interaction control service.
|
||||
// If the device opts out of including this service then this is a no-op.
|
||||
mPreBundledServiceComponentToCategoryMap.put(
|
||||
AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
|
||||
mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL));
|
||||
|
||||
final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
|
||||
getPrefContext());
|
||||
|
||||
final PreferenceCategory downloadedServicesCategory =
|
||||
mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
|
||||
|
||||
for (int i = 0, count = preferenceList.size(); i < count; ++i) {
|
||||
final RestrictedPreference preference = preferenceList.get(i);
|
||||
final ComponentName componentName = preference.getExtras().getParcelable(
|
||||
EXTRA_COMPONENT_NAME);
|
||||
PreferenceCategory prefCategory = downloadedServicesCategory;
|
||||
// Set the appropriate category if the service comes pre-installed.
|
||||
if (mPreBundledServiceComponentToCategoryMap.containsKey(componentName)) {
|
||||
prefCategory = mPreBundledServiceComponentToCategoryMap.get(componentName);
|
||||
}
|
||||
prefCategory.addPreference(preference);
|
||||
mServicePreferenceToPreferenceCategoryMap.put(preference, prefCategory);
|
||||
}
|
||||
|
||||
// Update the order of all the category according to the order defined in xml file.
|
||||
updateCategoryOrderFromArray(CATEGORY_SCREEN_READER,
|
||||
R.array.config_order_screen_reader_services);
|
||||
updateCategoryOrderFromArray(CATEGORY_CAPTIONS,
|
||||
R.array.config_order_captions_services);
|
||||
updateCategoryOrderFromArray(CATEGORY_AUDIO,
|
||||
R.array.config_order_audio_services);
|
||||
updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL,
|
||||
R.array.config_order_interaction_control_services);
|
||||
updateCategoryOrderFromArray(CATEGORY_DISPLAY,
|
||||
R.array.config_order_display_services);
|
||||
updateCategoryOrderFromArray(CATEGORY_SPEECH,
|
||||
R.array.config_order_speech_services);
|
||||
|
||||
// Need to check each time when updateServicePreferences() called.
|
||||
if (downloadedServicesCategory.getPreferenceCount() == 0) {
|
||||
getPreferenceScreen().removePreference(downloadedServicesCategory);
|
||||
} else {
|
||||
getPreferenceScreen().addPreference(downloadedServicesCategory);
|
||||
}
|
||||
|
||||
// Hide category if it is empty.
|
||||
updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER);
|
||||
updatePreferenceCategoryVisibility(CATEGORY_SPEECH);
|
||||
updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS);
|
||||
}
|
||||
|
||||
private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
|
||||
final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context);
|
||||
final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
|
||||
|
||||
final List<AccessibilityShortcutInfo> installedShortcutList =
|
||||
a11yManager.getInstalledAccessibilityShortcutListAsUser(context,
|
||||
UserHandle.myUserId());
|
||||
|
||||
// Remove duplicate item here, new a ArrayList to copy unmodifiable list result
|
||||
// (getInstalledAccessibilityServiceList).
|
||||
final List<AccessibilityServiceInfo> installedServiceList = new ArrayList<>(
|
||||
a11yManager.getInstalledAccessibilityServiceList());
|
||||
installedServiceList.removeIf(
|
||||
target -> containsTargetNameInList(installedShortcutList, target));
|
||||
|
||||
final List<RestrictedPreference> activityList =
|
||||
preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
|
||||
|
||||
final List<RestrictedPreference> serviceList =
|
||||
preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);
|
||||
|
||||
final List<RestrictedPreference> preferenceList = new ArrayList<>();
|
||||
preferenceList.addAll(activityList);
|
||||
preferenceList.addAll(serviceList);
|
||||
|
||||
return preferenceList;
|
||||
}
|
||||
|
||||
private boolean containsTargetNameInList(List<AccessibilityShortcutInfo> shortcutInfos,
|
||||
AccessibilityServiceInfo targetServiceInfo) {
|
||||
final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo;
|
||||
final String servicePackageName = serviceInfo.packageName;
|
||||
final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager());
|
||||
|
||||
for (int i = 0, count = shortcutInfos.size(); i < count; ++i) {
|
||||
final ActivityInfo activityInfo = shortcutInfos.get(i).getActivityInfo();
|
||||
final String activityPackageName = activityInfo.packageName;
|
||||
final CharSequence activityLabel = activityInfo.loadLabel(getPackageManager());
|
||||
if (servicePackageName.equals(activityPackageName)
|
||||
&& serviceLabel.equals(activityLabel)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializePreBundledServicesMapFromArray(String categoryKey, int key) {
|
||||
String[] services = getResources().getStringArray(key);
|
||||
PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
|
||||
for (int i = 0; i < services.length; i++) {
|
||||
ComponentName component = ComponentName.unflattenFromString(services[i]);
|
||||
mPreBundledServiceComponentToCategoryMap.put(component, category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the order of preferences in the category by matching their preference
|
||||
* key with the string array of preference order which is defined in the xml.
|
||||
*
|
||||
* @param categoryKey The key of the category need to update the order
|
||||
* @param key The key of the string array which defines the order of category
|
||||
*/
|
||||
private void updateCategoryOrderFromArray(String categoryKey, int key) {
|
||||
String[] services = getResources().getStringArray(key);
|
||||
PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
|
||||
int preferenceCount = category.getPreferenceCount();
|
||||
int serviceLength = services.length;
|
||||
for (int preferenceIndex = 0; preferenceIndex < preferenceCount; preferenceIndex++) {
|
||||
for (int serviceIndex = 0; serviceIndex < serviceLength; serviceIndex++) {
|
||||
if (category.getPreference(preferenceIndex).getKey()
|
||||
.equals(services[serviceIndex])) {
|
||||
category.getPreference(preferenceIndex).setOrder(serviceIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility of a category according to its child preference count.
|
||||
*
|
||||
* @param categoryKey The key of the category which needs to check
|
||||
*/
|
||||
private void updatePreferenceCategoryVisibility(String categoryKey) {
|
||||
final PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
|
||||
category.setVisible(category.getPreferenceCount() != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates preferences related to system configurations.
|
||||
*/
|
||||
protected void updateSystemPreferences() {
|
||||
updateKeyboardPreferencesVisibility();
|
||||
}
|
||||
|
||||
private void updatePreferencesState() {
|
||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
||||
getPreferenceControllers().forEach(controllers::addAll);
|
||||
controllers.forEach(controller -> controller.updateState(
|
||||
findPreference(controller.getPreferenceKey())));
|
||||
}
|
||||
|
||||
private void updateKeyboardPreferencesVisibility() {
|
||||
if (!mCategoryToPrefCategoryMap.containsKey(CATEGORY_KEYBOARD_OPTIONS)) {
|
||||
return;
|
||||
}
|
||||
boolean isVisible = isAnyHardKeyboardsExist()
|
||||
&& isAnyKeyboardPreferenceAvailable();
|
||||
mCategoryToPrefCategoryMap.get(CATEGORY_KEYBOARD_OPTIONS).setVisible(
|
||||
isVisible);
|
||||
if (isVisible) {
|
||||
//set summary here.
|
||||
findPreference(KeyboardBounceKeyPreferenceController.PREF_KEY).setSummary(
|
||||
getContext().getString(R.string.bounce_keys_summary,
|
||||
PhysicalKeyboardFragment.BOUNCE_KEYS_THRESHOLD));
|
||||
findPreference(KeyboardSlowKeyPreferenceController.PREF_KEY).setSummary(
|
||||
getContext().getString(R.string.slow_keys_summary,
|
||||
PhysicalKeyboardFragment.SLOW_KEYS_THRESHOLD));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAnyHardKeyboardsExist() {
|
||||
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||
final InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAnyKeyboardPreferenceAvailable() {
|
||||
for (List<AbstractPreferenceController> controllerList : getPreferenceControllers()) {
|
||||
for (AbstractPreferenceController controller : controllerList) {
|
||||
if (controller.getPreferenceKey().equals(
|
||||
KeyboardBounceKeyPreferenceController.PREF_KEY)
|
||||
|| controller.getPreferenceKey().equals(
|
||||
KeyboardSlowKeyPreferenceController.PREF_KEY)
|
||||
|| controller.getPreferenceKey().equals(
|
||||
KeyboardStickyKeyPreferenceController.PREF_KEY)) {
|
||||
if (controller.isAvailable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(R.xml.accessibility_settings) {
|
||||
@Override
|
||||
public List<SearchIndexableRaw> getRawDataToIndex(Context context,
|
||||
boolean enabled) {
|
||||
return FeatureFactory.getFeatureFactory()
|
||||
.getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
|
||||
context);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibility;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class AccessibilitySettingsContentObserver extends ContentObserver {
|
||||
|
||||
private static final String TAG = "AccessibilitySettingsContentObserver";
|
||||
|
||||
public interface ContentObserverCallback {
|
||||
void onChange(String key);
|
||||
}
|
||||
|
||||
// Key: Preference key's uri, Value: Preference key
|
||||
private final Map<Uri, String> mUriToKey = new HashMap<>(2);
|
||||
|
||||
// Key: Collection of preference keys, Value: onChange callback for keys
|
||||
private final Map<List<String>, ContentObserverCallback> mUrisToCallback = new HashMap<>();
|
||||
|
||||
AccessibilitySettingsContentObserver(Handler handler) {
|
||||
super(handler);
|
||||
|
||||
// default key to be observed
|
||||
addDefaultKeysToMap();
|
||||
}
|
||||
|
||||
public void register(ContentResolver contentResolver) {
|
||||
for (Uri uri : mUriToKey.keySet()) {
|
||||
contentResolver.registerContentObserver(uri, false, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(ContentResolver contentResolver) {
|
||||
contentResolver.unregisterContentObserver(this);
|
||||
}
|
||||
|
||||
private void addDefaultKeysToMap() {
|
||||
addKeyToMap(Settings.Secure.ACCESSIBILITY_ENABLED);
|
||||
addKeyToMap(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
|
||||
}
|
||||
|
||||
private boolean isDefaultKey(String key) {
|
||||
return Settings.Secure.ACCESSIBILITY_ENABLED.equals(key)
|
||||
|| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(key);
|
||||
}
|
||||
|
||||
private void addKeyToMap(String key) {
|
||||
mUriToKey.put(Settings.Secure.getUriFor(key), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContentObserverCallback} is added to {@link ContentObserver} to handle the
|
||||
* onChange event triggered by the key collection of {@code keysToObserve} and the default
|
||||
* keys.
|
||||
*
|
||||
* Note: The following key are default to be observed.
|
||||
* {@link Settings.Secure.ACCESSIBILITY_ENABLED}
|
||||
* {@link Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES}
|
||||
*
|
||||
* @param keysToObserve A collection of keys which are going to be observed.
|
||||
* @param observerCallback A callback which is used to handle the onChange event triggered
|
||||
* by the key collection of {@code keysToObserve}.
|
||||
*/
|
||||
public void registerKeysToObserverCallback(List<String> keysToObserve,
|
||||
ContentObserverCallback observerCallback) {
|
||||
|
||||
for (String key: keysToObserve) {
|
||||
addKeyToMap(key);
|
||||
}
|
||||
|
||||
mUrisToCallback.put(keysToObserve, observerCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContentObserverCallback} is added to {@link ContentObserver} to handle the
|
||||
* onChange event triggered by the default keys.
|
||||
*
|
||||
* Note: The following key are default to be observed.
|
||||
* {@link Settings.Secure.ACCESSIBILITY_ENABLED}
|
||||
* {@link Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES}
|
||||
*
|
||||
* @param observerCallback A callback which is used to handle the onChange event triggered
|
||||
* * by the key collection of {@code keysToObserve}.
|
||||
*/
|
||||
public void registerObserverCallback(ContentObserverCallback observerCallback) {
|
||||
mUrisToCallback.put(Collections.emptyList(), observerCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onChange(boolean selfChange, Uri uri) {
|
||||
|
||||
final String key = mUriToKey.get(uri);
|
||||
|
||||
if (key == null) {
|
||||
Log.w(TAG, "AccessibilitySettingsContentObserver can not find the key for "
|
||||
+ "uri: " + uri);
|
||||
return;
|
||||
}
|
||||
|
||||
for (List<String> keys : mUrisToCallback.keySet()) {
|
||||
final boolean isDefaultKey = isDefaultKey(key);
|
||||
if (isDefaultKey || keys.contains(key)) {
|
||||
mUrisToCallback.get(keys).onChange(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.accessibility;
|
||||
|
||||
import static android.app.Activity.RESULT_CANCELED;
|
||||
|
||||
import static com.android.settings.Utils.getAdaptiveIcon;
|
||||
import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Activity with the accessibility settings specific to Setup Wizard.
|
||||
*/
|
||||
public class AccessibilitySettingsForSetupWizard extends DashboardFragment
|
||||
implements Preference.OnPreferenceChangeListener {
|
||||
private static final String TAG = "AccessibilitySettingsForSetupWizard";
|
||||
|
||||
// Preferences.
|
||||
private static final String DISPLAY_MAGNIFICATION_PREFERENCE =
|
||||
"screen_magnification_preference";
|
||||
private static final String SCREEN_READER_PREFERENCE = "screen_reader_preference";
|
||||
private static final String SELECT_TO_SPEAK_PREFERENCE = "select_to_speak_preference";
|
||||
|
||||
// Package names and service names used to identify screen reader and SelectToSpeak services.
|
||||
@VisibleForTesting
|
||||
static final String SCREEN_READER_PACKAGE_NAME = "com.google.android.marvin.talkback";
|
||||
@VisibleForTesting
|
||||
static final String SCREEN_READER_SERVICE_NAME =
|
||||
"com.google.android.marvin.talkback.TalkBackService";
|
||||
@VisibleForTesting
|
||||
static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
|
||||
@VisibleForTesting
|
||||
static final String SELECT_TO_SPEAK_SERVICE_NAME =
|
||||
"com.google.android.accessibility.selecttospeak.SelectToSpeakService";
|
||||
|
||||
// Preference controls.
|
||||
protected Preference mDisplayMagnificationPreference;
|
||||
protected RestrictedPreference mScreenReaderPreference;
|
||||
protected RestrictedPreference mSelectToSpeakPreference;
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.SUW_ACCESSIBILITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (view instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
|
||||
final String title = getContext().getString(R.string.vision_settings_title);
|
||||
final String description = getContext().getString(R.string.vision_settings_description);
|
||||
final Drawable icon = getContext().getDrawable(R.drawable.ic_accessibility_visibility);
|
||||
AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title,
|
||||
description, icon);
|
||||
|
||||
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
|
||||
AccessibilitySetupWizardUtils.setPrimaryButton(getContext(), mixin, R.string.done,
|
||||
() -> {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
if (parent instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
mDisplayMagnificationPreference = findPreference(DISPLAY_MAGNIFICATION_PREFERENCE);
|
||||
mScreenReaderPreference = findPreference(SCREEN_READER_PREFERENCE);
|
||||
mSelectToSpeakPreference = findPreference(SELECT_TO_SPEAK_PREFERENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateAccessibilityServicePreference(mScreenReaderPreference,
|
||||
SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME);
|
||||
updateAccessibilityServicePreference(mSelectToSpeakPreference,
|
||||
SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
if (mDisplayMagnificationPreference == preference) {
|
||||
Bundle extras = mDisplayMagnificationPreference.getExtras();
|
||||
extras.putBoolean(AccessibilitySettings.EXTRA_LAUNCHED_FROM_SUW, true);
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.accessibility_settings_for_setup_wizard;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns accessibility service info by given package name and service name.
|
||||
*
|
||||
* @param packageName Package of accessibility service
|
||||
* @param serviceName Class of accessibility service
|
||||
* @return {@link AccessibilityServiceInfo} instance if available, null otherwise.
|
||||
*/
|
||||
private AccessibilityServiceInfo findService(String packageName, String serviceName) {
|
||||
final AccessibilityManager manager =
|
||||
getActivity().getSystemService(AccessibilityManager.class);
|
||||
final List<AccessibilityServiceInfo> accessibilityServices =
|
||||
manager.getInstalledAccessibilityServiceList();
|
||||
for (AccessibilityServiceInfo info : accessibilityServices) {
|
||||
ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
|
||||
if (TextUtils.equals(packageName, serviceInfo.packageName)
|
||||
&& TextUtils.equals(serviceName, serviceInfo.name)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateAccessibilityServicePreference(RestrictedPreference preference,
|
||||
String packageName, String serviceName) {
|
||||
final AccessibilityServiceInfo info = findService(packageName, serviceName);
|
||||
if (info == null) {
|
||||
getPreferenceScreen().removePreference(preference);
|
||||
return;
|
||||
}
|
||||
|
||||
final ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
|
||||
final Drawable icon = info.getResolveInfo().loadIcon(getPackageManager());
|
||||
preference.setIcon(getAdaptiveIcon(getContext(), icon, Color.WHITE));
|
||||
preference.setIconSize(ICON_SIZE_MEDIUM);
|
||||
final String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
|
||||
preference.setTitle(title);
|
||||
final ComponentName componentName =
|
||||
new ComponentName(serviceInfo.packageName, serviceInfo.name);
|
||||
preference.setKey(componentName.flattenToString());
|
||||
|
||||
// Update the extras.
|
||||
final Bundle extras = preference.getExtras();
|
||||
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
|
||||
|
||||
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
|
||||
preference.getKey());
|
||||
extras.putString(AccessibilitySettings.EXTRA_TITLE, title);
|
||||
|
||||
final String description = info.loadDescription(getPackageManager());
|
||||
extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description);
|
||||
|
||||
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
|
||||
|
||||
final String htmlDescription = info.loadHtmlDescription(getPackageManager());
|
||||
extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.accessibility;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SetupWizardUtils;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.search.actionbar.SearchMenuController;
|
||||
import com.android.settings.support.actionbar.HelpResourceProvider;
|
||||
import com.android.settingslib.core.instrumentation.Instrumentable;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivity {
|
||||
|
||||
private static final String SAVE_KEY_TITLE = "activity_title";
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle savedState) {
|
||||
savedState.putCharSequence(SAVE_KEY_TITLE, getTitle());
|
||||
super.onSaveInstanceState(savedState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
setTitle(savedState.getCharSequence(SAVE_KEY_TITLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Return true, so we get notified when items in the menu are clicked.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
onBackPressed();
|
||||
|
||||
// Clear accessibility focus and let the screen reader announce the new title.
|
||||
getWindow().getDecorView()
|
||||
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
|
||||
Bundle args = pref.getExtras();
|
||||
if (args == null) {
|
||||
args = new Bundle();
|
||||
}
|
||||
args.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 0);
|
||||
args.putBoolean(SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR, false);
|
||||
new SubSettingLauncher(this)
|
||||
.setDestination(pref.getFragment())
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(caller instanceof Instrumentable
|
||||
? ((Instrumentable) caller).getMetricsCategory()
|
||||
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
|
||||
.setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
|
||||
new Bundle()))
|
||||
.launch();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
applyTheme();
|
||||
findViewById(R.id.content_parent).setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
private void applyTheme() {
|
||||
final boolean isAnySetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
if (isAnySetupWizard) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupcompat.template.Mixin;
|
||||
import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
import com.google.android.setupdesign.R;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
/** Provides utility methods to accessibility settings for Setup Wizard only. */
|
||||
public class AccessibilitySetupWizardUtils {
|
||||
|
||||
private AccessibilitySetupWizardUtils(){}
|
||||
|
||||
/**
|
||||
* Updates the {@link GlifPreferenceLayout} attributes if they have previously been initialized.
|
||||
* When the SetupWizard supports the extended partner configs, it means the material layout
|
||||
* would be applied. It should set a different padding/margin in views to align Settings style
|
||||
* for accessibility feature pages.
|
||||
*
|
||||
* @param layout The layout instance
|
||||
* @param title The text to be set as title
|
||||
* @param description The text to be set as description
|
||||
* @param icon The icon to be set
|
||||
*/
|
||||
public static void updateGlifPreferenceLayout(Context context, GlifPreferenceLayout layout,
|
||||
@Nullable CharSequence title, @Nullable CharSequence description,
|
||||
@Nullable Drawable icon) {
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
layout.setHeaderText(title);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(description)) {
|
||||
layout.setDescriptionText(description);
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
layout.setIcon(icon);
|
||||
}
|
||||
layout.setDividerInsets(Integer.MAX_VALUE, 0);
|
||||
|
||||
if (ThemeHelper.shouldApplyMaterialYouStyle(context)) {
|
||||
final LinearLayout headerLayout = layout.findManagedViewById(R.id.sud_layout_header);
|
||||
if (headerLayout != null) {
|
||||
headerLayout.setPadding(0, layout.getPaddingTop(), 0,
|
||||
layout.getPaddingBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets primary button for footer of the {@link GlifPreferenceLayout}.
|
||||
*
|
||||
* <p> This will be the initial by given material theme style.
|
||||
*
|
||||
* @param context A {@link Context}
|
||||
* @param mixin A {@link Mixin} for managing buttons.
|
||||
* @param text The {@code text} by resource.
|
||||
* @param runnable The {@link Runnable} to run.
|
||||
*/
|
||||
public static void setPrimaryButton(Context context, FooterBarMixin mixin, @StringRes int text,
|
||||
Runnable runnable) {
|
||||
mixin.setPrimaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(text)
|
||||
.setListener(l -> runnable.run())
|
||||
.setButtonType(FooterButton.ButtonType.DONE)
|
||||
.setTheme(R.style.SudGlifButton_Primary)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets secondary button for the footer of the {@link GlifPreferenceLayout}.
|
||||
*
|
||||
* <p> This will be the initial by given material theme style.
|
||||
*
|
||||
* @param context A {@link Context}
|
||||
* @param mixin A {@link Mixin} for managing buttons.
|
||||
* @param text The {@code text} by resource.
|
||||
* @param runnable The {@link Runnable} to run.
|
||||
*/
|
||||
public static void setSecondaryButton(Context context, FooterBarMixin mixin,
|
||||
@StringRes int text, Runnable runnable) {
|
||||
mixin.setSecondaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(text)
|
||||
.setListener(l -> runnable.run())
|
||||
.setButtonType(FooterButton.ButtonType.CLEAR)
|
||||
.setTheme(R.style.SudGlifButton_Secondary)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user