Revamp SD card UX

Improve the SD card setup flow so that each formatting option is provided in a separate screen with clear description and illustration.Also make sure that guest users do not have any access to formatting options.

Test: Manual testing on Pixel Device
Screenshot1: https://screenshot.googleplex.com/WJwyxFkBtbSfZmN.png
Screenshot2: https://screenshot.googleplex.com/3oBcrrgRsKTxNPC.png

Bug: 201252175
Change-Id: I77df55c40fd99cabcfc6128084be035bb5b19531

Change-Id: I757abc6076fcc8f467d8faed9f090bcdd5774ff3
(cherry picked from commit 9b432d54a6)
Merged-In: I757abc6076fcc8f467d8faed9f090bcdd5774ff3
This commit is contained in:
sayakiitg
2022-01-12 10:45:33 +00:00
committed by Sayak Dutta
parent aa3262857c
commit efdedcdc15
22 changed files with 658 additions and 489 deletions

View File

@@ -19,6 +19,7 @@ package com.android.settings.deviceinfo;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserManager;
@@ -57,15 +58,34 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
private String mVolumeId;
private VolumeInfo mVolume;
private final View.OnClickListener mUnmountListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
new UnmountTask(getActivity(), mVolume).execute();
}
};
private DiskInfo mDisk;
private UsageProgressBarPreference mSummary;
private Preference mMount;
private Preference mFormatPublic;
private Preference mFormatPrivate;
private Button mUnmount;
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (Objects.equals(mVolume.getId(), vol.getId())) {
mVolume = vol;
update();
}
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
mVolume = mStorageManager.findVolumeById(mVolumeId);
update();
}
}
};
private boolean mIsPermittedToAdopt;
private boolean isVolumeValid() {
@@ -120,10 +140,7 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
mUnmount = new Button(getActivity());
mUnmount.setText(R.string.storage_menu_unmount);
mUnmount.setOnClickListener(mUnmountListener);
mFormatPublic = buildAction(R.string.storage_menu_format);
if (mIsPermittedToAdopt) {
mFormatPrivate = buildAction(R.string.storage_menu_format_private);
}
mFormatPublic = buildAction(R.string.storage_menu_format_option);
}
@Override
@@ -176,9 +193,6 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
mUnmount.setVisibility(View.GONE);
}
addPreference(mFormatPublic);
if (mDisk.isAdoptable() && mIsPermittedToAdopt) {
addPreference(mFormatPrivate);
}
}
private void addPreference(Preference pref) {
@@ -215,39 +229,14 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
@Override
public boolean onPreferenceTreeClick(Preference pref) {
final Intent intent = new Intent(getActivity(), StorageWizardInit.class);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
if (pref == mMount) {
new MountTask(getActivity(), mVolume).execute();
} else if (pref == mFormatPublic) {
StorageWizardFormatConfirm.showPublic(getActivity(), mDisk.getId());
} else if (pref == mFormatPrivate) {
StorageWizardFormatConfirm.showPrivate(getActivity(), mDisk.getId());
startActivity(intent);
}
return super.onPreferenceTreeClick(pref);
}
private final View.OnClickListener mUnmountListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
new UnmountTask(getActivity(), mVolume).execute();
}
};
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (Objects.equals(mVolume.getId(), vol.getId())) {
mVolume = vol;
update();
}
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
mVolume = mStorageManager.findVolumeById(mVolumeId);
update();
}
}
};
}
}

View File

