From 7efe9315bacd3192614fc8a1cdf8207d313f568e Mon Sep 17 00:00:00 2001 From: Varun Shah Date: Thu, 26 Mar 2020 23:02:50 -0700 Subject: [PATCH] Add a new developer options screen for shared data. Add a new Storage section in the developer options menu which has a new Shared Data preference. This preference screen shows all shared data blobs on the device. There is also a new screen for each data blob which shows all of the packages which currently have a leases on it. This screen also has a button to delete the shared data blob. Bug: 150626561 Test: make RunSettingsRoboTests ROBOTEST_FILTER=SharedDataPreferenceControllerTest Test: manual (visual) Change-Id: Id84a33dc7eeac493b2f81d3996ad24ee70557a07 --- AndroidManifest.xml | 5 + res/layout/blob_list_item_view.xml | 50 ++++++ res/layout/lease_list_item_view.xml | 52 ++++++ res/layout/shared_data_empty_list_view.xml | 31 ++++ res/values/dimens.xml | 3 + res/xml/development_settings.xml | 15 ++ .../DevelopmentSettingsDashboardFragment.java | 2 + .../development/storage/BlobInfoListView.java | 162 +++++++++++++++++ .../storage/BlobInfoViewHolder.java | 48 +++++ .../storage/LeaseInfoListView.java | 167 ++++++++++++++++++ .../storage/LeaseInfoViewHolder.java | 48 +++++ .../SharedDataPreferenceController.java | 52 ++++++ .../development/storage/SharedDataUtils.java | 42 +++++ .../SharedDataPreferenceControllerTest.java | 82 +++++++++ 14 files changed, 759 insertions(+) create mode 100644 res/layout/blob_list_item_view.xml create mode 100644 res/layout/lease_list_item_view.xml create mode 100644 res/layout/shared_data_empty_list_view.xml create mode 100644 src/com/android/settings/development/storage/BlobInfoListView.java create mode 100644 src/com/android/settings/development/storage/BlobInfoViewHolder.java create mode 100644 src/com/android/settings/development/storage/LeaseInfoListView.java create mode 100644 src/com/android/settings/development/storage/LeaseInfoViewHolder.java create mode 100644 src/com/android/settings/development/storage/SharedDataPreferenceController.java create mode 100644 src/com/android/settings/development/storage/SharedDataUtils.java create mode 100644 tests/robotests/src/com/android/settings/development/storage/SharedDataPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 17b498e1878..98789bdc86c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1990,6 +1990,11 @@ android:label="Terms of Service" android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" /> + + + diff --git a/res/layout/blob_list_item_view.xml b/res/layout/blob_list_item_view.xml new file mode 100644 index 00000000000..897d19c2694 --- /dev/null +++ b/res/layout/blob_list_item_view.xml @@ -0,0 +1,50 @@ + + + + + + + + + + diff --git a/res/layout/lease_list_item_view.xml b/res/layout/lease_list_item_view.xml new file mode 100644 index 00000000000..5edd9e54034 --- /dev/null +++ b/res/layout/lease_list_item_view.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + diff --git a/res/layout/shared_data_empty_list_view.xml b/res/layout/shared_data_empty_list_view.xml new file mode 100644 index 00000000000..1bb338bdaf8 --- /dev/null +++ b/res/layout/shared_data_empty_list_view.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 79071ed3a05..7f4bb544ba1 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -417,4 +417,7 @@ 8dp 48dp 16dp + + + 16dp diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 48a0850c7ba..c134763d82a 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -652,4 +652,19 @@ android:title="@string/autofill_reset_developer_options" /> + + + + + + + diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index c349de9e8c9..ef58c8a84a7 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -52,6 +52,7 @@ import com.android.settings.development.bluetooth.BluetoothCodecDialogPreference import com.android.settings.development.bluetooth.BluetoothHDAudioPreferenceController; import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferenceController; import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController; +import com.android.settings.development.storage.SharedDataPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBar; import com.android.settingslib.core.AbstractPreferenceController; @@ -531,6 +532,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra bluetoothA2dpConfigStore)); controllers.add(new BluetoothHDAudioPreferenceController(context, lifecycle, bluetoothA2dpConfigStore, fragment)); + controllers.add(new SharedDataPreferenceController(context)); return controllers; } diff --git a/src/com/android/settings/development/storage/BlobInfoListView.java b/src/com/android/settings/development/storage/BlobInfoListView.java new file mode 100644 index 00000000000..427e37fbc10 --- /dev/null +++ b/src/com/android/settings/development/storage/BlobInfoListView.java @@ -0,0 +1,162 @@ +/* + * 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.development.storage; + +import android.app.ListActivity; +import android.app.blob.BlobInfo; +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; + +import com.android.internal.util.CollectionUtils; +import com.android.settings.R; + +import java.io.IOException; +import java.util.List; + +// TODO: have this class extend DashboardFragment for consistency +public class BlobInfoListView extends ListActivity { + private static final String TAG = "BlobInfoListView"; + + private Context mContext; + private BlobStoreManager mBlobStoreManager; + private BlobListAdapter mAdapter; + private LayoutInflater mInflater; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + + mBlobStoreManager = (BlobStoreManager) getSystemService(BlobStoreManager.class); + mInflater = (LayoutInflater) getSystemService(LayoutInflater.class); + + mAdapter = new BlobListAdapter(this); + setListAdapter(mAdapter); + } + + @Override + protected void onResume() { + super.onResume(); + queryBlobsAndUpdateList(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == SharedDataUtils.LEASE_VIEW_REQUEST_CODE + && resultCode == SharedDataUtils.LEASE_VIEW_RESULT_CODE_FAILURE) { + Toast.makeText(this, R.string.shared_data_delete_failure_text, Toast.LENGTH_LONG) + .show(); + } + // do nothing on LEASE_VIEW_RESULT_CODE_SUCCESS since data is updated in onResume() + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + final BlobInfo blob = mAdapter.getItem(position); + if (CollectionUtils.isEmpty(blob.getLeases())) { + showDeleteBlobDialog(blob); + } else { + final Intent intent = new Intent(this, LeaseInfoListView.class); + intent.putExtra(SharedDataUtils.BLOB_KEY, blob); + startActivityForResult(intent, SharedDataUtils.LEASE_VIEW_REQUEST_CODE); + } + } + + private View getEmptyView() { + final View emptyView = mInflater.inflate(R.layout.shared_data_empty_list_view, + (ViewGroup) getListView().getRootView()); + final TextView emptyText = emptyView.findViewById(R.id.empty_view_text); + emptyText.setText(R.string.shared_data_no_blobs_text); + return emptyView; + } + + private void showDeleteBlobDialog(BlobInfo blob) { + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setMessage(R.string.shared_data_no_accessors_dialog_text) + .setPositiveButton(android.R.string.ok, getDialogOnClickListener(blob)) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.show(); + } + + private DialogInterface.OnClickListener getDialogOnClickListener(BlobInfo blob) { + return (dialog, which) -> { + try { + mBlobStoreManager.deleteBlob(blob); + } catch (IOException e) { + Log.e(TAG, "Unable to delete blob: " + e.getMessage()); + Toast.makeText(this, R.string.shared_data_delete_failure_text, Toast.LENGTH_LONG) + .show(); + } + queryBlobsAndUpdateList(); + }; + } + + private void queryBlobsAndUpdateList() { + try { + mAdapter.updateList(mBlobStoreManager.queryBlobsForUser(UserHandle.CURRENT)); + } catch (IOException e) { + Log.e(TAG, "Unable to fetch blobs for current user: " + e.getMessage()); + Toast.makeText(this, R.string.shared_data_query_failure_text, Toast.LENGTH_LONG).show(); + finish(); + } + } + + private class BlobListAdapter extends ArrayAdapter { + BlobListAdapter(Context context) { + super(context, 0); + } + + void updateList(List blobs) { + clear(); + if (blobs.isEmpty()) { + getListView().setEmptyView(getEmptyView()); + } else { + addAll(blobs); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final BlobInfoViewHolder holder = BlobInfoViewHolder.createOrRecycle( + mInflater, convertView); + convertView = holder.rootView; + + final BlobInfo blob = getItem(position); + holder.blobLabel.setText(blob.getLabel()); + holder.blobId.setText(getString(R.string.blob_id_text, blob.getId())); + holder.blobExpiry.setText(getString(R.string.blob_expires_text, + SharedDataUtils.formatTime(blob.getExpiryTimeMs()))); + return convertView; + } + } +} diff --git a/src/com/android/settings/development/storage/BlobInfoViewHolder.java b/src/com/android/settings/development/storage/BlobInfoViewHolder.java new file mode 100644 index 00000000000..de8c9a95a4b --- /dev/null +++ b/src/com/android/settings/development/storage/BlobInfoViewHolder.java @@ -0,0 +1,48 @@ +/* + * 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.development.storage; + +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.android.settings.R; + +/** + * View holder for {@link BlobInfoListView}. + */ +class BlobInfoViewHolder { + View rootView; + TextView blobLabel; + TextView blobId; + TextView blobExpiry; + + static BlobInfoViewHolder createOrRecycle(LayoutInflater inflater, View convertView) { + if (convertView != null) { + return (BlobInfoViewHolder) convertView.getTag(); + } + convertView = inflater.inflate(R.layout.blob_list_item_view, null); + + final BlobInfoViewHolder holder = new BlobInfoViewHolder(); + holder.rootView = convertView; + holder.blobLabel = convertView.findViewById(R.id.blob_label); + holder.blobId = convertView.findViewById(R.id.blob_id); + holder.blobExpiry = convertView.findViewById(R.id.blob_expiry); + convertView.setTag(holder); + return holder; + } +} diff --git a/src/com/android/settings/development/storage/LeaseInfoListView.java b/src/com/android/settings/development/storage/LeaseInfoListView.java new file mode 100644 index 00000000000..b9a30427397 --- /dev/null +++ b/src/com/android/settings/development/storage/LeaseInfoListView.java @@ -0,0 +1,167 @@ +/* + * 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.development.storage; + +import android.app.ListActivity; +import android.app.blob.BlobInfo; +import android.app.blob.BlobStoreManager; +import android.app.blob.LeaseInfo; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.android.internal.util.CollectionUtils; +import com.android.settings.R; + +import java.io.IOException; +import java.util.List; + +// TODO: have this class extend DashboardFragment for consistency +public class LeaseInfoListView extends ListActivity { + private static final String TAG = "LeaseInfoListView"; + + private Context mContext; + private BlobStoreManager mBlobStoreManager; + private BlobInfo mBlobInfo; + private LeaseListAdapter mAdapter; + private LayoutInflater mInflater; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + mBlobStoreManager = (BlobStoreManager) getSystemService(BlobStoreManager.class); + mInflater = (LayoutInflater) getSystemService(LayoutInflater.class); + + mBlobInfo = getIntent().getParcelableExtra(SharedDataUtils.BLOB_KEY); + + mAdapter = new LeaseListAdapter(this); + if (mAdapter.isEmpty()) { + // this should never happen since we're checking the size in BlobInfoListView + Log.e(TAG, "Error fetching leases for shared data: " + mBlobInfo.toString()); + finish(); + } + + setListAdapter(mAdapter); + getListView().addHeaderView(getHeaderView()); + getListView().addFooterView(getFooterView()); + getListView().setClickable(false); + } + + private LinearLayout getHeaderView() { + final LinearLayout headerView = (LinearLayout) mInflater.inflate( + R.layout.blob_list_item_view , null); + final TextView blobLabel = headerView.findViewById(R.id.blob_label); + final TextView blobId = headerView.findViewById(R.id.blob_id); + final TextView blobExpiry = headerView.findViewById(R.id.blob_expiry); + + blobLabel.setText(mBlobInfo.getLabel()); + blobLabel.setTypeface(Typeface.DEFAULT_BOLD); + blobId.setText(getString(R.string.blob_id_text, mBlobInfo.getId())); + blobExpiry.setVisibility(View.GONE); + return headerView; + } + + private Button getFooterView() { + final Button deleteButton = new Button(this); + deleteButton.setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + deleteButton.setText(R.string.delete_blob_text); + deleteButton.setOnClickListener(getButtonOnClickListener()); + return deleteButton; + } + + private View.OnClickListener getButtonOnClickListener() { + return v -> { + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setMessage(R.string.delete_blob_confirmation_text) + .setPositiveButton(android.R.string.ok, getDialogOnClickListener()) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.show(); + }; + } + + private DialogInterface.OnClickListener getDialogOnClickListener() { + return (dialog, which) -> { + try { + mBlobStoreManager.deleteBlob(mBlobInfo); + setResult(SharedDataUtils.LEASE_VIEW_RESULT_CODE_SUCCESS); + } catch (IOException e) { + Log.e(TAG, "Unable to delete blob: " + e.getMessage()); + setResult(SharedDataUtils.LEASE_VIEW_RESULT_CODE_FAILURE); + } + finish(); + }; + } + + private class LeaseListAdapter extends ArrayAdapter { + LeaseListAdapter(Context context) { + super(context, 0); + + final List leases = mBlobInfo.getLeases(); + if (CollectionUtils.isEmpty(leases)) { + return; + } + addAll(leases); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final LeaseInfoViewHolder holder = LeaseInfoViewHolder.createOrRecycle( + mInflater, convertView); + convertView = holder.rootView; + + final LeaseInfo lease = getItem(position); + holder.leasePackageName.setText(lease.getPackageName()); + holder.leaseDescription.setText(getDescriptionString(lease)); + holder.leaseExpiry.setText(getString(R.string.accessor_expires_text, + SharedDataUtils.formatTime(lease.getExpiryTimeMillis()))); + return convertView; + } + + private String getDescriptionString(LeaseInfo lease) { + String description = null; + try { + description = getString(lease.getDescriptionResId()); + } catch (Resources.NotFoundException ignored) { + if (lease.getDescription() != null) { + description = lease.getDescription().toString(); + } + } finally { + if (TextUtils.isEmpty(description)) { + description = getString(R.string.accessor_no_description_text); + } + } + return description; + } + } +} diff --git a/src/com/android/settings/development/storage/LeaseInfoViewHolder.java b/src/com/android/settings/development/storage/LeaseInfoViewHolder.java new file mode 100644 index 00000000000..d74c92994d7 --- /dev/null +++ b/src/com/android/settings/development/storage/LeaseInfoViewHolder.java @@ -0,0 +1,48 @@ +/* + * 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.development.storage; + +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.android.settings.R; + +/** + * View holder for {@link LeaseInfoListView}. + */ +class LeaseInfoViewHolder { + View rootView; + TextView leasePackageName; + TextView leaseDescription; + TextView leaseExpiry; + + static LeaseInfoViewHolder createOrRecycle(LayoutInflater inflater, View convertView) { + if (convertView != null) { + return (LeaseInfoViewHolder) convertView.getTag(); + } + convertView = inflater.inflate(R.layout.lease_list_item_view, null); + + final LeaseInfoViewHolder holder = new LeaseInfoViewHolder(); + holder.rootView = convertView; + holder.leasePackageName = convertView.findViewById(R.id.lease_package); + holder.leaseDescription = convertView.findViewById(R.id.lease_desc); + holder.leaseExpiry = convertView.findViewById(R.id.lease_expiry); + convertView.setTag(holder); + return holder; + } +} diff --git a/src/com/android/settings/development/storage/SharedDataPreferenceController.java b/src/com/android/settings/development/storage/SharedDataPreferenceController.java new file mode 100644 index 00000000000..1d5c3e40168 --- /dev/null +++ b/src/com/android/settings/development/storage/SharedDataPreferenceController.java @@ -0,0 +1,52 @@ +/* + * 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.development.storage; + +import android.app.blob.BlobStoreManager; +import android.content.Context; + +import androidx.preference.Preference; + +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +public class SharedDataPreferenceController extends DeveloperOptionsPreferenceController { + + private static final String SHARED_DATA = "shared_data"; + + private BlobStoreManager mBlobStoreManager; + + public SharedDataPreferenceController(Context context) { + super(context); + mBlobStoreManager = (BlobStoreManager) context.getSystemService(BlobStoreManager.class); + } + + @Override + public String getPreferenceKey() { + return SHARED_DATA; + } + + @Override + public boolean isAvailable() { + return mBlobStoreManager != null; + } + + @Override + public void updateState(Preference preference) { + preference.setEnabled(mBlobStoreManager != null); + // TODO: update summary to indicate why this preference isn't available + } +} diff --git a/src/com/android/settings/development/storage/SharedDataUtils.java b/src/com/android/settings/development/storage/SharedDataUtils.java new file mode 100644 index 00000000000..2f48f6d41fa --- /dev/null +++ b/src/com/android/settings/development/storage/SharedDataUtils.java @@ -0,0 +1,42 @@ +/* + * 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.development.storage; + +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; +import android.icu.util.TimeZone; + +import java.util.Locale; + +class SharedDataUtils { + static final String BLOB_KEY = "BLOB_KEY"; + + static final int LEASE_VIEW_REQUEST_CODE = 8108; + static final int LEASE_VIEW_RESULT_CODE_SUCCESS = 1; + static final int LEASE_VIEW_RESULT_CODE_FAILURE = -1; + + private static final String BLOB_EXPIRY_PATTERN = "MMM dd, yyyy HH:mm:ss z"; + + private static final SimpleDateFormat FORMATTER = new SimpleDateFormat(BLOB_EXPIRY_PATTERN); + private static final Calendar CALENDAR = Calendar.getInstance( + TimeZone.getDefault(), Locale.getDefault()); + + static String formatTime(long millis) { + CALENDAR.setTimeInMillis(millis); + return FORMATTER.format(CALENDAR.getTime()); + } +} diff --git a/tests/robotests/src/com/android/settings/development/storage/SharedDataPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/storage/SharedDataPreferenceControllerTest.java new file mode 100644 index 00000000000..86bb4a70883 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/storage/SharedDataPreferenceControllerTest.java @@ -0,0 +1,82 @@ +/* + * 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.development.storage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.blob.BlobStoreManager; +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.util.ReflectionHelpers; + +// TODO: add more detailed tests for the shared data screens +@RunWith(RobolectricTestRunner.class) +public class SharedDataPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private BlobStoreManager mBlobStoreManager; + @Mock + private Preference mPreference; + @Mock + private PreferenceScreen mScreen; + + private SharedDataPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = spy(new SharedDataPreferenceController(mContext)); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + } + + @Test + public void updateState_BlobManagerIsNotNull_preferenceIsEnabled() { + ReflectionHelpers.setField(mController, "mBlobStoreManager", mBlobStoreManager); + mController.updateState(mPreference); + + verify(mPreference).setEnabled(true); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.shared_data_summary)); + } + + @Test + public void updateState_BlobManagerIsNull_preferenceIsDisabled() { + ReflectionHelpers.setField(mController, "mBlobStoreManager", null); + mController.updateState(mPreference); + + verify(mPreference).setEnabled(false); + } +}