fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View 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;
}
}
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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);
}
}

View 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);
}
}

View 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));
});
}
}

View 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);
}
}

View 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);
}
}
}
}
}

View 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();
}

View 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);
}
}

View 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;
}

View 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);
}
}
}
}

View 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;
}
}
}

View File

@@ -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());
}
}

View 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);
}

View 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);
}
};
}

View 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();
}
}
}

View 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)

View 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();
}
};
}

View 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();
}
}

View 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);
}
}

View 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);
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View File

@@ -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();
}
}

View 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];
}
};
}
}

View 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();
}
}

View 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);
}

View 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;
}
}

View File

@@ -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()
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View 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;
}
}
}

View 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 */ }
}

View 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;
}
}

View 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)
}
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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)));
}
}

View File

@@ -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
}
}

View 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;
}
}

View 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;
*
* &#064;Override
* public void onStart() {
* super.onStart();
* Bundle args = ...; // optional args
* mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR,
* SidecarFragment.class, args);
* }
*
* &#064;Override
* public void onResume() {
* mSomeSidecar.addListener(this);
* }
*
* &#064;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;
}
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}
}
}

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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(){}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}

View 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.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);
}
}
}

View File

@@ -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