@@ -41,17 +41,19 @@ import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settingslib.Utils;
import com.android.settings.SetupWizardUtils;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.util.ThemeHelper;
import java.text.NumberFormat;
import java.util.List;
import java.util.Objects;
public abstract class StorageWizardBase extends FragmentActivity {
private static final String TAG = "StorageWizardBase";
protected static final String EXTRA_FORMAT_FORGET_UUID = "format_forget_uuid";
@@ -70,6 +72,8 @@ public abstract class StorageWizardBase extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedInstanceState);
mStorage = getSystemService(StorageManager.class);
@@ -97,20 +101,20 @@ public abstract class StorageWizardBase extends FragmentActivity {
mFooterBarMixin = getGlifLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_back)
.setListener(this::onNavigateBack)
.setButtonType(FooterButton.ButtonType.OTHER)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
new FooterButton.Builder(this)
.setText(R.string.wizard_back)
.setListener(this::onNavigateBack)
.setButtonType(FooterButton.ButtonType.OTHER)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
mFooterBarMixin.setPrimaryButton(
new FooterButton.Builder(this)
.setText(R.string.wizard_next)
.setListener(this::onNavigateNext)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build()
new FooterButton.Builder(this)
.setText(R.string.wizard_next)
.setListener(this::onNavigateNext)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
mBack = mFooterBarMixin.getSecondaryButton();
mNext = mFooterBarMixin.getPrimaryButton();
@@ -149,7 +153,7 @@ public abstract class StorageWizardBase extends FragmentActivity {
protected void setCurrentProgress(int progress) {
getProgressBar().setProgress(progress);
((TextView) requireViewById(R.id.storage_wizard_progress_summary)).setText(
NumberFormat.getPercentInstance().format((double) progress / 100));
NumberFormat.getPercentInstance().format((double) progress / 100));
}
protected void setHeaderText(int resId, CharSequence... args) {
@@ -167,14 +171,14 @@ public abstract class StorageWizardBase extends FragmentActivity {
protected void setAuxChecklist() {
final FrameLayout aux = requireViewById(R.id.storage_wizard_aux);
aux.addView(LayoutInflater.from(aux.getContext())
.inflate(R.layout.storage_wizard_checklist, aux, false));
.inflate(R.layout.storage_wizard_checklist, aux, false));
aux.setVisibility(View.VISIBLE);
// Customize string based on disk
((TextView) aux.requireViewById(R.id.storage_wizard_migrate_v2_checklist_media))
.setText(TextUtils.expandTemplate(
getText(R.string.storage_wizard_migrate_v2_checklist_media),
getDiskShortDescription()));
.setText(TextUtils.expandTemplate(
getText(R.string.storage_wizard_migrate_v2_checklist_media),
getDiskShortDescription()));
}
protected void setBackButtonText(int resId, CharSequence... args) {
@@ -198,7 +202,6 @@ public abstract class StorageWizardBase extends FragmentActivity {
protected void setIcon(int resId) {
final GlifLayout layout = getGlifLayout();
final Drawable icon = getDrawable(resId).mutate();
icon.setTintList(Utils.getColorAccent(layout.getContext()));
layout.setIcon(icon);
}
@@ -250,14 +253,14 @@ public abstract class StorageWizardBase extends FragmentActivity {
final List<VolumeInfo> vols = mStorage.getVolumes();
for (VolumeInfo vol : vols) {
if (Objects.equals(mDisk.getId(), vol.getDiskId()) && (vol.getType() == type)
&& (vol.getState() == VolumeInfo.STATE_MOUNTED)) {
&& (vol.getState() == VolumeInfo.STATE_MOUNTED)) {
return vol;
}
}
if (--attempts > 0) {
Log.w(TAG, "Missing mounted volume of type " + type + " hosted by disk "
+ mDisk.getId() + "; trying again");
+ mDisk.getId() + "; trying again");
SystemClock.sleep(250);
} else {
return null;
@@ -265,7 +268,8 @@ public abstract class StorageWizardBase extends FragmentActivity {
}
}
protected @NonNull CharSequence getDiskDescription() {
protected @NonNull
CharSequence getDiskDescription() {
if (mDisk != null) {
return mDisk.getDescription();
} else if (mVolume != null) {
@@ -275,7 +279,8 @@ public abstract class StorageWizardBase extends FragmentActivity {
}
}
protected @NonNull CharSequence getDiskShortDescription() {
protected @NonNull
CharSequence getDiskShortDescription() {
if (mDisk != null) {
return mDisk.getShortDescription();
} else if (mVolume != null) {
@@ -294,4 +299,4 @@ public abstract class StorageWizardBase extends FragmentActivity {
}
}
};
}
}

View File

@@ -84,15 +84,23 @@ public class StorageWizardFormatConfirm extends InstrumentedDialogFragment {
builder.setTitle(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_title),
disk.getShortDescription()));
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_body),
if (formatPrivate) {
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_body),
disk.getDescription(),
disk.getShortDescription(),
disk.getShortDescription()));
} else {
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_body_external),
disk.getDescription(),
disk.getShortDescription(),
disk.getShortDescription()));
}
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(
TextUtils.expandTemplate(getText(R.string.storage_wizard_format_confirm_v2_action),
TextUtils.expandTemplate(getText(R.string.storage_menu_format_option),
disk.getShortDescription()),
(dialog, which) -> {
final Intent intent = new Intent(context, StorageWizardFormatProgress.class);
@@ -104,4 +112,4 @@ public class StorageWizardFormatConfirm extends InstrumentedDialogFragment {
return builder.create();
}
}
}

View File

@@ -18,21 +18,27 @@ package com.android.settings.deviceinfo;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
import android.os.storage.VolumeInfo;
import android.text.Html;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
public class StorageWizardInit extends StorageWizardBase {
private Button mInternal;
private boolean mIsPermittedToAdopt;
private boolean mPortable;
private ViewFlipper mFlipper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -41,63 +47,119 @@ public class StorageWizardInit extends StorageWizardBase {
finish();
return;
}
setContentView(R.layout.storage_wizard_init);
mIsPermittedToAdopt = UserManager.get(this).isAdminUser()
&& !ActivityManager.isUserAMonkey();
&& !ActivityManager.isUserAMonkey();
setHeaderText(R.string.storage_wizard_init_v2_title, getDiskShortDescription());
if (!mIsPermittedToAdopt) {
//Notify guest users as to why formatting is disallowed
Toast.makeText(getApplicationContext(),
R.string.storage_wizard_guest, Toast.LENGTH_LONG).show();
finish();
return;
}
mInternal = requireViewById(R.id.storage_wizard_init_internal);
setContentView(R.layout.storage_wizard_init);
setupHyperlink();
mPortable = true;
setBackButtonText(R.string.storage_wizard_init_v2_later);
setNextButtonVisibility(View.INVISIBLE);
mFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
mFlipper.setDisplayedChild(0);
setHeaderText(R.string.storage_wizard_init_v2_external_title,
getDiskShortDescription());
setNextButtonText(R.string.storage_wizard_init_v2_external_action);
setBackButtonText(R.string.wizard_back_adoptable);
setNextButtonVisibility(View.VISIBLE);
if (!mDisk.isAdoptable()) {
// If not adoptable, we only have one choice
mInternal.setEnabled(false);
onNavigateExternal(null);
} else if (!mIsPermittedToAdopt) {
// TODO: Show a message about why this is disabled for guest and
// that only an admin user can adopt an sd card.
mInternal.setEnabled(false);
setBackButtonVisibility(View.GONE);
}
}
@Override
public void onNavigateBack(View view) {
finish();
public void onBackPressed() {
if (mPortable) {
super.onBackPressed();
} else {
mFlipper.showPrevious();
setBackButtonText(R.string.wizard_back_adoptable);
setHeaderText(R.string.storage_wizard_init_v2_external_title,
getDiskShortDescription());
setNextButtonText(R.string.storage_wizard_init_v2_external_action);
mPortable = true;
}
}
@Override
public void onNavigateBack(View v) {
if (mPortable == false) {
return;
}
if (!mIsPermittedToAdopt) {
// TODO: Show a message about why this is disabled for guest and
// that only an admin user can adopt an sd card.
v.setEnabled(false);
} else {
mFlipper.showNext();
setHeaderText(R.string.storage_wizard_init_v2_internal_title,
getDiskShortDescription());
setNextButtonText(R.string.storage_wizard_init_v2_internal_action);
setBackButtonVisibility(View.INVISIBLE);
mPortable = false;
}
}
@Override
public void onNavigateNext(View v) {
if (mPortable) {
onNavigateExternal(v);
} else {
onNavigateInternal(v);
}
}
public void onNavigateExternal(View view) {
if (view != null) {
// User made an explicit choice for external
FeatureFactory.getFactory(this).getMetricsFeatureProvider().action(this,
SettingsEnums.ACTION_STORAGE_INIT_EXTERNAL);
}
if (mVolume != null && mVolume.getType() == VolumeInfo.TYPE_PUBLIC
&& mVolume.getState() != VolumeInfo.STATE_UNMOUNTABLE) {
// Remember that user made decision
mStorage.setVolumeInited(mVolume.getFsUuid(), true);
final Intent intent = new Intent(this, StorageWizardReady.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
startActivity(intent);
finish();
} else {
// Gotta format to get there
StorageWizardFormatConfirm.showPublic(this, mDisk.getId());
SettingsEnums.ACTION_STORAGE_INIT_EXTERNAL);
}
StorageWizardFormatConfirm.showPublic(this, mDisk.getId());
}
public void onNavigateInternal(View view) {
if (view != null) {
// User made an explicit choice for internal
FeatureFactory.getFactory(this).getMetricsFeatureProvider().action(this,
SettingsEnums.ACTION_STORAGE_INIT_INTERNAL);
SettingsEnums.ACTION_STORAGE_INIT_INTERNAL);
}
StorageWizardFormatConfirm.showPrivate(this, mDisk.getId());
}
private void setupHyperlink() {
TextView external_storage_textview = findViewById(R.id.storage_wizard_init_external_text);
TextView internal_storage_textview = findViewById(R.id.storage_wizard_init_internal_text);
String external_storage_text = getResources().getString(R.string.
storage_wizard_init_v2_external_summary);
String internal_storage_text = getResources().getString(R.string.
storage_wizard_init_v2_internal_summary);
Spannable external_storage_spannable = styleFont(external_storage_text);
Spannable internal_storage_spannable = styleFont(internal_storage_text);
external_storage_textview.setText(external_storage_spannable);
internal_storage_textview.setText(internal_storage_spannable);
external_storage_textview.setMovementMethod(LinkMovementMethod.getInstance());
internal_storage_textview.setMovementMethod(LinkMovementMethod.getInstance());
}
private Spannable styleFont(String text) {
Spannable s = (Spannable) Html.fromHtml(text);
for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
TypefaceSpan typefaceSpan = new TypefaceSpan("sans-serif-medium");
s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
}
return s;
}
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.deviceinfo;
import android.os.Bundle;
import android.os.storage.VolumeInfo;
import android.view.View;
import android.widget.ImageView;
import com.android.settings.R;
@@ -48,7 +49,9 @@ public class StorageWizardReady extends StorageWizardBase {
setBodyText(R.string.storage_wizard_ready_v2_external_body,
getDiskDescription());
}
ImageView img = (ImageView) findViewById(R.id.storage_wizard_body_image);
img.setImageResource(R.drawable.ic_storage_wizard_ready);
setIcon(R.drawable.ic_test_tick);
setNextButtonText(R.string.done);
setBackButtonVisibility(View.INVISIBLE);
}

