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:
Daniel Nishi
2016-06-23 00:05:43 +00:00
committed by android-build-merger
31 changed files with 9 additions and 2380 deletions

View File

@@ -2921,17 +2921,6 @@
android:value="true" /> android:value="true" />
</activity> </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 for gesture settings -->
<activity android:name="Settings$GestureSettingsActivity" <activity android:name="Settings$GestureSettingsActivity"
android:label="@string/gesture_preference_title" android:label="@string/gesture_preference_title"
@@ -3010,21 +2999,6 @@
</intent-filter> </intent-filter>
</service> </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. --> <!-- This is the longest AndroidManifest.xml ever. -->
</application> </application>
</manifest> </manifest>

View File

@@ -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>

View File

@@ -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>

View File

@@ -41,6 +41,6 @@
<!-- Whether none security option is hide or not (country specific). --> <!-- Whether none security option is hide or not (country specific). -->
<bool name="config_hide_none_security_option">false</bool> <bool name="config_hide_none_security_option">false</bool>
<!-- Whether the automatic storage management job should be scheduled. --> <!--Whether the storage manager exists. -->
<bool name="enable_automatic_storage_management">false</bool> <bool name="config_has_storage_manager">false</bool>
</resources> </resources>

View File

@@ -7493,17 +7493,6 @@
<!-- [CHAR LIMIT=60] Name of dev option called demo mode --> <!-- [CHAR LIMIT=60] Name of dev option called demo mode -->
<string name="demo_mode">Demo mode</string> <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]--> <!-- Title text for connecting to customer support [CHAR LIMIT=80]-->
<string name="support_escalation_title">We\'re here to help</string> <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. --> <!-- Message for telling the user the kind of BT device being displayed in list. -->
<string name="bluetooth_talkback_bluetooth">Bluetooth</string> <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 &amp; 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] --> <!-- Used as title on the automatic storage manager settings. [CHAR LIMIT=60] -->
<string name="automatic_storage_manager_settings">Storage manager</string> <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.--> <!-- Dropdown preference title for dropdown describing how many days of data to retain.-->
<string name="automatic_storage_manager_days_title">Remove photos &amp; videos</string> <string name="automatic_storage_manager_days_title">Remove photos &amp; 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] --> <!-- Category title for the automatic settings in the storage manager settings. [CHAR LIMIT=40] -->
<string name="deletion_helper_automatic_title">Automatic</string> <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]--> <!-- 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> <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]--> <!-- Preference title for gesture settings [CHAR LIMIT=25]-->
<string name="gesture_preference_title">Gestures</string> <string name="gesture_preference_title">Gestures</string>

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -74,7 +74,6 @@ import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.dashboard.DashboardContainerFragment; import com.android.settings.dashboard.DashboardContainerFragment;
import com.android.settings.dashboard.SearchResultsSummary; import com.android.settings.dashboard.SearchResultsSummary;
import com.android.settings.datausage.DataUsageSummary; import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deletionhelper.DeletionHelperFragment;
import com.android.settings.deviceinfo.ImeiInformation; import com.android.settings.deviceinfo.ImeiInformation;
import com.android.settings.deviceinfo.PrivateVolumeForget; import com.android.settings.deviceinfo.PrivateVolumeForget;
import com.android.settings.deviceinfo.PrivateVolumeSettings; import com.android.settings.deviceinfo.PrivateVolumeSettings;
@@ -343,7 +342,6 @@ public class SettingsActivity extends SettingsDrawerActivity
WallpaperTypeSettings.class.getName(), WallpaperTypeSettings.class.getName(),
VrListenerSettings.class.getName(), VrListenerSettings.class.getName(),
ManagedProfileSettings.class.getName(), ManagedProfileSettings.class.getName(),
DeletionHelperFragment.class.getName(),
ChooseAccountActivity.class.getName(), ChooseAccountActivity.class.getName(),
IccLockSettings.class.getName(), IccLockSettings.class.getName(),
ImeiInformation.class.getName(), ImeiInformation.class.getName(),

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.deletionhelper;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
@@ -115,8 +116,8 @@ public class AutomaticStorageManagerSettings extends SettingsPreferenceFragment
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (KEY_DELETION_HELPER.equals(preference.getKey())) { if (KEY_DELETION_HELPER.equals(preference.getKey())) {
startFragment(this, DeletionHelperFragment.class.getCanonicalName(), Intent intent = new Intent(Settings.ACTION_DELETION_HELPER_SETTINGS);
R.string.deletion_helper_title, 0, null); getContext().startActivity(intent);
} }
return true; return true;
} }

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -38,6 +38,7 @@ import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo; import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord; import android.os.storage.VolumeRecord;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.provider.Settings;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceGroup; 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.SettingsPreferenceFragment;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.applications.ManageApplications; import com.android.settings.applications.ManageApplications;
import com.android.settings.deletionhelper.DeletionHelperFragment;
import com.android.settings.deletionhelper.AutomaticStorageManagerSettings; import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
import com.android.settings.deletionhelper.StorageManagerUpsellDialog;
import com.android.settings.deviceinfo.StorageSettings.MountTask; import com.android.settings.deviceinfo.StorageSettings.MountTask;
import com.android.settingslib.deviceinfo.StorageMeasurement; import com.android.settingslib.deviceinfo.StorageMeasurement;
import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails; import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
@@ -445,8 +444,9 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.storage_free: case R.id.storage_free:
startFragment(this, DeletionHelperFragment.class.getCanonicalName(), final Intent deletion_helper_intent =
R.string.deletion_helper_title, DELETION_HELPER_SETTINGS, args); new Intent(Settings.ACTION_DELETION_HELPER_SETTINGS);
startActivity(deletion_helper_intent);
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@@ -540,18 +540,6 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
return super.onPreferenceTreeClick(pref); 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() { private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
@Override @Override
public void onDetailsChanged(MeasurementDetails details) { public void onDetailsChanged(MeasurementDetails details) {

View File

@@ -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);
}

View File

@@ -61,12 +61,6 @@ public abstract class FeatureFactory {
public abstract SupportFeatureProvider getSupportFeatureProvider(Context context); 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 static final class FactoryNotFoundException extends RuntimeException {
public FactoryNotFoundException(Throwable throwable) { public FactoryNotFoundException(Throwable throwable) {
super("Unable to create factory. Did you misconfigure Proguard?", throwable); super("Unable to create factory. Did you misconfigure Proguard?", throwable);

View File

@@ -28,14 +28,4 @@ public final class FeatureFactoryImpl extends FeatureFactory {
return null; return null;
} }
@Override
public DeletionHelperFeatureProvider getDeletionHelperFeatureProvider() {
return null;
}
@Override
public StorageManagementJobProvider getStorageManagementJobProvider() {
return null;
}
} }

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}