1 /*
2  * Copyright (C) 2022 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.providers.media.photopicker.ui.settings;
18 
19 import static com.android.providers.media.MediaApplication.getConfigStore;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.content.Context;
24 import android.content.Intent;
25 import android.os.Bundle;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.widget.Toast;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.UiThread;
32 import androidx.lifecycle.ViewModel;
33 import androidx.lifecycle.ViewModelProvider;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceFragmentCompat;
36 import androidx.preference.PreferenceScreen;
37 
38 import com.android.providers.media.R;
39 import com.android.providers.media.photopicker.data.model.UserId;
40 import com.android.settingslib.widget.SelectorWithWidgetPreference;
41 
42 /**
43  * This fragment will display a list of available cloud providers for the profile selected.
44  * Users can view or change the preferred cloud provider media app.
45  */
46 public class SettingsCloudMediaSelectFragment extends PreferenceFragmentCompat
47         implements SelectorWithWidgetPreference.OnClickListener {
48     public static final String EXTRA_TAB_USER_ID = "user_id";
49     private static final String TAG = "SettingsCMSelectFgmt";
50 
51     @NonNull
52     private UserId mUserId;
53     @NonNull
54     private Context mContext;
55     @NonNull
56     private SettingsCloudMediaViewModel mSettingsCloudMediaViewModel;
57 
58     @Override
onAttach(@onNull Context context)59     public void onAttach(@NonNull Context context) {
60         super.onAttach(context);
61 
62         mContext = requireNonNull(context);
63         mUserId = requireNonNull(getUserId());
64         mSettingsCloudMediaViewModel = createViewModel();
65     }
66 
67     @Override
onResume()68     public void onResume() {
69         super.onResume();
70 
71         mSettingsCloudMediaViewModel.loadMediaCollectionInfoAsync();
72     }
73 
74     @UiThread
75     @Override
onRadioButtonClicked(@onNull SelectorWithWidgetPreference selectedPref)76     public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selectedPref) {
77         final String selectedProviderKey = selectedPref.getKey();
78         // Check if current provider is different from the selected provider.
79         if (!TextUtils.equals(selectedProviderKey,
80                 mSettingsCloudMediaViewModel.getSelectedPreferenceKey())) {
81             final boolean success =
82                     mSettingsCloudMediaViewModel.updateSelectedProvider(selectedProviderKey);
83             if (success) {
84                 updateSelectedRadioButton();
85             } else {
86                 Toast.makeText(getContext(),
87                         R.string.picker_settings_toast_error, Toast.LENGTH_SHORT).show();
88             }
89         }
90     }
91 
92     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)93     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
94         super.addPreferencesFromResource(R.xml.pref_screen_picker_settings);
95 
96         mSettingsCloudMediaViewModel.loadData(getConfigStore());
97         observeMediaCollectionInfoChanges();
98         refreshUI();
99     }
100 
101     @UiThread
refreshUI()102     private void refreshUI() {
103         final PreferenceScreen screen = getPreferenceScreen();
104         resetPreferenceScreen(screen);
105 
106         screen.addPreference(buildTitlePreference());
107         for (CloudMediaProviderOption provider :
108                 mSettingsCloudMediaViewModel.getProviderOptions()) {
109             screen.addPreference(buildProviderOptionPreference(provider));
110         }
111 
112         updateSelectedRadioButton();
113     }
114 
observeMediaCollectionInfoChanges()115     private void observeMediaCollectionInfoChanges() {
116         mSettingsCloudMediaViewModel.getCurrentProviderMediaCollectionInfo().observe(this,
117                 providerMediaCollectionInfo -> {
118                     // Only update the UI preference if the cloud provider linked to the media
119                     // collection info matches the current provider.
120                     if (providerMediaCollectionInfo == null
121                             || !TextUtils.equals(providerMediaCollectionInfo.getAuthority(),
122                             mSettingsCloudMediaViewModel.getSelectedProviderAuthority())) {
123                         return;
124                     }
125 
126                     final SelectorWithWidgetPreference selectedPref =
127                             findPreference(mSettingsCloudMediaViewModel.getSelectedPreferenceKey());
128 
129                     // TODO(b/262002538): {@code selectedPref} could be null if the selected
130                     //  cloud provider is not in the allowed list. This is not something a
131                     //  typical user will encounter.
132                     if (selectedPref == null) {
133                         return;
134                     }
135 
136                     selectedPref.setSummary(providerMediaCollectionInfo.getAccountName());
137 
138                     final Intent accountConfigurationIntent =
139                             providerMediaCollectionInfo.getAccountConfigurationIntent();
140                     selectedPref.setExtraWidgetOnClickListener(
141                             accountConfigurationIntent == null ? null : v ->
142                                     requireActivity().startActivityAsUser(
143                                             accountConfigurationIntent, mUserId.getUserHandle()));
144                 });
145     }
146 
147     @UiThread
updateSelectedRadioButton()148     private void updateSelectedRadioButton() {
149         final String selectedPreferenceKey =
150                 mSettingsCloudMediaViewModel.getSelectedPreferenceKey();
151         for (CloudMediaProviderOption providerOption
152                 : mSettingsCloudMediaViewModel.getProviderOptions()) {
153             final SelectorWithWidgetPreference preference = findPreference(providerOption.getKey());
154             if (preference == null) {
155                 continue;
156             }
157 
158             final boolean isSelected = TextUtils.equals(preference.getKey(), selectedPreferenceKey);
159             preference.setChecked(isSelected);
160 
161             preference.setSummary(null);
162             preference.setExtraWidgetOnClickListener(null);
163 
164             if (isSelected) {
165                 mSettingsCloudMediaViewModel.loadMediaCollectionInfoAsync();
166             }
167         }
168     }
169 
170     @NonNull
buildProviderOptionPreference(@onNull CloudMediaProviderOption provider)171     private Preference buildProviderOptionPreference(@NonNull CloudMediaProviderOption provider) {
172         final SelectorWithWidgetPreference pref =
173                 new SelectorWithWidgetPreference(getPrefContext());
174 
175         // Preferences are stored in SharedPreferences by default. This feature is disabled
176         // as Cloud picker preferences are stored in SharedPreferences separately.
177         pref.setPersistent(false);
178         pref.setTitle(provider.getLabel());
179         pref.setIcon(provider.getIcon());
180         pref.setKey(provider.getKey());
181         pref.setOnClickListener(this);
182         return pref;
183     }
184 
185     @NonNull
buildTitlePreference()186     private Preference buildTitlePreference() {
187         final Preference titlePref = new Preference(getPrefContext());
188         titlePref.setTitle(R.string.picker_settings_selection_message);
189         titlePref.setSelectable(false);
190         titlePref.setPersistent(false);
191         titlePref.setLayoutResource(R.layout.pref_settings_cloud_select_title);
192         return titlePref;
193     }
194 
getPrefContext()195     private Context getPrefContext() {
196         return getPreferenceManager().getContext();
197     }
198 
resetPreferenceScreen(PreferenceScreen screen)199     private void resetPreferenceScreen(PreferenceScreen screen) {
200         screen.removeAll();
201     }
202 
203     @NonNull
getUserId()204     private UserId getUserId() {
205         if (getArguments() != null && getArguments().containsKey(EXTRA_TAB_USER_ID)) {
206             final int currentUID = getArguments().getInt(EXTRA_TAB_USER_ID);
207             final UserHandle userHandle = UserHandle.of(currentUID);
208             return UserId.of(userHandle);
209         } else {
210             throw new IllegalArgumentException(
211                     "User Id for a SettingsCloudMediaSelectFragment is not set.");
212         }
213     }
214 
215     @NonNull
createViewModel()216     private SettingsCloudMediaViewModel createViewModel() {
217         final ViewModelProvider viewModelProvider =
218                 new ViewModelProvider(
219                         requireActivity(),
220                         new SettingsViewModelFactory(mContext, mUserId)
221                 );
222         final String viewModelKey = Integer.toString(mUserId.getIdentifier());
223 
224         return viewModelProvider.get(viewModelKey, SettingsCloudMediaViewModel.class);
225     }
226 
227     private static class SettingsViewModelFactory implements ViewModelProvider.Factory {
228         @NonNull
229         private final Context mContext;
230         @NonNull
231         private final UserId mUserId;
232 
SettingsViewModelFactory(@onNull Context context, @NonNull UserId userId)233         SettingsViewModelFactory(@NonNull Context context, @NonNull UserId userId) {
234             mContext = requireNonNull(context);
235             mUserId = requireNonNull(userId);
236         }
237 
238         @NonNull
239         @Override
create(@onNull Class<T> modelClass)240         public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
241             return (T) new SettingsCloudMediaViewModel(mContext, mUserId);
242         }
243     }
244 }