View File

@@ -30,6 +30,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
@@ -55,7 +56,9 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
OnPrepareOptionsMenu, OnOptionsItemSelected {
private static final String TAG = "VolumeOptionMenuController";
private final Context mContext;
private final Fragment mFragment;
private final PackageManager mPackageManager;
@VisibleForTesting
MenuItem mRename;
@VisibleForTesting
@@ -74,18 +77,12 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
MenuItem mFree;
@VisibleForTesting
MenuItem mForget;
private final Context mContext;
private final Fragment mFragment;
private final PackageManager mPackageManager;
private final StorageManager mStorageManager;
private StorageEntry mStorageEntry;
public VolumeOptionMenuController(Context context, Fragment parent, StorageEntry storageEntry) {
mContext = context;
mFragment = parent;
mPackageManager = context.getPackageManager();
mStorageManager = context.getSystemService(StorageManager.class);
mStorageEntry = storageEntry;
}
@@ -162,12 +159,7 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
if (mStorageEntry.isPublic()) {
mRename.setVisible(true);
mUnmount.setVisible(true);
mFormat.setVisible(true);
final DiskInfo diskInfo = mStorageManager.findDiskById(mStorageEntry.getDiskId());
mFormatAsInternal.setVisible(diskInfo != null
&& diskInfo.isAdoptable()
&& UserManager.get(mContext).isAdminUser()
&& !ActivityManager.isUserAMonkey());
mFormatAsInternal.setVisible(true);
return;
}
}
@@ -225,6 +217,16 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
}
if (menuId == R.id.storage_format_as_portable) {
if (mStorageEntry.isPrivate()) {
boolean mIsPermittedToAdopt = UserManager.get(mContext).isAdminUser()
&& !ActivityManager.isUserAMonkey();
if(!mIsPermittedToAdopt){
//Notify guest users as to why formatting is disallowed
Toast.makeText(mFragment.getActivity(),
R.string.storage_wizard_guest,Toast.LENGTH_LONG).show();
(mFragment.getActivity()).finish();
return false;
}
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId());
new SubSettingLauncher(mContext)
@@ -239,8 +241,9 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
}
if (menuId == R.id.storage_format_as_internal) {
if (mStorageEntry.isPublic()) {
StorageWizardFormatConfirm.showPrivate(mFragment.getActivity(),
mStorageEntry.getDiskId());
final Intent intent = new Intent(mFragment.getActivity(), StorageWizardInit.class);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId());
mContext.startActivity(intent);
return true;
}
return false;
@@ -269,4 +272,4 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp
updateOptionsMenu();
}
}
}