Merge \\"Remove the Deletion Helper and Automatic Storage Management jobs.\\" into nyc-mr1-dev am: de17410d37
am: 4938a9a470
Change-Id: If4f17948cef697c6bc4cb0306a7506eb445f49d7
This commit is contained in:
@@ -2921,17 +2921,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"
|
||||
@@ -3010,21 +2999,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">We\'re here to help</string>
|
||||
|
||||
@@ -7610,27 +7599,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>
|
||||
|
||||
@@ -7640,21 +7608,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>
|
||||
|
||||
@@ -7664,12 +7617,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