1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.deviceinfo;
18 
19 import static android.os.storage.DiskInfo.EXTRA_DISK_ID;
20 import static android.os.storage.VolumeInfo.EXTRA_VOLUME_ID;
21 
22 import static com.android.settings.deviceinfo.StorageSettings.TAG;
23 
24 import android.annotation.LayoutRes;
25 import android.annotation.NonNull;
26 import android.content.Intent;
27 import android.content.res.Resources.Theme;
28 import android.graphics.drawable.Drawable;
29 import android.os.Bundle;
30 import android.os.SystemClock;
31 import android.os.storage.DiskInfo;
32 import android.os.storage.StorageEventListener;
33 import android.os.storage.StorageManager;
34 import android.os.storage.VolumeInfo;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.widget.FrameLayout;
40 import android.widget.ProgressBar;
41 import android.widget.TextView;
42 
43 import androidx.fragment.app.FragmentActivity;
44 
45 import com.android.settings.R;
46 import com.android.settingslib.Utils;
47 
48 import com.google.android.setupcompat.template.FooterBarMixin;
49 import com.google.android.setupcompat.template.FooterButton;
50 import com.google.android.setupdesign.GlifLayout;
51 
52 import java.text.NumberFormat;
53 import java.util.List;
54 import java.util.Objects;
55 
56 public abstract class StorageWizardBase extends FragmentActivity {
57     protected static final String EXTRA_FORMAT_FORGET_UUID = "format_forget_uuid";
58     protected static final String EXTRA_FORMAT_PRIVATE = "format_private";
59     protected static final String EXTRA_FORMAT_SLOW = "format_slow";
60     protected static final String EXTRA_MIGRATE_SKIP = "migrate_skip";
61 
62     protected StorageManager mStorage;
63 
64     protected VolumeInfo mVolume;
65     protected DiskInfo mDisk;
66 
67     private FooterBarMixin mFooterBarMixin;
68     private FooterButton mBack;
69     private FooterButton mNext;
70 
71     @Override
onCreate(Bundle savedInstanceState)72     protected void onCreate(Bundle savedInstanceState) {
73         super.onCreate(savedInstanceState);
74 
75         mStorage = getSystemService(StorageManager.class);
76 
77         final String volumeId = getIntent().getStringExtra(EXTRA_VOLUME_ID);
78         if (!TextUtils.isEmpty(volumeId)) {
79             mVolume = mStorage.findVolumeById(volumeId);
80         }
81 
82         final String diskId = getIntent().getStringExtra(EXTRA_DISK_ID);
83         if (!TextUtils.isEmpty(diskId)) {
84             mDisk = mStorage.findDiskById(diskId);
85         } else if (mVolume != null) {
86             mDisk = mVolume.getDisk();
87         }
88 
89         if (mDisk != null) {
90             mStorage.registerListener(mStorageListener);
91         }
92     }
93 
94     @Override
setContentView(@ayoutRes int layoutResID)95     public void setContentView(@LayoutRes int layoutResID) {
96         super.setContentView(layoutResID);
97 
98         mFooterBarMixin = getGlifLayout().getMixin(FooterBarMixin.class);
99         mFooterBarMixin.setSecondaryButton(
100                 new FooterButton.Builder(this)
101                         .setText(R.string.wizard_back)
102                         .setListener(this::onNavigateBack)
103                         .setButtonType(FooterButton.ButtonType.OTHER)
104                         .setTheme(R.style.SudGlifButton_Secondary)
105                         .build()
106         );
107         mFooterBarMixin.setPrimaryButton(
108                 new FooterButton.Builder(this)
109                         .setText(R.string.wizard_next)
110                         .setListener(this::onNavigateNext)
111                         .setButtonType(FooterButton.ButtonType.NEXT)
112                         .setTheme(R.style.SudGlifButton_Primary)
113                         .build()
114         );
115         mBack = mFooterBarMixin.getSecondaryButton();
116         mNext = mFooterBarMixin.getPrimaryButton();
117 
118         setIcon(com.android.internal.R.drawable.ic_sd_card_48dp);
119     }
120 
121     @Override
onDestroy()122     protected void onDestroy() {
123         mStorage.unregisterListener(mStorageListener);
124         super.onDestroy();
125     }
126 
127     @Override
onApplyThemeResource(Theme theme, int resid, boolean first)128     protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
129         theme.applyStyle(R.style.SetupWizardPartnerResource, true);
130         super.onApplyThemeResource(theme, resid, first);
131     }
132 
getBackButton()133     protected FooterButton getBackButton() {
134         return mBack;
135     }
136 
getNextButton()137     protected FooterButton getNextButton() {
138         return mNext;
139     }
140 
getGlifLayout()141     protected GlifLayout getGlifLayout() {
142         return requireViewById(R.id.setup_wizard_layout);
143     }
144 
getProgressBar()145     protected ProgressBar getProgressBar() {
146         return requireViewById(R.id.storage_wizard_progress);
147     }
148 
setCurrentProgress(int progress)149     protected void setCurrentProgress(int progress) {
150         getProgressBar().setProgress(progress);
151         ((TextView) requireViewById(R.id.storage_wizard_progress_summary)).setText(
152                 NumberFormat.getPercentInstance().format((double) progress / 100));
153     }
154 
setHeaderText(int resId, CharSequence... args)155     protected void setHeaderText(int resId, CharSequence... args) {
156         final CharSequence headerText = TextUtils.expandTemplate(getText(resId), args);
157         getGlifLayout().setHeaderText(headerText);
158         setTitle(headerText);
159     }
160 
setBodyText(int resId, CharSequence... args)161     protected void setBodyText(int resId, CharSequence... args) {
162         final TextView body = requireViewById(R.id.storage_wizard_body);
163         body.setText(TextUtils.expandTemplate(getText(resId), args));
164         body.setVisibility(View.VISIBLE);
165     }
166 
setAuxChecklist()167     protected void setAuxChecklist() {
168         final FrameLayout aux = requireViewById(R.id.storage_wizard_aux);
169         aux.addView(LayoutInflater.from(aux.getContext())
170                 .inflate(R.layout.storage_wizard_checklist, aux, false));
171         aux.setVisibility(View.VISIBLE);
172 
173         // Customize string based on disk
174         ((TextView) aux.requireViewById(R.id.storage_wizard_migrate_v2_checklist_media))
175                 .setText(TextUtils.expandTemplate(
176                         getText(R.string.storage_wizard_migrate_v2_checklist_media),
177                         getDiskShortDescription()));
178     }
179 
setBackButtonText(int resId, CharSequence... args)180     protected void setBackButtonText(int resId, CharSequence... args) {
181         mBack.setText(TextUtils.expandTemplate(getText(resId), args));
182         mBack.setVisibility(View.VISIBLE);
183     }
184 
setNextButtonText(int resId, CharSequence... args)185     protected void setNextButtonText(int resId, CharSequence... args) {
186         mNext.setText(TextUtils.expandTemplate(getText(resId), args));
187         mNext.setVisibility(View.VISIBLE);
188     }
189 
setBackButtonVisibility(int visible)190     protected void setBackButtonVisibility(int visible) {
191         mBack.setVisibility(visible);
192     }
193 
setNextButtonVisibility(int visible)194     protected void setNextButtonVisibility(int visible) {
195         mNext.setVisibility(visible);
196     }
197 
setIcon(int resId)198     protected void setIcon(int resId) {
199         final GlifLayout layout = getGlifLayout();
200         final Drawable icon = getDrawable(resId).mutate();
201         icon.setTintList(Utils.getColorAccent(layout.getContext()));
202         layout.setIcon(icon);
203     }
204 
setKeepScreenOn(boolean keepScreenOn)205     protected void setKeepScreenOn(boolean keepScreenOn) {
206         getGlifLayout().setKeepScreenOn(keepScreenOn);
207     }
208 
onNavigateBack(View view)209     public void onNavigateBack(View view) {
210         throw new UnsupportedOperationException();
211     }
212 
onNavigateNext(View view)213     public void onNavigateNext(View view) {
214         throw new UnsupportedOperationException();
215     }
216 
copyStringExtra(Intent from, Intent to, String key)217     private void copyStringExtra(Intent from, Intent to, String key) {
218         if (from.hasExtra(key) && !to.hasExtra(key)) {
219             to.putExtra(key, from.getStringExtra(key));
220         }
221     }
222 
copyBooleanExtra(Intent from, Intent to, String key)223     private void copyBooleanExtra(Intent from, Intent to, String key) {
224         if (from.hasExtra(key) && !to.hasExtra(key)) {
225             to.putExtra(key, from.getBooleanExtra(key, false));
226         }
227     }
228 
229     @Override
startActivity(Intent intent)230     public void startActivity(Intent intent) {
231         final Intent from = getIntent();
232         final Intent to = intent;
233 
234         copyStringExtra(from, to, EXTRA_DISK_ID);
235         copyStringExtra(from, to, EXTRA_VOLUME_ID);
236         copyStringExtra(from, to, EXTRA_FORMAT_FORGET_UUID);
237         copyBooleanExtra(from, to, EXTRA_FORMAT_PRIVATE);
238         copyBooleanExtra(from, to, EXTRA_FORMAT_SLOW);
239         copyBooleanExtra(from, to, EXTRA_MIGRATE_SKIP);
240 
241         super.startActivity(intent);
242     }
243 
findFirstVolume(int type)244     protected VolumeInfo findFirstVolume(int type) {
245         return findFirstVolume(type, 1);
246     }
247 
findFirstVolume(int type, int attempts)248     protected VolumeInfo findFirstVolume(int type, int attempts) {
249         while (true) {
250             final List<VolumeInfo> vols = mStorage.getVolumes();
251             for (VolumeInfo vol : vols) {
252                 if (Objects.equals(mDisk.getId(), vol.getDiskId()) && (vol.getType() == type)
253                         && (vol.getState() == VolumeInfo.STATE_MOUNTED)) {
254                     return vol;
255                 }
256             }
257 
258             if (--attempts > 0) {
259                 Log.w(TAG, "Missing mounted volume of type " + type + " hosted by disk "
260                         + mDisk.getId() + "; trying again");
261                 SystemClock.sleep(250);
262             } else {
263                 return null;
264             }
265         }
266     }
267 
getDiskDescription()268     protected @NonNull CharSequence getDiskDescription() {
269         if (mDisk != null) {
270             return mDisk.getDescription();
271         } else if (mVolume != null) {
272             return mVolume.getDescription();
273         } else {
274             return getText(R.string.unknown);
275         }
276     }
277 
getDiskShortDescription()278     protected @NonNull CharSequence getDiskShortDescription() {
279         if (mDisk != null) {
280             return mDisk.getShortDescription();
281         } else if (mVolume != null) {
282             return mVolume.getDescription();
283         } else {
284             return getText(R.string.unknown);
285         }
286     }
287 
288     private final StorageEventListener mStorageListener = new StorageEventListener() {
289         @Override
290         public void onDiskDestroyed(DiskInfo disk) {
291             // We know mDisk != null.
292             if (mDisk.id.equals(disk.id)) {
293                 finish();
294             }
295         }
296     };
297 }
298