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
This commit is contained in:
@@ -1990,6 +1990,11 @@
|
|||||||
android:label="Terms of Service"
|
android:label="Terms of Service"
|
||||||
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
|
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
|
||||||
|
|
||||||
|
<activity android:name=".development.storage.BlobInfoListView"
|
||||||
|
android:label="@string/shared_data_title" />
|
||||||
|
<activity android:name=".development.storage.LeaseInfoListView"
|
||||||
|
android:label="@string/accessor_info_title" />
|
||||||
|
|
||||||
<activity android:name="Settings$WebViewAppPickerActivity"
|
<activity android:name="Settings$WebViewAppPickerActivity"
|
||||||
android:label="@string/select_webview_provider_dialog_title" />
|
android:label="@string/select_webview_provider_dialog_title" />
|
||||||
|
|
||||||
|
50
res/layout/blob_list_item_view.xml
Normal file
50
res/layout/blob_list_item_view.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||||
|
android:padding="@dimen/list_preferred_item_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blob_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textDirection="locale"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blob_id"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/blob_expiry"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
</LinearLayout>
|
52
res/layout/lease_list_item_view.xml
Normal file
52
res/layout/lease_list_item_view.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||||
|
android:padding="@dimen/list_preferred_item_padding">
|
||||||
|
|
||||||
|
<!-- TODO (varunshah@): add an image view for the app icon -->
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lease_package"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textDirection="locale"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lease_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lease_expiry"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
</LinearLayout>
|
31
res/layout/shared_data_empty_list_view.xml
Normal file
31
res/layout/shared_data_empty_list_view.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_view_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||||
|
</LinearLayout>
|
@@ -417,4 +417,7 @@
|
|||||||
<dimen name="developer_option_dialog_margin_top">8dp</dimen>
|
<dimen name="developer_option_dialog_margin_top">8dp</dimen>
|
||||||
<dimen name="developer_option_dialog_min_height">48dp</dimen>
|
<dimen name="developer_option_dialog_min_height">48dp</dimen>
|
||||||
<dimen name="developer_option_dialog_padding_start">16dp</dimen>
|
<dimen name="developer_option_dialog_padding_start">16dp</dimen>
|
||||||
|
|
||||||
|
<!-- Developer options shared data screens related dimensions -->
|
||||||
|
<dimen name="list_preferred_item_padding">16dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -652,4 +652,19 @@
|
|||||||
android:title="@string/autofill_reset_developer_options" />
|
android:title="@string/autofill_reset_developer_options" />
|
||||||
|
|
||||||
</com.android.settings.development.autofill.AutofillPreferenceCategory>
|
</com.android.settings.development.autofill.AutofillPreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="storage_category"
|
||||||
|
android:title="@string/storage_category"
|
||||||
|
android:order="1200">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="shared_data"
|
||||||
|
android:title="@string/shared_data_title"
|
||||||
|
android:summary="@string/shared_data_summary">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="com.android.settings"
|
||||||
|
android:targetClass="com.android.settings.development.storage.BlobInfoListView" />
|
||||||
|
</Preference>
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -52,6 +52,7 @@ import com.android.settings.development.bluetooth.BluetoothCodecDialogPreference
|
|||||||
import com.android.settings.development.bluetooth.BluetoothHDAudioPreferenceController;
|
import com.android.settings.development.bluetooth.BluetoothHDAudioPreferenceController;
|
||||||
import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferenceController;
|
import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferenceController;
|
||||||
import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController;
|
import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController;
|
||||||
|
import com.android.settings.development.storage.SharedDataPreferenceController;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
import com.android.settings.widget.SwitchBar;
|
import com.android.settings.widget.SwitchBar;
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
import com.android.settingslib.core.AbstractPreferenceController;
|
||||||
@@ -531,6 +532,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
|
|||||||
bluetoothA2dpConfigStore));
|
bluetoothA2dpConfigStore));
|
||||||
controllers.add(new BluetoothHDAudioPreferenceController(context, lifecycle,
|
controllers.add(new BluetoothHDAudioPreferenceController(context, lifecycle,
|
||||||
bluetoothA2dpConfigStore, fragment));
|
bluetoothA2dpConfigStore, fragment));
|
||||||
|
controllers.add(new SharedDataPreferenceController(context));
|
||||||
|
|
||||||
return controllers;
|
return controllers;
|
||||||
}
|
}
|
||||||
|
@@ -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<BlobInfo> {
|
||||||
|
BlobListAdapter(Context context) {
|
||||||
|
super(context, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateList(List<BlobInfo> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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<LeaseInfo> {
|
||||||
|
LeaseListAdapter(Context context) {
|
||||||
|
super(context, 0);
|
||||||
|
|
||||||
|
final List<LeaseInfo> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user