Remove the Deletion Helper and Automatic Storage Management jobs.
The automatic storage management settings have been left in place and updated to use the intent to get into the deletion helper. Bug: 28965462 Change-Id: I736c8e741b519eceb89075b74d42b38c3aa5d0f4
This commit is contained in:
@@ -2919,17 +2919,6 @@
|
||||
android:value="true" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="Settings$DeletionHelperActivity"
|
||||
android:label="@string/deletion_helper_title"
|
||||
android:taskAffinity="">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.settings.DELETION_HELPER_SETTINGS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
|
||||
android:value="com.android.settings.deletionhelper.DeletionHelperFragment" />
|
||||
</activity>
|
||||
|
||||
<!-- activity for gesture settings -->
|
||||
<activity android:name="Settings$GestureSettingsActivity"
|
||||
android:label="@string/gesture_preference_title"
|
||||
@@ -3008,21 +2997,6 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- Automatic storage management tasks. -->
|
||||
<service
|
||||
android:name=".deletionhelper.AutomaticStorageManagementJobService"
|
||||
android:label="@string/automatic_storage_manager_service_label"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:enabled="@bool/enable_automatic_storage_management"
|
||||
android:exported="false"/>
|
||||
|
||||
<receiver android:name=".deletionhelper.AutomaticStorageBroadcastReceiver"
|
||||
android:enabled="@bool/enable_automatic_storage_management">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- This is the longest AndroidManifest.xml ever. -->
|
||||
</application>
|
||||
</manifest>
|
||||
|
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
|
||||
</vector>
|
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||
</vector>
|
@@ -41,6 +41,6 @@
|
||||
<!-- Whether none security option is hide or not (country specific). -->
|
||||
<bool name="config_hide_none_security_option">false</bool>
|
||||
|
||||
<!-- Whether the automatic storage management job should be scheduled. -->
|
||||
<bool name="enable_automatic_storage_management">false</bool>
|
||||
<!--Whether the storage manager exists. -->
|
||||
<bool name="config_has_storage_manager">false</bool>
|
||||
</resources>
|
||||
|
@@ -7493,17 +7493,6 @@
|
||||
<!-- [CHAR LIMIT=60] Name of dev option called demo mode -->
|
||||
<string name="demo_mode">Demo mode</string>
|
||||
|
||||
<!-- Activity title for deletion helper. [CHAR LIMIT=25] -->
|
||||
<string name="deletion_helper_title">Remove from Device</string>
|
||||
<!-- Summary of how much storage an app is using and the number of days since last use. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_app_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Last used <xliff:g id="days" example="67">%2$d</xliff:g> days ago</string>
|
||||
<!-- Summary of how much storage an app is using when it has never been used before. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_app_summary_never_used"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Never used before</string>
|
||||
<!-- Summary of how much storage an app is using when its last use is unknown. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_app_summary_unknown_used"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Not sure when last used</string>
|
||||
<!-- Button which clears out storage in the deletion helper. [CHAR LIMIT=60]-->
|
||||
<string name="deletion_helper_free_button">Free up <xliff:g id="freeable" example="1.2GB">%1$s</xliff:g></string>
|
||||
|
||||
<!-- Title text for connecting to customer support [CHAR LIMIT=80]-->
|
||||
<string name="support_escalation_title">Around-the-clock support</string>
|
||||
|
||||
@@ -7598,27 +7587,6 @@
|
||||
<!-- Message for telling the user the kind of BT device being displayed in list. -->
|
||||
<string name="bluetooth_talkback_bluetooth">Bluetooth</string>
|
||||
|
||||
<!-- Preference group title for the photos and videos deletion service. [CHAR LIMIT=40]-->
|
||||
<string name="deletion_helper_photos_title">Photos & Videos (<xliff:g id="num_items">%1$d</xliff:g>)</string>
|
||||
|
||||
<!-- Summary of how much backed up storage that photos and videos service can clear from the local device. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_photos_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Older than <xliff:g id="days">%2$d</xliff:g> days</string>
|
||||
|
||||
<!-- Preference title for the downloads deletion service. [CHAR LIMIT=40]-->
|
||||
<string name="deletion_helper_downloads_title">Downloads (<xliff:g id="numItems" example="67">%1$d</xliff:g>)</string>
|
||||
|
||||
<!-- Summary of how much stale data can be cleared from the local download folder. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_downloads_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Last modified <xliff:g id="days">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Summary for when when there is nothing in the downloads folder to clear. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_downloads_summary_empty"><xliff:g id="used" example="1.2GB">%1$s</xliff:g></string>
|
||||
|
||||
<!-- Message to warn the user before clearing space in the deletion helper. [CHAR LIMIT=NONE] -->
|
||||
<string name="deletion_helper_clear_dialog_message">Remove <xliff:g id="clearable_bytes" example="1.2GB">%1$s</xliff:g> from your device.</string>
|
||||
|
||||
<!-- Button label for the dialog prompt for clearing data in deletion helper. [CHAR LIMIT=40] -->
|
||||
<string name="deletion_helper_clear_dialog_remove">Remove</string>
|
||||
|
||||
<!-- Used as title on the automatic storage manager settings. [CHAR LIMIT=60] -->
|
||||
<string name="automatic_storage_manager_settings">Storage manager</string>
|
||||
|
||||
@@ -7628,21 +7596,6 @@
|
||||
<!-- Dropdown preference title for dropdown describing how many days of data to retain.-->
|
||||
<string name="automatic_storage_manager_days_title">Remove photos & videos</string>
|
||||
|
||||
<!-- Title for the dialog to up sell the storage manager. [CHAR LIMIT=NONE] -->
|
||||
<string name="deletion_helper_upsell_title"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> now free. Manage storage automatically?</string>
|
||||
<!-- Summary for the dialog to up sell the storage manager. [CHAR LIMIT=NONE] -->
|
||||
<string name="deletion_helper_upsell_summary">Let Storage manager automatically free up space by removing backed up content from your device?</string>
|
||||
<!-- Button to delay turning on the storage manager on the storage manager upsell. [CHAR LIMIT=20]-->
|
||||
<string name="deletion_helper_upsell_cancel">No thanks</string>
|
||||
<!-- Button to activate the storage manager on the storage manager upsell. [CHAR LIMIT=20]-->
|
||||
<string name="deletion_helper_upsell_activate">Turn on</string>
|
||||
|
||||
<!-- Title for the apps category in the deletion helper, showing how many apps to delete. [CHAR LIMIT=40]-->
|
||||
<string name="deletion_helper_apps_group_title">Apps (<xliff:g id="num_items">%1$d</xliff:g>)</string>
|
||||
|
||||
<!-- Summary for the apps category in the deletion helper, showing how many space to clear. [CHAR LIMIT=NONE]-->
|
||||
<string name="deletion_helper_apps_group_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> • Last used <xliff:g id="days">%2$d</xliff:g> days ago</string>
|
||||
|
||||
<!-- Category title for the automatic settings in the storage manager settings. [CHAR LIMIT=40] -->
|
||||
<string name="deletion_helper_automatic_title">Automatic</string>
|
||||
|
||||
@@ -7652,12 +7605,6 @@
|
||||
<!-- Preference menu title for accessing the deletion helper from the storage manager settings. [CHAR LIMIT=30]-->
|
||||
<string name="deletion_helper_preference_title">Free space now</string>
|
||||
|
||||
<!-- Preference title for the automatic storage manager toggle. [CHAR LIMIT=60]-->
|
||||
<string name="automatic_storage_manager_preference_title">Storage manager</string>
|
||||
|
||||
<!-- Automatic storage management service label. [CHAR LIMIT=40]-->
|
||||
<string name="automatic_storage_manager_service_label">Automatic Storage Management Service</string>
|
||||
|
||||
<!-- Preference title for gesture settings [CHAR LIMIT=25]-->
|
||||
<string name="gesture_preference_title">Gestures</string>
|
||||
|
||||
|
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/deletion_helper_title">
|
||||
|
||||
<com.android.settings.PhotosDeletionPreference
|
||||
android:key="delete_photos" />
|
||||
|
||||
<com.android.settings.deletionhelper.DownloadsDeletionPreferenceGroup
|
||||
android:key="delete_downloads"
|
||||
android:icon="@drawable/ic_keyboard_arrow_down_black_32"/>
|
||||
|
||||
<com.android.settings.CollapsibleCheckboxPreferenceGroup
|
||||
android:key="apps_group"
|
||||
android:icon="@drawable/ic_keyboard_arrow_down_black_32"/>
|
||||
|
||||
</PreferenceScreen>
|
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* CollapsibleCheckboxPreferenceGroup is a preference group that can be expanded or collapsed and
|
||||
* also has a checkbox.
|
||||
*/
|
||||
public class CollapsibleCheckboxPreferenceGroup extends PreferenceGroup implements
|
||||
View.OnClickListener {
|
||||
private boolean mCollapsed;
|
||||
private boolean mChecked;
|
||||
|
||||
public CollapsibleCheckboxPreferenceGroup(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CollapsibleCheckboxPreferenceGroup(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setWidgetLayoutResource(com.android.settings.R.layout.preference_widget_checkbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
View checkbox = holder.findViewById(com.android.internal.R.id.checkbox);
|
||||
if (checkbox != null && checkbox instanceof Checkable) {
|
||||
((Checkable) checkbox).setChecked(mChecked);
|
||||
checkbox.setClickable(true);
|
||||
checkbox.setFocusable(true);
|
||||
checkbox.setOnClickListener(this);
|
||||
}
|
||||
|
||||
final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
|
||||
if (titleView != null) {
|
||||
Context context = getContext();
|
||||
TypedValue value = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
|
||||
titleView.setTextColor(context.getColor(value.resourceId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPreference(Preference p) {
|
||||
super.addPreference(p);
|
||||
p.setVisible(!isCollapsed());
|
||||
return true;
|
||||
}
|
||||
|
||||
// The preference click handler.
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
setCollapse(!isCollapsed());
|
||||
}
|
||||
|
||||
// The checkbox view click handler.
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setChecked(!isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the view is collapsed.
|
||||
*/
|
||||
public boolean isCollapsed() {
|
||||
return mCollapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checked state of the preference.
|
||||
*/
|
||||
public boolean isChecked() {
|
||||
return mChecked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked state and notifies listeners of the state change.
|
||||
*/
|
||||
public void setChecked(boolean checked) {
|
||||
if (mChecked != checked) {
|
||||
mChecked = checked;
|
||||
|
||||
callChangeListener(checked);
|
||||
notifyDependencyChange(shouldDisableDependents());
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void setCollapse(boolean isCollapsed) {
|
||||
if (mCollapsed == isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCollapsed = isCollapsed;
|
||||
if (isCollapsed) {
|
||||
hideDropdownPreferences();
|
||||
} else {
|
||||
showDropdownPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
private void showDropdownPreferences() {
|
||||
setAllPreferencesVisibility(true);
|
||||
setIcon(R.drawable.ic_keyboard_arrow_down_black_32);
|
||||
}
|
||||
|
||||
private void hideDropdownPreferences() {
|
||||
setAllPreferencesVisibility(false);
|
||||
setIcon(R.drawable.ic_keyboard_arrow_up_black_32);
|
||||
}
|
||||
|
||||
private void setAllPreferencesVisibility(boolean visible) {
|
||||
for (int i = 0; i < getPreferenceCount(); i++) {
|
||||
Preference p = getPreference(i);
|
||||
p.setVisible(visible);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.text.format.Formatter;
|
||||
import android.widget.TextView;
|
||||
import com.android.settings.deletionhelper.DeletionType;
|
||||
|
||||
/**
|
||||
* Preference to handle the deletion of various data types in the Deletion Helper.
|
||||
*/
|
||||
public abstract class DeletionPreference extends CheckBoxPreference implements
|
||||
DeletionType.FreeableChangedListener, OnPreferenceChangeListener {
|
||||
private DeletionType.FreeableChangedListener mListener;
|
||||
private boolean mChecked;
|
||||
private long mFreeableBytes;
|
||||
private int mFreeableItems;
|
||||
private DeletionType mDeletionService;
|
||||
|
||||
public DeletionPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
|
||||
if (titleView != null) {
|
||||
titleView.setTextColor(getTintColor(getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes which can be cleared by the deletion service.
|
||||
* @return The number of bytes.
|
||||
*/
|
||||
public long getFreeableBytes() {
|
||||
return mChecked ? mFreeableBytes : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener to be called back on when the freeable bytes have changed.
|
||||
* @param listener The callback listener.
|
||||
*/
|
||||
public void registerFreeableChangedListener(DeletionType.FreeableChangedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a deletion service to update the preference's information.
|
||||
* @param deletionService A photo/video deletion service.
|
||||
*/
|
||||
public void registerDeletionService(DeletionType deletionService) {
|
||||
mDeletionService = deletionService;
|
||||
if (mDeletionService != null) {
|
||||
mDeletionService.registerFreeableChangedListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the deletion service powering the preference.
|
||||
* @return The deletion service.
|
||||
*/
|
||||
public DeletionType getDeletionService() {
|
||||
return mDeletionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFreeableChanged(int numItems, long freeableBytes) {
|
||||
mFreeableItems = numItems;
|
||||
mFreeableBytes = freeableBytes;
|
||||
maybeUpdateListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mChecked = (boolean) newValue;
|
||||
maybeUpdateListener();
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getTintColor(Context context) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
|
||||
return context.getColor(value.resourceId);
|
||||
}
|
||||
|
||||
private void maybeUpdateListener() {
|
||||
if (mListener != null) {
|
||||
mListener.onFreeableChanged(mFreeableItems, getFreeableBytes());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.text.format.Formatter;
|
||||
|
||||
/**
|
||||
* Preference to handle the deletion of photos and videos in the Deletion Helper.
|
||||
*/
|
||||
public class PhotosDeletionPreference extends DeletionPreference {
|
||||
public static final int DAYS_TO_KEEP = 30;
|
||||
|
||||
public PhotosDeletionPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setIcon(getIcon(context));
|
||||
updatePreferenceText(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the title and summary of the preference with fresh information.
|
||||
*/
|
||||
public void updatePreferenceText(int items, long bytes) {
|
||||
Context context = getContext();
|
||||
setTitle(context.getString(R.string.deletion_helper_photos_title, items));
|
||||
setSummary(context.getString(R.string.deletion_helper_photos_summary,
|
||||
Formatter.formatFileSize(context, bytes), DAYS_TO_KEEP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFreeableChanged(int items, long bytes) {
|
||||
super.onFreeableChanged(items, bytes);
|
||||
updatePreferenceText(items, bytes);
|
||||
}
|
||||
|
||||
private Drawable getIcon(Context context) {
|
||||
final Drawable iconDrawable;
|
||||
try {
|
||||
Resources resources = context.getResources();
|
||||
final int resId = resources.getIdentifier("ic_photos_black_24", "drawable",
|
||||
context.getPackageName());
|
||||
iconDrawable = context.getDrawable(resId);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
return iconDrawable;
|
||||
}
|
||||
}
|
@@ -74,7 +74,6 @@ import com.android.settings.bluetooth.BluetoothSettings;
|
||||
import com.android.settings.dashboard.DashboardContainerFragment;
|
||||
import com.android.settings.dashboard.SearchResultsSummary;
|
||||
import com.android.settings.datausage.DataUsageSummary;
|
||||
import com.android.settings.deletionhelper.DeletionHelperFragment;
|
||||
import com.android.settings.deviceinfo.ImeiInformation;
|
||||
import com.android.settings.deviceinfo.PrivateVolumeForget;
|
||||
import com.android.settings.deviceinfo.PrivateVolumeSettings;
|
||||
@@ -343,7 +342,6 @@ public class SettingsActivity extends SettingsDrawerActivity
|
||||
WallpaperTypeSettings.class.getName(),
|
||||
VrListenerSettings.class.getName(),
|
||||
ManagedProfileSettings.class.getName(),
|
||||
DeletionHelperFragment.class.getName(),
|
||||
ChooseAccountActivity.class.getName(),
|
||||
IccLockSettings.class.getName(),
|
||||
ImeiInformation.class.getName(),
|
||||
|
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.text.format.Formatter;
|
||||
import android.view.View;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import com.android.settings.deletionhelper.AppStateUsageStatsBridge.UsageStatsState;
|
||||
import com.android.settings.R;
|
||||
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
|
||||
/**
|
||||
* Preference item for an app with a switch to signify if it should be uninstalled.
|
||||
* This shows the name and icon of the app along with the days since its last use.
|
||||
*/
|
||||
public class AppDeletionPreference extends SwitchPreference {
|
||||
private AppEntry mEntry;
|
||||
private Context mContext;
|
||||
|
||||
public AppDeletionPreference(Context context, AppEntry item, ApplicationsState state) {
|
||||
super(context);
|
||||
mEntry = item;
|
||||
mContext = context;
|
||||
setLayoutResource(com.android.settings.R.layout.preference_app);
|
||||
setWidgetLayoutResource(R.layout.widget_text_views);
|
||||
|
||||
synchronized (item) {
|
||||
state.ensureIcon(item);
|
||||
if (item.icon != null)
|
||||
setIcon(item.icon);
|
||||
if (item.label != null)
|
||||
setTitle(item.label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
Switch switchWidget = (Switch) holder.findViewById(com.android.internal.R.id.switch_widget);
|
||||
switchWidget.setVisibility(View.VISIBLE);
|
||||
|
||||
TextView summary = (TextView) holder.findViewById(R.id.widget_text1);
|
||||
updateSummaryText(summary);
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mEntry.label;
|
||||
}
|
||||
|
||||
private void updateSummaryText(TextView summary) {
|
||||
if (mEntry.extraInfo == null) return;
|
||||
if (mEntry.size == ApplicationsState.SIZE_UNKNOWN ||
|
||||
mEntry.size == ApplicationsState.SIZE_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
UsageStatsState extraData = (UsageStatsState) mEntry.extraInfo;
|
||||
String fileSize = Formatter.formatFileSize(mContext, mEntry.size);
|
||||
if (extraData.daysSinceLastUse == AppStateUsageStatsBridge.NEVER_USED) {
|
||||
summary.setText(mContext.getString(R.string.deletion_helper_app_summary_never_used,
|
||||
fileSize));
|
||||
} else if (extraData.daysSinceLastUse == AppStateUsageStatsBridge.UNKNOWN_LAST_USE) {
|
||||
summary.setText(mContext.getString(R.string.deletion_helper_app_summary_unknown_used,
|
||||
fileSize));
|
||||
} else {
|
||||
summary.setText(mContext.getString(R.string.deletion_helper_app_summary,
|
||||
fileSize,
|
||||
extraData.daysSinceLastUse));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.usage.UsageStats;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
import com.android.settings.applications.AppStateBaseBridge;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppFilter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Connects data from the UsageStatsManager to the ApplicationsState.
|
||||
*/
|
||||
public class AppStateUsageStatsBridge extends AppStateBaseBridge {
|
||||
private static final String TAG = "AppStateUsageStatsBridge";
|
||||
private UsageStatsManager mUsageStatsManager;
|
||||
private PackageManager mPm;
|
||||
public static final long NEVER_USED = -1;
|
||||
public static final long UNKNOWN_LAST_USE = -2;
|
||||
public static final long UNUSED_DAYS_DELETION_THRESHOLD = 60;
|
||||
|
||||
public AppStateUsageStatsBridge(Context context, ApplicationsState appState,
|
||||
Callback callback) {
|
||||
super(appState, callback);
|
||||
mUsageStatsManager =
|
||||
(UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
||||
mPm = context.getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAllExtraInfo() {
|
||||
ArrayList<AppEntry> apps = mAppSession.getAllApps();
|
||||
if (apps == null) return;
|
||||
|
||||
final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(0,
|
||||
System.currentTimeMillis());
|
||||
for (AppEntry entry : apps) {
|
||||
UsageStats usageStats = map.get(entry.info.packageName);
|
||||
entry.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats),
|
||||
getDaysSinceInstalled(entry.info.packageName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
|
||||
Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(0,
|
||||
System.currentTimeMillis());
|
||||
UsageStats usageStats = map.get(app.info.packageName);
|
||||
app.extraInfo = new UsageStatsState(getDaysSinceLastUse(usageStats),
|
||||
getDaysSinceInstalled(app.info.packageName));
|
||||
}
|
||||
|
||||
private long getDaysSinceLastUse(UsageStats stats) {
|
||||
if (stats == null) {
|
||||
return NEVER_USED;
|
||||
}
|
||||
long lastUsed = stats.getLastTimeUsed();
|
||||
// Sometimes, a usage is recorded without a time and we don't know when the use was.
|
||||
if (lastUsed == 0) {
|
||||
return UNKNOWN_LAST_USE;
|
||||
}
|
||||
return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUsed);
|
||||
}
|
||||
|
||||
private long getDaysSinceInstalled(String packageName) {
|
||||
PackageInfo pi = null;
|
||||
try {
|
||||
pi = mPm.getPackageInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, packageName + " was not found.");
|
||||
}
|
||||
|
||||
if (pi == null) {
|
||||
return NEVER_USED;
|
||||
}
|
||||
|
||||
return (TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - pi.firstInstallTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters only non-system apps which haven't been used in the last 60 days. If an app's last
|
||||
* usage is unknown, it is skipped.
|
||||
*/
|
||||
public static final AppFilter FILTER_USAGE_STATS = new AppFilter() {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filterApp(AppEntry info) {
|
||||
if (info == null) return false;
|
||||
return isExtraInfoValid(info.extraInfo) && !isBundled(info)
|
||||
&& !isPersistentProcess(info);
|
||||
}
|
||||
|
||||
private boolean isExtraInfoValid(Object extraInfo) {
|
||||
if (extraInfo == null || !(extraInfo instanceof UsageStatsState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UsageStatsState state = (UsageStatsState) extraInfo;
|
||||
long mostRecentUse = Math.max(state.daysSinceFirstInstall, state.daysSinceLastUse);
|
||||
return mostRecentUse >= UNUSED_DAYS_DELETION_THRESHOLD || mostRecentUse == NEVER_USED;
|
||||
}
|
||||
|
||||
private boolean isBundled(AppEntry info) {
|
||||
return (info.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||
}
|
||||
|
||||
private boolean isPersistentProcess(AppEntry info) {
|
||||
return (info.info.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* UsageStatsState contains the days since the last use and first install of a given app.
|
||||
*/
|
||||
public static class UsageStatsState {
|
||||
public long daysSinceLastUse;
|
||||
public long daysSinceFirstInstall;
|
||||
|
||||
public UsageStatsState(long daysSinceLastUse, long daysSinceFirstInstall) {
|
||||
this.daysSinceLastUse = daysSinceLastUse;
|
||||
this.daysSinceFirstInstall = daysSinceFirstInstall;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
/**
|
||||
* A {@link BroadcastReceiver} listening for {@link Intent#ACTION_BOOT_COMPLETED} broadcasts to
|
||||
* schedule an automatic storage management job. Automatic storage management jobs are only
|
||||
* scheduled once a day for a plugged in device.
|
||||
*/
|
||||
public class AutomaticStorageBroadcastReceiver extends BroadcastReceiver {
|
||||
private static final int AUTOMATIC_STORAGE_JOB_ID = 0;
|
||||
private static final long PERIOD = DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
JobScheduler jobScheduler =
|
||||
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
ComponentName component = new ComponentName(context,
|
||||
AutomaticStorageManagementJobService.class);
|
||||
JobInfo job = new JobInfo.Builder(AUTOMATIC_STORAGE_JOB_ID, component)
|
||||
.setRequiresCharging(true)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.setPeriodic(PERIOD)
|
||||
.build();
|
||||
jobScheduler.schedule(job);
|
||||
}
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.VolumeInfo;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.overlay.StorageManagementJobProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* {@link JobService} class to start automatic storage clearing jobs to free up space. The job only
|
||||
* starts if the device is under a certain percent of free storage.
|
||||
*/
|
||||
public class AutomaticStorageManagementJobService extends JobService {
|
||||
private static final String TAG = "AsmJobService";
|
||||
private static final String SHARED_PREFRENCES_NAME = "automatic_storage_manager_settings";
|
||||
private static final String KEY_DAYS_TO_RETAIN = "days_to_retain";
|
||||
|
||||
private static final long DEFAULT_LOW_FREE_PERCENT = 15;
|
||||
|
||||
private StorageManagementJobProvider mProvider;
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters args) {
|
||||
boolean isEnabled =
|
||||
Settings.Secure.getInt(getContentResolver(),
|
||||
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, 0) != 0;
|
||||
if (!isEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StorageManager manager = getSystemService(StorageManager.class);
|
||||
VolumeInfo internalVolume = manager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
|
||||
|
||||
final File dataPath = internalVolume.getPath();
|
||||
if (!volumeNeedsManagement(dataPath)) {
|
||||
Log.i(TAG, "Skipping automatic storage management.");
|
||||
return false;
|
||||
}
|
||||
mProvider = FeatureFactory.getFactory(this).getStorageManagementJobProvider();
|
||||
if (mProvider != null) {
|
||||
return mProvider.onStartJob(this, args, getDaysToRetain());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters args) {
|
||||
if (mProvider != null) {
|
||||
return mProvider.onStopJob(this, args);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getDaysToRetain() {
|
||||
SharedPreferences sharedPreferences =
|
||||
getSharedPreferences(SHARED_PREFRENCES_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getInt(KEY_DAYS_TO_RETAIN,
|
||||
AutomaticStorageManagerSettings.DEFAULT_DAYS_TO_RETAIN);
|
||||
}
|
||||
|
||||
private boolean volumeNeedsManagement(final File dataPath) {
|
||||
long lowStorageThreshold = (dataPath.getTotalSpace() * DEFAULT_LOW_FREE_PERCENT) / 100;
|
||||
return dataPath.getFreeSpace() < lowStorageThreshold;
|
||||
}
|
||||
}
|
@@ -18,6 +18,7 @@ package com.android.settings.deletionhelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
@@ -115,8 +116,8 @@ public class AutomaticStorageManagerSettings extends SettingsPreferenceFragment
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (KEY_DELETION_HELPER.equals(preference.getKey())) {
|
||||
startFragment(this, DeletionHelperFragment.class.getCanonicalName(),
|
||||
R.string.deletion_helper_title, 0, null);
|
||||
Intent intent = new Intent(Settings.ACTION_DELETION_HELPER_SETTINGS);
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.Formatter;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Fragment used to confirm that the user wishes to delete a certain amount of data.
|
||||
*/
|
||||
public class ConfirmDeletionDialog extends DialogFragment implements
|
||||
DialogInterface.OnClickListener {
|
||||
public static final String TAG = "ConfirmDeletionDialog";
|
||||
private static final String ARG_TOTAL_SPACE = "total_freeable";
|
||||
|
||||
public static ConfirmDeletionDialog newInstance(long freeableBytes) {
|
||||
Bundle args = new Bundle(1);
|
||||
args.putLong(ARG_TOTAL_SPACE, freeableBytes);
|
||||
|
||||
ConfirmDeletionDialog dialog = new ConfirmDeletionDialog();
|
||||
dialog.setArguments(args);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
long totalFreeableSpace = args.getLong(ARG_TOTAL_SPACE);
|
||||
|
||||
final Context context = getContext();
|
||||
return new AlertDialog.Builder(context)
|
||||
.setMessage(context.getString(R.string.deletion_helper_clear_dialog_message,
|
||||
Formatter.formatFileSize(context, totalFreeableSpace)))
|
||||
.setPositiveButton(R.string.deletion_helper_clear_dialog_remove, this)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
((DeletionHelperFragment) getTargetFragment()).clearData();
|
||||
}
|
||||
}
|
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import com.android.settings.CollapsibleCheckboxPreferenceGroup;
|
||||
import com.android.settings.PhotosDeletionPreference;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
import com.android.settings.R;
|
||||
import com.android.internal.logging.MetricsProto.MetricsEvent;
|
||||
import com.android.settings.applications.AppStateBaseBridge;
|
||||
import com.android.settings.overlay.DeletionHelperFeatureProvider;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.applications.ApplicationsState.Callbacks;
|
||||
import com.android.settingslib.applications.ApplicationsState.Session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Settings screen for the deletion helper, which manually removes data which is not recently used.
|
||||
*/
|
||||
public class DeletionHelperFragment extends SettingsPreferenceFragment implements
|
||||
ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
|
||||
Preference.OnPreferenceChangeListener, DeletionType.FreeableChangedListener,
|
||||
View.OnClickListener {
|
||||
public static final int CLEAR_DATA_RESULT = 1;
|
||||
public static final String FREED_BYTES_KEY = "freed";
|
||||
|
||||
private static final String TAG = "DeletionHelperFragment";
|
||||
|
||||
private static final String EXTRA_HAS_BRIDGE = "hasBridge";
|
||||
private static final String EXTRA_HAS_SIZES = "hasSizes";
|
||||
private static final String EXTRA_CHECKED_SET = "checkedSet";
|
||||
|
||||
private static final String KEY_APPS_GROUP = "apps_group";
|
||||
private static final String KEY_PHOTOS_VIDEOS_PREFERENCE = "delete_photos";
|
||||
private static final String KEY_DOWNLOADS_PREFERENCE = "delete_downloads";
|
||||
|
||||
private static final int DOWNLOADS_LOADER_ID = 1;
|
||||
|
||||
private Button mCancel, mFree;
|
||||
private CollapsibleCheckboxPreferenceGroup mApps;
|
||||
private PhotosDeletionPreference mPhotoPreference;
|
||||
private DownloadsDeletionPreferenceGroup mDownloadsPreference;
|
||||
|
||||
private ApplicationsState mState;
|
||||
private Session mSession;
|
||||
private HashSet<String> mCheckedApplications;
|
||||
private AppStateUsageStatsBridge mDataUsageBridge;
|
||||
private ArrayList<AppEntry> mAppEntries;
|
||||
private boolean mHasReceivedAppEntries, mHasReceivedBridgeCallback, mFinishedLoading;
|
||||
private DeletionHelperFeatureProvider mProvider;
|
||||
private DeletionType mPhotoVideoDeletion;
|
||||
private DownloadsDeletionType mDownloadsDeletion;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setAnimationAllowed(true);
|
||||
Application app = getActivity().getApplication();
|
||||
mState = ApplicationsState.getInstance(app);
|
||||
mSession = mState.newSession(this);
|
||||
mCheckedApplications = new HashSet<>();
|
||||
mDataUsageBridge = new AppStateUsageStatsBridge(getActivity(), mState, this);
|
||||
|
||||
addPreferencesFromResource(R.xml.deletion_helper_list);
|
||||
mApps = (CollapsibleCheckboxPreferenceGroup) findPreference(KEY_APPS_GROUP);
|
||||
mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE);
|
||||
mDownloadsPreference =
|
||||
(DownloadsDeletionPreferenceGroup) findPreference(KEY_DOWNLOADS_PREFERENCE);
|
||||
mProvider =
|
||||
FeatureFactory.getFactory(app).getDeletionHelperFeatureProvider();
|
||||
if (mProvider != null) {
|
||||
mPhotoVideoDeletion = mProvider.createPhotoVideoDeletionType(getContext());
|
||||
}
|
||||
mDownloadsDeletion = new DownloadsDeletionType(getActivity());
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mHasReceivedAppEntries =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_SIZES, false);
|
||||
mHasReceivedBridgeCallback =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
|
||||
mCheckedApplications =
|
||||
(HashSet<String>) savedInstanceState.getSerializable(EXTRA_CHECKED_SET);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeButtons(View v) {
|
||||
mCancel = (Button) v.findViewById(R.id.skip_button);
|
||||
mCancel.setText(R.string.cancel);
|
||||
mCancel.setOnClickListener(this);
|
||||
mCancel.setVisibility(View.VISIBLE);
|
||||
|
||||
mFree = (Button) v.findViewById(R.id.next_button);
|
||||
mFree.setText(R.string.storage_menu_free);
|
||||
mFree.setOnClickListener(this);
|
||||
|
||||
Button back = (Button) v.findViewById(R.id.back_button);
|
||||
back.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initializeDeletionPreferences() {
|
||||
if (mProvider == null) {
|
||||
getPreferenceScreen().removePreference(mPhotoPreference);
|
||||
mPhotoPreference = null;
|
||||
} else {
|
||||
mPhotoPreference.registerFreeableChangedListener(this);
|
||||
mPhotoPreference.registerDeletionService(mPhotoVideoDeletion);
|
||||
}
|
||||
|
||||
mDownloadsPreference.registerFreeableChangedListener(this);
|
||||
mDownloadsPreference.registerDeletionService(mDownloadsDeletion);
|
||||
mApps.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View v, Bundle savedInstanceState) {
|
||||
super.onViewCreated(v, savedInstanceState);
|
||||
initializeButtons(v);
|
||||
initializeDeletionPreferences();
|
||||
setLoading(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mSession.resume();
|
||||
mDataUsageBridge.resume();
|
||||
mDownloadsDeletion.onResume();
|
||||
getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);
|
||||
|
||||
if (mPhotoVideoDeletion != null) {
|
||||
mPhotoVideoDeletion.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(EXTRA_HAS_SIZES, mHasReceivedAppEntries);
|
||||
outState.putBoolean(EXTRA_HAS_BRIDGE, mHasReceivedBridgeCallback);
|
||||
outState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mDataUsageBridge.pause();
|
||||
mSession.pause();
|
||||
mDownloadsDeletion.onPause();
|
||||
|
||||
if (mPhotoVideoDeletion != null) {
|
||||
mPhotoVideoDeletion.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuild() {
|
||||
// Only rebuild if we have the packages and their usage stats.
|
||||
if (!mHasReceivedBridgeCallback || !mHasReceivedAppEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<AppEntry> apps =
|
||||
mSession.rebuild(AppStateUsageStatsBridge.FILTER_USAGE_STATS,
|
||||
ApplicationsState.SIZE_COMPARATOR);
|
||||
if (apps == null) return;
|
||||
mAppEntries = apps;
|
||||
refreshAppGroup(apps);
|
||||
|
||||
// All applications should be filled in if we've received the sizes.
|
||||
// setLoading being called multiple times causes flickering, so we only do it once.
|
||||
if (mHasReceivedAppEntries && !mFinishedLoading) {
|
||||
mFinishedLoading = true;
|
||||
setLoading(false, true);
|
||||
getButtonBar().setVisibility(View.VISIBLE);
|
||||
}
|
||||
updateFreeButtonText();
|
||||
}
|
||||
|
||||
private void updateFreeButtonText() {
|
||||
mFree.setText(String.format(getActivity().getString(R.string.deletion_helper_free_button),
|
||||
Formatter.formatFileSize(getActivity(), getTotalFreeableSpace())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningStateChanged(boolean running) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageListChanged() {
|
||||
rebuild();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRebuildComplete(ArrayList<AppEntry> apps) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageIconChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageSizeChanged(String packageName) {
|
||||
rebuild();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllSizesComputed() {
|
||||
rebuild();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherInfoChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadEntriesCompleted() {
|
||||
mHasReceivedAppEntries = true;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExtraInfoUpdated() {
|
||||
mHasReceivedBridgeCallback = true;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMetricsCategory() {
|
||||
return MetricsEvent.DEVICEINFO_STORAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
boolean checked = (boolean) newValue;
|
||||
if (preference.getKey().equals(mApps.getKey())) {
|
||||
return toggleAllApps(checked);
|
||||
}
|
||||
|
||||
String packageName = ((AppDeletionPreference) preference).getPackageName();
|
||||
if (checked) {
|
||||
mCheckedApplications.add(packageName);
|
||||
} else {
|
||||
mCheckedApplications.remove(packageName);
|
||||
|
||||
// We remove the preference change listener to avoid toggling every app on and off.
|
||||
mApps.setOnPreferenceChangeListener(null);
|
||||
mApps.setChecked(false);
|
||||
mApps.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
updateFreeButtonText();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFreeableChanged(int numItems, long freeableBytes) {
|
||||
updateFreeButtonText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == mFree.getId()) {
|
||||
ConfirmDeletionDialog dialog =
|
||||
ConfirmDeletionDialog.newInstance(getTotalFreeableSpace());
|
||||
// The 0 is a placeholder for an optional result code.
|
||||
dialog.setTargetFragment(this, 0);
|
||||
dialog.show(getFragmentManager(), ConfirmDeletionDialog.TAG);
|
||||
} else {
|
||||
finishFragment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out the selected apps and data from the device and closes the fragment.
|
||||
*/
|
||||
protected void clearData() {
|
||||
// This should be fine as long as there is only one extra deletion feature.
|
||||
// In the future, this should be done in an async queue in order to not
|
||||
// interfere with the simultaneous PackageDeletionTask.
|
||||
if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
|
||||
mPhotoVideoDeletion.clearFreeableData();
|
||||
}
|
||||
mDownloadsDeletion.clearFreeableData();
|
||||
|
||||
ArraySet<String> apps = new ArraySet<>();
|
||||
for (AppEntry entry : mAppEntries) {
|
||||
if (mCheckedApplications.contains(entry.label)) {
|
||||
synchronized (entry) {
|
||||
apps.add(entry.info.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: If needed, add an action on the callback.
|
||||
PackageDeletionTask task = new PackageDeletionTask(getActivity().getPackageManager(), apps,
|
||||
new PackageDeletionTask.Callback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
Log.e(TAG, "An error occurred while uninstalling packages.");
|
||||
}
|
||||
});
|
||||
Intent data = new Intent();
|
||||
data.putExtra(FREED_BYTES_KEY, getTotalFreeableSpace());
|
||||
getActivity().setResult(CLEAR_DATA_RESULT, data);
|
||||
|
||||
task.run();
|
||||
finishFragment();
|
||||
}
|
||||
|
||||
private long getTotalFreeableSpace() {
|
||||
long freeableSpace = 0;
|
||||
freeableSpace += getTotalAppsFreeableSpace(false);
|
||||
if (mPhotoPreference != null) {
|
||||
freeableSpace += mPhotoPreference.getFreeableBytes();
|
||||
}
|
||||
freeableSpace += mDownloadsDeletion.getFreeableBytes();
|
||||
return freeableSpace;
|
||||
}
|
||||
|
||||
private void refreshAppGroup(ArrayList<AppEntry> apps) {
|
||||
int entryCount = apps.size();
|
||||
cacheRemoveAllPrefs(mApps);
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
AppEntry entry = apps.get(i);
|
||||
final String packageName = entry.label;
|
||||
AppDeletionPreference preference =
|
||||
(AppDeletionPreference) getCachedPreference(entry.label);
|
||||
if (preference == null) {
|
||||
preference = new AppDeletionPreference(getActivity(), entry, mState);
|
||||
preference.setKey(packageName);
|
||||
preference.setOnPreferenceChangeListener(this);
|
||||
mApps.addPreference(preference);
|
||||
}
|
||||
preference.setChecked(mCheckedApplications.contains(packageName));
|
||||
preference.setOrder(i);
|
||||
}
|
||||
removeCachedPrefs(mApps);
|
||||
updateAppsGroupText();
|
||||
}
|
||||
|
||||
private long getTotalAppsFreeableSpace(boolean countUnchecked) {
|
||||
long freeableSpace = 0;
|
||||
if (mAppEntries != null) {
|
||||
for (int i = 0; i < mAppEntries.size(); i++) {
|
||||
final AppEntry entry = mAppEntries.get(i);
|
||||
long entrySize = mAppEntries.get(i).size;
|
||||
// If the entrySize is negative, it is either an unknown size or an error occurred.
|
||||
if ((countUnchecked ||
|
||||
mCheckedApplications.contains(entry.label)) && entrySize > 0) {
|
||||
freeableSpace += entrySize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return freeableSpace;
|
||||
}
|
||||
|
||||
private void updateAppsGroupText() {
|
||||
if (mAppEntries != null) {
|
||||
Activity app = getActivity();
|
||||
mApps.setTitle(app.getString(R.string.deletion_helper_apps_group_title,
|
||||
mAppEntries.size()));
|
||||
mApps.setSummary(app.getString(R.string.deletion_helper_apps_group_summary,
|
||||
Formatter.formatFileSize(app,
|
||||
getTotalAppsFreeableSpace(true)),
|
||||
AppStateUsageStatsBridge.UNUSED_DAYS_DELETION_THRESHOLD));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean toggleAllApps(boolean checked) {
|
||||
for (AppEntry entry : mAppEntries) {
|
||||
final String packageName = entry.label;
|
||||
if (checked) {
|
||||
mCheckedApplications.add(packageName);
|
||||
} else {
|
||||
mCheckedApplications.remove(packageName);
|
||||
}
|
||||
}
|
||||
refreshAppGroup(mAppEntries);
|
||||
updateFreeButtonText();
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
/**
|
||||
* Helper for the Deletion Helper which can query, clear out, and visualize deletable data.
|
||||
* This could represent a helper for deleting photos, downloads, movies, etc.
|
||||
*/
|
||||
public interface DeletionType {
|
||||
/**
|
||||
* Registers a callback to call when the amount of freeable space is updated.
|
||||
* @param listener A callback.
|
||||
*/
|
||||
void registerFreeableChangedListener(FreeableChangedListener listener);
|
||||
|
||||
/**
|
||||
* Resumes an operation, intended to be called when the deletion fragment resumes.
|
||||
*/
|
||||
void onResume();
|
||||
|
||||
/**
|
||||
* Pauses the feature's operations, intended to be called when the deletion fragment is paused.
|
||||
*/
|
||||
void onPause();
|
||||
|
||||
/**
|
||||
* Asynchronously free up the freeable information for the feature.
|
||||
*/
|
||||
void clearFreeableData();
|
||||
|
||||
/**
|
||||
* Callback interface to listen for when a deletion feature's amount of freeable space updates.
|
||||
*/
|
||||
interface FreeableChangedListener {
|
||||
void onFreeableChanged(int numItems, long bytesFreeable);
|
||||
}
|
||||
}
|
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import com.android.settings.CollapsibleCheckboxPreferenceGroup;
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* DownloadsDeletionPreferenceGroup defines a checkable preference group which contains
|
||||
* downloads file deletion preferences.
|
||||
*/
|
||||
public class DownloadsDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup
|
||||
implements DeletionType.FreeableChangedListener, Preference.OnPreferenceChangeListener {
|
||||
private DownloadsDeletionType mDeletionType;
|
||||
private DeletionType.FreeableChangedListener mListener;
|
||||
|
||||
public DownloadsDeletionPreferenceGroup(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public DownloadsDeletionPreferenceGroup(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setOrderingAsAdded(false);
|
||||
setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a deletion type to get info for the preference group.
|
||||
* @param type A {@link DownloadsDeletionType}.
|
||||
*/
|
||||
public void registerDeletionService(DownloadsDeletionType type) {
|
||||
mDeletionType = type;
|
||||
mDeletionType.registerFreeableChangedListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when the amount of freeable space updates.
|
||||
* @param listener The callback listener.
|
||||
*/
|
||||
public void registerFreeableChangedListener(DeletionType.FreeableChangedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFreeableChanged(int numItems, long freeableBytes) {
|
||||
updatePreferenceText(numItems, freeableBytes, mDeletionType.getMostRecentLastModified());
|
||||
maybeUpdateListener(numItems, freeableBytes);
|
||||
updateFiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
boolean checked = (boolean) newValue;
|
||||
if (!checked) {
|
||||
// Temporarily stop listening to avoid propagating the checked change to children.
|
||||
setOnPreferenceChangeListener(null);
|
||||
setChecked(false);
|
||||
setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
// If the group checkbox changed, we need to toggle every child preference.
|
||||
if (preference == this) {
|
||||
for (int i = 0; i < getPreferenceCount(); i++) {
|
||||
DownloadsFilePreference p = (DownloadsFilePreference) getPreference(i);
|
||||
p.setOnPreferenceChangeListener(null);
|
||||
mDeletionType.toggleFile(p.getFile(), checked);
|
||||
p.setChecked(checked);
|
||||
p.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
maybeUpdateListener(mDeletionType.getFiles().size(), mDeletionType.getFreeableBytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a single DownloadFilePreference changed, we need to toggle just itself.
|
||||
DownloadsFilePreference p = (DownloadsFilePreference) preference;
|
||||
mDeletionType.toggleFile(p.getFile(), checked);
|
||||
maybeUpdateListener(mDeletionType.getFiles().size(), mDeletionType.getFreeableBytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void updatePreferenceText(int itemCount, long bytes, long mostRecent) {
|
||||
Context context = getContext();
|
||||
setTitle(context.getString(R.string.deletion_helper_downloads_title, itemCount));
|
||||
// If there are no files to clear, show the empty text instead.
|
||||
if (itemCount != 0) {
|
||||
setSummary(context.getString(R.string.deletion_helper_downloads_summary,
|
||||
Formatter.formatFileSize(context, bytes),
|
||||
DateUtils.getRelativeTimeSpanString(mostRecent,
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE)));
|
||||
} else {
|
||||
setSummary(context.getString(R.string.deletion_helper_downloads_summary_empty,
|
||||
Formatter.formatFileSize(context, bytes)));
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateListener(int numItems, long bytesFreeable) {
|
||||
if (mListener != null) {
|
||||
mListener.onFreeableChanged(numItems, bytesFreeable);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFiles() {
|
||||
// TODO: Remove impl overlap with the cached preferences methods in
|
||||
// SettingsPreferenceFragment.
|
||||
|
||||
// Cache the existing file preferences.
|
||||
ArrayMap<String, Preference> cachedPreferences = new ArrayMap<>();
|
||||
for (int i = 0; i < getPreferenceCount(); i++) {
|
||||
Preference p = getPreference(i);
|
||||
cachedPreferences.put(p.getKey(), p);
|
||||
}
|
||||
|
||||
// Iterate over all of the files and re-use the old file preference, if it exists.
|
||||
Set<File> files = mDeletionType.getFiles();
|
||||
for (File file : files) {
|
||||
DownloadsFilePreference filePreference =
|
||||
(DownloadsFilePreference) cachedPreferences.remove(file.getPath());
|
||||
if (filePreference == null) {
|
||||
filePreference = new DownloadsFilePreference(getContext(), file);
|
||||
filePreference.setChecked(isChecked());
|
||||
filePreference.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
addPreference(filePreference);
|
||||
}
|
||||
|
||||
// Remove all of the unused preferences.
|
||||
for (Preference p : cachedPreferences.values()) {
|
||||
removePreference(p);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.LoaderManager.LoaderCallbacks;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import com.android.settings.deletionhelper.FetchDownloadsLoader.DownloadsResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The DownloadsDeletionType provides stale download file information to the
|
||||
* {@link DownloadsDeletionPreferenceGroup}.
|
||||
*/
|
||||
public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<DownloadsResult> {
|
||||
private long mBytes;
|
||||
private long mMostRecent;
|
||||
private FreeableChangedListener mListener;
|
||||
private Context mContext;
|
||||
private ArrayMap<File, Boolean> mFiles;
|
||||
|
||||
public DownloadsDeletionType(Context context) {
|
||||
mContext = context;
|
||||
mFiles = new ArrayMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFreeableChangedListener(FreeableChangedListener listener) {
|
||||
mListener = listener;
|
||||
if (mFiles != null) {
|
||||
maybeUpdateListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearFreeableData() {
|
||||
if (mFiles != null) {
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Map.Entry<File, Boolean> entry : mFiles.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
entry.getKey().delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<DownloadsResult> onCreateLoader(int id, Bundle args) {
|
||||
return new FetchDownloadsLoader(mContext,
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<DownloadsResult> loader, DownloadsResult data) {
|
||||
mMostRecent = data.youngestLastModified;
|
||||
for (File file : data.files) {
|
||||
if (mFiles.containsKey(file)) {
|
||||
continue;
|
||||
}
|
||||
mFiles.put(file, false);
|
||||
}
|
||||
mBytes = data.totalSize;
|
||||
maybeUpdateListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<DownloadsResult> loader) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent last modified time for any clearable file.
|
||||
* @return The last modified time.
|
||||
*/
|
||||
public long getMostRecentLastModified() {
|
||||
return mMostRecent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the files in the Downloads folder after the loader task finishes.
|
||||
*/
|
||||
public Set<File> getFiles() {
|
||||
if (mFiles == null) {
|
||||
return null;
|
||||
}
|
||||
return mFiles.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle if a file should be deleted when the service is asked to clear files.
|
||||
*/
|
||||
public void toggleFile(File file, boolean checked) {
|
||||
mFiles.put(file, checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that would be cleared if the deletion tasks runs.
|
||||
*/
|
||||
public long getFreeableBytes() {
|
||||
long freedBytes = 0;
|
||||
for (Map.Entry<File, Boolean> entry : mFiles.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
freedBytes += entry.getKey().length();
|
||||
}
|
||||
}
|
||||
return freedBytes;
|
||||
}
|
||||
|
||||
private void maybeUpdateListener() {
|
||||
if (mListener != null) {
|
||||
mListener.onFreeableChanged(mFiles.size(), mBytes);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.format.Formatter;
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* DownloadsFilePreference is a preference representing a file in the Downloads folder
|
||||
* with a checkbox that represents if the file should be deleted.
|
||||
*/
|
||||
public class DownloadsFilePreference extends CheckBoxPreference {
|
||||
private File mFile;
|
||||
|
||||
public DownloadsFilePreference(Context context, File file) {
|
||||
super(context);
|
||||
mFile = file;
|
||||
setKey(mFile.getPath());
|
||||
setTitle(file.getName());
|
||||
setSummary(context.getString(R.string.deletion_helper_downloads_summary,
|
||||
Formatter.formatFileSize(getContext(), file.length()),
|
||||
DateUtils.getRelativeTimeSpanString(mFile.lastModified(),
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE)));
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return mFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Preference other) {
|
||||
if (other == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (other instanceof DownloadsFilePreference) {
|
||||
DownloadsFilePreference preference = (DownloadsFilePreference) other;
|
||||
return Long.compare(getFile().length(), preference.getFile().length());
|
||||
} else {
|
||||
// If a non-DownloadsFilePreference appears, consider ourselves to be greater.
|
||||
// This means if a non-DownloadsFilePreference sneaks into a DownloadsPreferenceGroup
|
||||
// then the DownloadsFilePreference will appear higher.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import com.android.settings.utils.AsyncLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* FetchDownloadsLoader is an asynchronous task which returns files in the Downloads
|
||||
* directory which have not been modified in longer than 90 days.
|
||||
*/
|
||||
public class FetchDownloadsLoader extends
|
||||
AsyncLoader<FetchDownloadsLoader.DownloadsResult> {
|
||||
private File mDirectory;
|
||||
|
||||
/**
|
||||
* Sets up a FetchDownloadsLoader in any directory.
|
||||
* @param directory The directory to look into.
|
||||
*/
|
||||
public FetchDownloadsLoader(Context context, File directory) {
|
||||
super(context);
|
||||
mDirectory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDiscardResult(DownloadsResult result) {}
|
||||
|
||||
@Override
|
||||
public DownloadsResult loadInBackground() {
|
||||
return collectFiles(mDirectory);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static DownloadsResult collectFiles(File dir) {
|
||||
return collectFiles(dir, new DownloadsResult());
|
||||
}
|
||||
|
||||
private static DownloadsResult collectFiles(File dir, DownloadsResult result) {
|
||||
File downloadFiles[] = dir.listFiles();
|
||||
if (downloadFiles == null) {
|
||||
}
|
||||
if (downloadFiles != null && downloadFiles.length > 0) {
|
||||
for (File currentFile : downloadFiles) {
|
||||
if (currentFile.isDirectory()) {
|
||||
collectFiles(currentFile, result);
|
||||
} else {
|
||||
if (currentFile.lastModified() < result.youngestLastModified) {
|
||||
result.youngestLastModified = currentFile.lastModified();
|
||||
}
|
||||
result.files.add(currentFile);
|
||||
result.totalSize += currentFile.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DownloadsResult is the result of a {@link FetchDownloadsLoader} with the files
|
||||
* and the amount of space they use.
|
||||
*/
|
||||
public static class DownloadsResult {
|
||||
public long totalSize;
|
||||
public long youngestLastModified;
|
||||
public ArrayList<File> files;
|
||||
|
||||
public DownloadsResult() {
|
||||
this(0, Long.MAX_VALUE, new ArrayList<File>());
|
||||
}
|
||||
|
||||
public DownloadsResult(long totalSize, long youngestLastModified, ArrayList<File> files) {
|
||||
this.totalSize = totalSize;
|
||||
this.youngestLastModified = youngestLastModified;
|
||||
this.files = files;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package com.android.settings.deletionhelper;
|
||||
|
||||
import android.content.pm.IPackageDeleteObserver;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Deletes a specified set of apps as a specified user and calls back once done.
|
||||
*/
|
||||
public class PackageDeletionTask {
|
||||
private Set<String> mPackages;
|
||||
private Callback mCallback;
|
||||
private PackageManager mPm;
|
||||
private UserHandle mUser;
|
||||
|
||||
public PackageDeletionTask(PackageManager pm, Set<String> packageNames, Callback callback) {
|
||||
mPackages = packageNames;
|
||||
mCallback = callback;
|
||||
mPm = pm;
|
||||
mUser = android.os.Process.myUserHandle();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
PackageDeletionObserver observer = new PackageDeletionObserver(mPackages.size());
|
||||
for (String packageName : mPackages) {
|
||||
mPm.deletePackageAsUser(packageName, observer, 0, mUser.getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
private class PackageDeletionObserver extends IPackageDeleteObserver.Stub {
|
||||
private final AtomicInteger mPackagesRemaining = new AtomicInteger(0);
|
||||
|
||||
public PackageDeletionObserver(int packages) {
|
||||
mPackagesRemaining.set(packages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packageDeleted(String packageName, int returnCode) {
|
||||
if (returnCode != PackageManager.DELETE_SUCCEEDED) {
|
||||
mCallback.onError();
|
||||
return;
|
||||
}
|
||||
|
||||
int remaining = mPackagesRemaining.decrementAndGet();
|
||||
if (remaining == 0) {
|
||||
mCallback.onSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Callback {
|
||||
public abstract void onSuccess();
|
||||
public abstract void onError();
|
||||
}
|
||||
}
|
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.Formatter;
|
||||
import com.android.settings.R;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Fragment for activating the storage manager after a manual clear.
|
||||
*/
|
||||
public class StorageManagerUpsellDialog extends DialogFragment
|
||||
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
|
||||
public static final String TAG = "StorageManagerUpsellDialog";
|
||||
private static final String SHARED_PREFERENCES_NAME = "StorageManagerUpsellDialog";
|
||||
private static final String NEXT_SHOW_TIME = "next_show_time";
|
||||
private static final String DISMISSED_COUNT = "dismissed_count";
|
||||
private static final String NO_THANKS_COUNT = "no_thanks_count";
|
||||
|
||||
private static final String ARGS_FREED_BYTES = "freed_bytes";
|
||||
|
||||
private static final long NEVER = -1;
|
||||
private static final long DISMISS_SHORT_DELAY = TimeUnit.DAYS.toMillis(14);
|
||||
private static final long DISMISS_LONG_DELAY = TimeUnit.DAYS.toMillis(90);
|
||||
private static final int DISMISS_LONG_THRESHOLD = 9;
|
||||
private static final long NO_THANKS_SHORT_DELAY = TimeUnit.DAYS.toMillis(90);
|
||||
private static final long NO_THANKS_LONG_DELAY = NEVER;
|
||||
private static final int NO_THANKS_LONG_THRESHOLD = 3;
|
||||
|
||||
public static StorageManagerUpsellDialog newInstance(long freedBytes) {
|
||||
StorageManagerUpsellDialog dialog = new StorageManagerUpsellDialog();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putLong(ARGS_FREED_BYTES, freedBytes);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
long freedBytes = args.getLong(ARGS_FREED_BYTES);
|
||||
|
||||
final Context context = getContext();
|
||||
return new AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.deletion_helper_upsell_title,
|
||||
Formatter.formatFileSize(context, freedBytes)))
|
||||
.setMessage(context.getString(R.string.deletion_helper_upsell_summary))
|
||||
.setPositiveButton(R.string.deletion_helper_upsell_activate, this)
|
||||
.setNegativeButton(R.string.deletion_helper_upsell_cancel, this)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int buttonId) {
|
||||
if (buttonId == DialogInterface.BUTTON_POSITIVE) {
|
||||
// TODO: Activate the storage manager once the storage manager is landed.
|
||||
} else {
|
||||
SharedPreferences sp = getSharedPreferences(getContext());
|
||||
int noThanksCount = sp.getInt(NO_THANKS_COUNT, 0) + 1;
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putInt(NO_THANKS_COUNT, noThanksCount);
|
||||
editor.putLong(NEXT_SHOW_TIME,
|
||||
System.currentTimeMillis() + getNoThanksDelay(noThanksCount));
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
SharedPreferences sp = getSharedPreferences(getContext());
|
||||
int dismissCount = sp.getInt(DISMISSED_COUNT, 0) + 1;
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putInt(DISMISSED_COUNT, dismissCount);
|
||||
editor.putLong(NEXT_SHOW_TIME,
|
||||
System.currentTimeMillis() + getDismissDelay(dismissCount));
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the dialog should be shown, given the delays between when it is shown.
|
||||
* @param context Context to get shared preferences for determining the next show time.
|
||||
*/
|
||||
public static boolean shouldShow(Context context) {
|
||||
// TODO: If the Storage Manager is enabled, return false.
|
||||
long nextTimeToShow = getSharedPreferences(context).getLong(NEXT_SHOW_TIME, 0);
|
||||
|
||||
if (nextTimeToShow == NEVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() > nextTimeToShow;
|
||||
}
|
||||
|
||||
private static SharedPreferences getSharedPreferences(Context context) {
|
||||
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private static long getNoThanksDelay(int noThanksCount) {
|
||||
return (noThanksCount > NO_THANKS_LONG_THRESHOLD)
|
||||
? NO_THANKS_LONG_DELAY : NO_THANKS_SHORT_DELAY;
|
||||
}
|
||||
|
||||
private static long getDismissDelay(int dismissCount) {
|
||||
return (dismissCount > DISMISS_LONG_THRESHOLD)
|
||||
? DISMISS_LONG_DELAY : DISMISS_SHORT_DELAY;
|
||||
}
|
||||
}
|
@@ -38,6 +38,7 @@ import android.os.storage.StorageManager;
|
||||
import android.os.storage.VolumeInfo;
|
||||
import android.os.storage.VolumeRecord;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.Settings;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
@@ -59,9 +60,7 @@ import com.android.settings.Settings.StorageUseActivity;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.applications.ManageApplications;
|
||||
import com.android.settings.deletionhelper.DeletionHelperFragment;
|
||||
import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
|
||||
import com.android.settings.deletionhelper.StorageManagerUpsellDialog;
|
||||
import com.android.settings.deviceinfo.StorageSettings.MountTask;
|
||||
import com.android.settingslib.deviceinfo.StorageMeasurement;
|
||||
import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
|
||||
@@ -445,8 +444,9 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.storage_free:
|
||||
startFragment(this, DeletionHelperFragment.class.getCanonicalName(),
|
||||
R.string.deletion_helper_title, DELETION_HELPER_SETTINGS, args);
|
||||
final Intent deletion_helper_intent =
|
||||
new Intent(Settings.ACTION_DELETION_HELPER_SETTINGS);
|
||||
startActivity(deletion_helper_intent);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -540,18 +540,6 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
|
||||
return super.onPreferenceTreeClick(pref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == DELETION_HELPER_SETTINGS && resultCode == DELETION_HELPER_CLEAR &&
|
||||
StorageManagerUpsellDialog.shouldShow(getActivity())) {
|
||||
long freedBytes = data.getLongExtra(DeletionHelperFragment.FREED_BYTES_KEY, 0);
|
||||
StorageManagerUpsellDialog dialog =
|
||||
StorageManagerUpsellDialog.newInstance(freedBytes);
|
||||
dialog.show(getFragmentManager(), StorageManagerUpsellDialog.TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
|
||||
@Override
|
||||
public void onDetailsChanged(MeasurementDetails details) {
|
||||
|
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.overlay;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import com.android.settings.deletionhelper.DeletionType;
|
||||
|
||||
/**
|
||||
* Feature provider for the manual deletion helper Settings page.
|
||||
*/
|
||||
public interface DeletionHelperFeatureProvider {
|
||||
/**
|
||||
* Creates a {@link DeletionType} for clearing out stored photos and videos on the device.
|
||||
*/
|
||||
DeletionType createPhotoVideoDeletionType(Context context);
|
||||
}
|
@@ -61,12 +61,6 @@ public abstract class FeatureFactory {
|
||||
|
||||
public abstract SupportFeatureProvider getSupportFeatureProvider(Context context);
|
||||
|
||||
/**
|
||||
* Return a provider which adds additional deletion services to the Deletion Helper.
|
||||
*/
|
||||
public abstract DeletionHelperFeatureProvider getDeletionHelperFeatureProvider();
|
||||
public abstract StorageManagementJobProvider getStorageManagementJobProvider();
|
||||
|
||||
public static final class FactoryNotFoundException extends RuntimeException {
|
||||
public FactoryNotFoundException(Throwable throwable) {
|
||||
super("Unable to create factory. Did you misconfigure Proguard?", throwable);
|
||||
|
@@ -28,14 +28,4 @@ public final class FeatureFactoryImpl extends FeatureFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeletionHelperFeatureProvider getDeletionHelperFeatureProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageManagementJobProvider getStorageManagementJobProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.overlay;
|
||||
|
||||
import android.app.job.JobParameters;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Feature provider for automatic storage management jobs.
|
||||
*/
|
||||
public interface StorageManagementJobProvider {
|
||||
/**
|
||||
* Starts an asynchronous deletion job to clear out storage older than
|
||||
* @param params Standard JobService parameters.
|
||||
* @param daysToRetain Number of days of information to retain on the device.
|
||||
* @return If the job needs to process the work on a separate thread.
|
||||
*/
|
||||
boolean onStartJob(Context context, JobParameters params, int daysToRetain);
|
||||
|
||||
/**
|
||||
* Attempt to stop the execution of the job.
|
||||
* @param params Parameters specifying info about this job.
|
||||
* @return If the job should be rescheduled.
|
||||
*/
|
||||
boolean onStopJob(Context context, JobParameters params);
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import com.android.settings.deletionhelper.FetchDownloadsLoader.DownloadsResult;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class FetchDownloadsLoaderTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void testEmptyDirectory() throws Exception {
|
||||
DownloadsResult result =
|
||||
FetchDownloadsLoader.collectFiles(temporaryFolder.getRoot());
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.totalSize);
|
||||
assertEquals(0, result.files.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilesInDirectory() throws Exception {
|
||||
temporaryFolder.newFile();
|
||||
temporaryFolder.newFile();
|
||||
|
||||
DownloadsResult result =
|
||||
FetchDownloadsLoader.collectFiles(temporaryFolder.getRoot());
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.totalSize);
|
||||
assertEquals(2, result.files.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedDirectories() throws Exception {
|
||||
File tempDir = temporaryFolder.newFolder();
|
||||
|
||||
File testFile = File.createTempFile("test", null, tempDir);
|
||||
testFile.deleteOnExit();
|
||||
DownloadsResult result =
|
||||
FetchDownloadsLoader.collectFiles(temporaryFolder.getRoot());
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.totalSize);
|
||||
assertEquals(1, result.files.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSumFileSizes() throws Exception {
|
||||
File first = temporaryFolder.newFile();
|
||||
FileWriter fileWriter = new FileWriter(first);
|
||||
fileWriter.write("test");
|
||||
fileWriter.close();
|
||||
|
||||
File second = temporaryFolder.newFile();
|
||||
fileWriter = new FileWriter(second);
|
||||
fileWriter.write("test2");
|
||||
fileWriter.close();
|
||||
|
||||
DownloadsResult result =
|
||||
FetchDownloadsLoader.collectFiles(temporaryFolder.getRoot());
|
||||
assertNotNull(result);
|
||||
assertEquals(9, result.totalSize);
|
||||
assertEquals(2, result.files.size());
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.deletionhelper;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.content.pm.IPackageDeleteObserver;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
import android.test.mock.MockPackageManager;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.settings.deletionhelper.PackageDeletionTask;
|
||||
import com.android.settings.deletionhelper.PackageDeletionTask.Callback;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class PackageDeletionTaskTest extends AndroidTestCase {
|
||||
private FakePackageManager mPackageManager;
|
||||
private Set<String> mDeletedApps;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
mPackageManager = new FakePackageManager();
|
||||
mDeletedApps = new HashSet<String>();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testDeleteNoApps() throws Exception {
|
||||
runTask(new HashSet<String>(), false);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testDeleteOneApp() throws Exception {
|
||||
HashSet<String> appsToDelete = new HashSet<String>();
|
||||
appsToDelete.add("app.test1");
|
||||
runTask(appsToDelete, false);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testDeleteManyApps() throws Exception {
|
||||
HashSet<String> appsToDelete = new HashSet<String>();
|
||||
appsToDelete.add("app.test1");
|
||||
appsToDelete.add("app.test2");
|
||||
runTask(appsToDelete, false);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testDeleteFails() throws Exception {
|
||||
HashSet<String> appsToDelete = new HashSet<String>();
|
||||
appsToDelete.add("app.test1");
|
||||
mPackageManager.deletionSucceeds = false;
|
||||
runTask(appsToDelete, true);
|
||||
}
|
||||
|
||||
private void runTask(HashSet<String> appsToDelete, boolean shouldFail) {
|
||||
PackageDeletionTask task = new PackageDeletionTask(mPackageManager, appsToDelete,
|
||||
new VerifierCallback(appsToDelete, shouldFail));
|
||||
task.run();
|
||||
}
|
||||
|
||||
class FakePackageManager extends MockPackageManager {
|
||||
public boolean deletionSucceeds = true;
|
||||
|
||||
@Override
|
||||
public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
|
||||
int flags, int userId) {
|
||||
int resultCode;
|
||||
if (deletionSucceeds) {
|
||||
resultCode = PackageManager.DELETE_SUCCEEDED;
|
||||
mDeletedApps.add(packageName);
|
||||
} else {
|
||||
resultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
try {
|
||||
observer.packageDeleted(packageName, resultCode);
|
||||
} catch (RemoteException e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VerifierCallback extends Callback {
|
||||
private Set<String> mExpectedDeletedApps;
|
||||
private boolean mShouldFail;
|
||||
|
||||
public VerifierCallback(HashSet<String> expectedDeletedApps, boolean shouldFail) {
|
||||
mExpectedDeletedApps = expectedDeletedApps;
|
||||
mShouldFail = shouldFail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
System.out.println("lol");
|
||||
assertFalse(mShouldFail);
|
||||
assertEquals(mExpectedDeletedApps, mDeletedApps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
assertTrue(mShouldFail);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user