Merge "Data usage UI fixes; sweeps, combined history."
This commit is contained in:
@@ -122,12 +122,13 @@ import android.widget.TextView;
|
||||
import com.android.internal.telephony.Phone;
|
||||
import com.android.settings.net.NetworkPolicyEditor;
|
||||
import com.android.settings.net.SummaryForAllUidLoader;
|
||||
import com.android.settings.widget.DataUsageChartView;
|
||||
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
|
||||
import com.android.settings.widget.ChartDataUsageView;
|
||||
import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
|
||||
import com.android.settings.widget.PieChartView;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -195,7 +196,7 @@ public class DataUsageSummary extends Fragment {
|
||||
private Spinner mCycleSpinner;
|
||||
private CycleAdapter mCycleAdapter;
|
||||
|
||||
private DataUsageChartView mChart;
|
||||
private ChartDataUsageView mChart;
|
||||
private TextView mUsageSummary;
|
||||
private TextView mEmpty;
|
||||
|
||||
@@ -216,8 +217,7 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
private NetworkTemplate mTemplate = null;
|
||||
|
||||
private static final int UID_NONE = -1;
|
||||
private int mUid = UID_NONE;
|
||||
private int[] mAppDetailUids = null;
|
||||
|
||||
private Intent mAppSettingsIntent;
|
||||
|
||||
@@ -307,7 +307,7 @@ public class DataUsageSummary extends Fragment {
|
||||
mCycleSpinner.setAdapter(mCycleAdapter);
|
||||
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
|
||||
|
||||
mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
|
||||
mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
|
||||
mChart.setListener(mChartListener);
|
||||
|
||||
{
|
||||
@@ -611,8 +611,9 @@ public class DataUsageSummary extends Fragment {
|
||||
mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
|
||||
|
||||
} else if (TAB_WIFI.equals(currentTab)) {
|
||||
// wifi doesn't have any controls
|
||||
mDataEnabledView.setVisibility(View.GONE);
|
||||
setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_wifi_limit);
|
||||
mDisableAtLimitView.setVisibility(View.GONE);
|
||||
mTemplate = buildTemplateWifi();
|
||||
|
||||
} else if (TAB_ETHERNET.equals(currentTab)) {
|
||||
@@ -649,12 +650,16 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
|
||||
private boolean isAppDetailMode() {
|
||||
return mUid != UID_NONE;
|
||||
return mAppDetailUids != null;
|
||||
}
|
||||
|
||||
private int getAppDetailPrimaryUid() {
|
||||
return mAppDetailUids[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update UID details panels to match {@link #mUid}, showing or hiding them
|
||||
* depending on {@link #isAppDetailMode()}.
|
||||
* Update UID details panels to match {@link #mAppDetailUids}, showing or
|
||||
* hiding them depending on {@link #isAppDetailMode()}.
|
||||
*/
|
||||
private void updateAppDetail() {
|
||||
final Context context = getActivity();
|
||||
@@ -681,7 +686,8 @@ public class DataUsageSummary extends Fragment {
|
||||
mChart.bindNetworkPolicy(null);
|
||||
|
||||
// show icon and all labels appearing under this app
|
||||
final UidDetail detail = resolveDetailForUid(context, mUid);
|
||||
final int primaryUid = getAppDetailPrimaryUid();
|
||||
final UidDetail detail = resolveDetailForUid(context, primaryUid);
|
||||
mAppIcon.setImageDrawable(detail.icon);
|
||||
|
||||
mAppTitles.removeAllViews();
|
||||
@@ -695,7 +701,7 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
// enable settings button when package provides it
|
||||
// TODO: target torwards entire UID instead of just first package
|
||||
final String[] packageNames = pm.getPackagesForUid(mUid);
|
||||
final String[] packageNames = pm.getPackagesForUid(primaryUid);
|
||||
if (packageNames != null && packageNames.length > 0) {
|
||||
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
|
||||
mAppSettingsIntent.setPackage(packageNames[0]);
|
||||
@@ -709,12 +715,40 @@ public class DataUsageSummary extends Fragment {
|
||||
mAppSettings.setEnabled(false);
|
||||
}
|
||||
|
||||
updateDetailHistory();
|
||||
updateDetailData();
|
||||
|
||||
if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
|
||||
&& !getRestrictBackground() && isBandwidthControlEnabled()) {
|
||||
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
|
||||
setPreferenceSummary(mAppRestrictView,
|
||||
getString(R.string.data_usage_app_restrict_background_summary,
|
||||
buildLimitedNetworksList()));
|
||||
|
||||
mAppRestrictView.setVisibility(View.VISIBLE);
|
||||
mAppRestrict.setChecked(getAppRestrictBackground());
|
||||
|
||||
} else {
|
||||
mAppRestrictView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update {@link #mDetailHistory} and related values based on
|
||||
* {@link #mAppDetailUids}.
|
||||
*/
|
||||
private void updateDetailHistory() {
|
||||
try {
|
||||
mDetailHistoryDefault = null;
|
||||
mDetailHistoryForeground = null;
|
||||
|
||||
// load stats for current uid and template
|
||||
mDetailHistoryDefault = mStatsService.getHistoryForUid(
|
||||
mTemplate, mUid, SET_DEFAULT, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||
mDetailHistoryForeground = mStatsService.getHistoryForUid(
|
||||
mTemplate, mUid, SET_FOREGROUND, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||
for (int uid : mAppDetailUids) {
|
||||
mDetailHistoryDefault = collectHistoryForUid(
|
||||
uid, SET_DEFAULT, mDetailHistoryDefault);
|
||||
mDetailHistoryForeground = collectHistoryForUid(
|
||||
uid, SET_FOREGROUND, mDetailHistoryForeground);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// since we can't do much without history, and we don't want to
|
||||
// leave with half-baked UI, we bail hard.
|
||||
@@ -727,23 +761,24 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
// bind chart to historical stats
|
||||
mChart.bindDetailNetworkStats(mDetailHistory);
|
||||
}
|
||||
|
||||
updateDetailData();
|
||||
|
||||
if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()
|
||||
&& isBandwidthControlEnabled()) {
|
||||
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
|
||||
setPreferenceSummary(mAppRestrictView,
|
||||
getString(R.string.data_usage_app_restrict_background_summary,
|
||||
buildLimitedNetworksList()));
|
||||
|
||||
mAppRestrictView.setVisibility(View.VISIBLE);
|
||||
mAppRestrict.setChecked(getAppRestrictBackground());
|
||||
/**
|
||||
* Collect {@link NetworkStatsHistory} for the requested UID, combining with
|
||||
* an existing {@link NetworkStatsHistory} if provided.
|
||||
*/
|
||||
private NetworkStatsHistory collectHistoryForUid(
|
||||
int uid, int set, NetworkStatsHistory existing)
|
||||
throws RemoteException {
|
||||
final NetworkStatsHistory history = mStatsService.getHistoryForUid(
|
||||
mTemplate, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||
|
||||
if (existing != null) {
|
||||
existing.recordEntireHistory(history);
|
||||
return existing;
|
||||
} else {
|
||||
mAppRestrictView.setVisibility(View.GONE);
|
||||
return history;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setPolicyCycleDay(int cycleDay) {
|
||||
@@ -764,8 +799,8 @@ public class DataUsageSummary extends Fragment {
|
||||
updatePolicy(false);
|
||||
}
|
||||
|
||||
private boolean isNetworkPolicyModifiable() {
|
||||
return isBandwidthControlEnabled() && mDataEnabled.isChecked();
|
||||
private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
|
||||
return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
|
||||
}
|
||||
|
||||
private boolean isBandwidthControlEnabled() {
|
||||
@@ -810,9 +845,10 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
|
||||
private boolean getAppRestrictBackground() {
|
||||
final int primaryUid = getAppDetailPrimaryUid();
|
||||
final int uidPolicy;
|
||||
try {
|
||||
uidPolicy = mPolicyService.getUidPolicy(mUid);
|
||||
uidPolicy = mPolicyService.getUidPolicy(primaryUid);
|
||||
} catch (RemoteException e) {
|
||||
// since we can't do much without policy, we bail hard.
|
||||
throw new RuntimeException("problem reading network policy", e);
|
||||
@@ -823,9 +859,10 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
private void setAppRestrictBackground(boolean restrictBackground) {
|
||||
if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
|
||||
final int primaryUid = getAppDetailPrimaryUid();
|
||||
try {
|
||||
mPolicyService.setUidPolicy(
|
||||
mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
|
||||
mPolicyService.setUidPolicy(primaryUid,
|
||||
restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("unable to save policy", e);
|
||||
}
|
||||
@@ -851,8 +888,8 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate, true);
|
||||
if (isNetworkPolicyModifiable()) {
|
||||
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
|
||||
if (isNetworkPolicyModifiable(policy)) {
|
||||
mDisableAtLimitView.setVisibility(View.VISIBLE);
|
||||
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
|
||||
if (!isAppDetailMode()) {
|
||||
@@ -912,19 +949,28 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
|
||||
// one last cycle entry to modify policy cycle day
|
||||
mCycleAdapter.setChangePossible(isNetworkPolicyModifiable());
|
||||
mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
|
||||
}
|
||||
|
||||
if (!hasCycles) {
|
||||
// no valid cycles; show all data
|
||||
// TODO: offer simple ranges like "last week" etc
|
||||
mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
|
||||
// no policy defined cycles; show entry for each four-week period
|
||||
long cycleEnd = historyEnd;
|
||||
while (cycleEnd > historyStart) {
|
||||
final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
|
||||
mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
|
||||
cycleEnd = cycleStart;
|
||||
}
|
||||
|
||||
mCycleAdapter.setChangePossible(false);
|
||||
}
|
||||
|
||||
// force pick the current cycle (first item)
|
||||
mCycleSpinner.setSelection(0);
|
||||
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
|
||||
if (mCycleAdapter.getCount() > 0) {
|
||||
mCycleSpinner.setSelection(0);
|
||||
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
|
||||
} else {
|
||||
updateDetailData();
|
||||
}
|
||||
}
|
||||
|
||||
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
|
||||
@@ -985,8 +1031,10 @@ public class DataUsageSummary extends Fragment {
|
||||
private OnItemClickListener mListListener = new OnItemClickListener() {
|
||||
/** {@inheritDoc} */
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Context context = view.getContext();
|
||||
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
|
||||
AppDetailsFragment.show(DataUsageSummary.this, app.uid);
|
||||
final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
|
||||
AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1065,6 +1113,7 @@ public class DataUsageSummary extends Fragment {
|
||||
entry = mHistory.getValues(start, end, now, null);
|
||||
|
||||
// kick off loader for detailed stats
|
||||
// TODO: delay loader until animation is finished
|
||||
getLoaderManager().restartLoader(LOADER_SUMMARY,
|
||||
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
|
||||
}
|
||||
@@ -1222,9 +1271,20 @@ public class DataUsageSummary extends Fragment {
|
||||
}
|
||||
|
||||
private static class AppUsageItem implements Comparable<AppUsageItem> {
|
||||
public int uid;
|
||||
public int[] uids;
|
||||
public long total;
|
||||
|
||||
public AppUsageItem(int uid) {
|
||||
uids = new int[] { uid };
|
||||
}
|
||||
|
||||
public void addUid(int uid) {
|
||||
if (contains(uids, uid)) return;
|
||||
final int length = uids.length;
|
||||
uids = Arrays.copyOf(uids, length + 1);
|
||||
uids[length] = uid;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public int compareTo(AppUsageItem another) {
|
||||
return Long.compare(another.total, total);
|
||||
@@ -1244,9 +1304,7 @@ public class DataUsageSummary extends Fragment {
|
||||
public void bindStats(NetworkStats stats) {
|
||||
mItems.clear();
|
||||
|
||||
final AppUsageItem systemItem = new AppUsageItem();
|
||||
systemItem.uid = android.os.Process.SYSTEM_UID;
|
||||
|
||||
final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID);
|
||||
final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
|
||||
|
||||
NetworkStats.Entry entry = null;
|
||||
@@ -1260,8 +1318,7 @@ public class DataUsageSummary extends Fragment {
|
||||
if (isApp || uid == TrafficStats.UID_REMOVED) {
|
||||
AppUsageItem item = knownUids.get(uid);
|
||||
if (item == null) {
|
||||
item = new AppUsageItem();
|
||||
item.uid = uid;
|
||||
item = new AppUsageItem(uid);
|
||||
knownUids.put(uid, item);
|
||||
mItems.add(item);
|
||||
}
|
||||
@@ -1269,6 +1326,7 @@ public class DataUsageSummary extends Fragment {
|
||||
item.total += entry.rxBytes + entry.txBytes;
|
||||
} else {
|
||||
systemItem.total += entry.rxBytes + entry.txBytes;
|
||||
systemItem.addUid(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,7 +1351,7 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mItems.get(position).uid;
|
||||
return mItems.get(position).uids[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1312,7 +1370,7 @@ public class DataUsageSummary extends Fragment {
|
||||
android.R.id.progress);
|
||||
|
||||
final AppUsageItem item = mItems.get(position);
|
||||
final UidDetail detail = resolveDetailForUid(context, item.uid);
|
||||
final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
|
||||
|
||||
icon.setImageDrawable(detail.icon);
|
||||
title.setText(detail.label);
|
||||
@@ -1323,7 +1381,6 @@ public class DataUsageSummary extends Fragment {
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1331,11 +1388,11 @@ public class DataUsageSummary extends Fragment {
|
||||
* {@link DataUsageSummary}.
|
||||
*/
|
||||
public static class AppDetailsFragment extends Fragment {
|
||||
private static final String EXTRA_UID = "uid";
|
||||
private static final String EXTRA_UIDS = "uids";
|
||||
|
||||
public static void show(DataUsageSummary parent, int uid) {
|
||||
public static void show(DataUsageSummary parent, int[] uids, CharSequence label) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(EXTRA_UID, uid);
|
||||
args.putIntArray(EXTRA_UIDS, uids);
|
||||
|
||||
final AppDetailsFragment fragment = new AppDetailsFragment();
|
||||
fragment.setArguments(args);
|
||||
@@ -1344,6 +1401,7 @@ public class DataUsageSummary extends Fragment {
|
||||
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
|
||||
ft.add(fragment, TAG_APP_DETAILS);
|
||||
ft.addToBackStack(TAG_APP_DETAILS);
|
||||
ft.setBreadCrumbTitle(label);
|
||||
ft.commit();
|
||||
}
|
||||
|
||||
@@ -1351,7 +1409,7 @@ public class DataUsageSummary extends Fragment {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||
target.mUid = getArguments().getInt(EXTRA_UID);
|
||||
target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS);
|
||||
target.updateBody();
|
||||
}
|
||||
|
||||
@@ -1359,7 +1417,7 @@ public class DataUsageSummary extends Fragment {
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||
target.mUid = UID_NONE;
|
||||
target.mAppDetailUids = null;
|
||||
target.updateBody();
|
||||
}
|
||||
}
|
||||
@@ -1441,7 +1499,7 @@ public class DataUsageSummary extends Fragment {
|
||||
private static final String EXTRA_CYCLE_DAY = "cycleDay";
|
||||
|
||||
public static void show(DataUsageSummary parent) {
|
||||
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate, false);
|
||||
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
|
||||
|
||||
@@ -1807,4 +1865,13 @@ public class DataUsageSummary extends Fragment {
|
||||
summary.setVisibility(View.VISIBLE);
|
||||
summary.setText(string);
|
||||
}
|
||||
|
||||
private static boolean contains(int[] haystack, int needle) {
|
||||
for (int value : haystack) {
|
||||
if (value == needle) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import static android.net.NetworkPolicy.SNOOZE_NEVER;
|
||||
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
|
||||
import static android.net.NetworkTemplate.MATCH_WIFI;
|
||||
import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
|
||||
import static android.net.NetworkTemplate.buildTemplateMobile4g;
|
||||
import static android.net.NetworkTemplate.buildTemplateMobileAll;
|
||||
@@ -40,7 +41,7 @@ import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
|
||||
* about which policies can coexist.
|
||||
* about which policies can coexist. Not thread safe.
|
||||
*/
|
||||
public class NetworkPolicyEditor {
|
||||
// TODO: be more robust when missing policies from service
|
||||
@@ -53,64 +54,80 @@ public class NetworkPolicyEditor {
|
||||
}
|
||||
|
||||
public void read() {
|
||||
final NetworkPolicy[] policies;
|
||||
try {
|
||||
final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
|
||||
mPolicies.clear();
|
||||
for (NetworkPolicy policy : policies) {
|
||||
// TODO: find better place to clamp these
|
||||
if (policy.limitBytes < -1) {
|
||||
policy.limitBytes = LIMIT_DISABLED;
|
||||
}
|
||||
if (policy.warningBytes < -1) {
|
||||
policy.warningBytes = WARNING_DISABLED;
|
||||
}
|
||||
|
||||
mPolicies.add(policy);
|
||||
}
|
||||
policies = mPolicyService.getNetworkPolicies();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("problem reading policies", e);
|
||||
}
|
||||
|
||||
boolean modified = false;
|
||||
mPolicies.clear();
|
||||
for (NetworkPolicy policy : policies) {
|
||||
// TODO: find better place to clamp these
|
||||
if (policy.limitBytes < -1) {
|
||||
policy.limitBytes = LIMIT_DISABLED;
|
||||
modified = true;
|
||||
}
|
||||
if (policy.warningBytes < -1) {
|
||||
policy.warningBytes = WARNING_DISABLED;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// drop any WIFI policies that were defined
|
||||
if (policy.template.getMatchRule() == MATCH_WIFI) {
|
||||
modified = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
mPolicies.add(policy);
|
||||
}
|
||||
|
||||
// when we cleaned policies above, write back changes
|
||||
if (modified) writeAsync();
|
||||
}
|
||||
|
||||
public void writeAsync() {
|
||||
// TODO: consider making more robust by passing through service
|
||||
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
write();
|
||||
write(policies);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public void write() {
|
||||
public void write(NetworkPolicy[] policies) {
|
||||
try {
|
||||
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
|
||||
mPolicyService.setNetworkPolicies(policies);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("problem reading policies", e);
|
||||
throw new RuntimeException("problem writing policies", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasLimitedPolicy(NetworkTemplate template) {
|
||||
final NetworkPolicy policy = getPolicy(template, false);
|
||||
final NetworkPolicy policy = getPolicy(template);
|
||||
return policy != null && policy.limitBytes != LIMIT_DISABLED;
|
||||
}
|
||||
|
||||
public NetworkPolicy getPolicy(NetworkTemplate template, boolean createDefault) {
|
||||
public NetworkPolicy getOrCreatePolicy(NetworkTemplate template) {
|
||||
NetworkPolicy policy = getPolicy(template);
|
||||
if (policy == null) {
|
||||
policy = buildDefaultPolicy(template);
|
||||
mPolicies.add(policy);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
public NetworkPolicy getPolicy(NetworkTemplate template) {
|
||||
for (NetworkPolicy policy : mPolicies) {
|
||||
if (policy.template.equals(template)) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
|
||||
if (createDefault) {
|
||||
final NetworkPolicy policy = buildDefaultPolicy(template);
|
||||
mPolicies.add(policy);
|
||||
return policy;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
|
||||
@@ -124,21 +141,21 @@ public class NetworkPolicyEditor {
|
||||
}
|
||||
|
||||
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
|
||||
final NetworkPolicy policy = getPolicy(template, true);
|
||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||
policy.cycleDay = cycleDay;
|
||||
policy.lastSnooze = SNOOZE_NEVER;
|
||||
writeAsync();
|
||||
}
|
||||
|
||||
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
|
||||
final NetworkPolicy policy = getPolicy(template, true);
|
||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||
policy.warningBytes = warningBytes;
|
||||
policy.lastSnooze = SNOOZE_NEVER;
|
||||
writeAsync();
|
||||
}
|
||||
|
||||
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
|
||||
final NetworkPolicy policy = getPolicy(template, true);
|
||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||
policy.limitBytes = limitBytes;
|
||||
policy.lastSnooze = SNOOZE_NEVER;
|
||||
writeAsync();
|
||||
@@ -176,8 +193,8 @@ public class NetworkPolicyEditor {
|
||||
|
||||
} else if (beforeSplit && !split) {
|
||||
// combine, picking most restrictive policy
|
||||
final NetworkPolicy policy3g = getPolicy(template3g, false);
|
||||
final NetworkPolicy policy4g = getPolicy(template4g, false);
|
||||
final NetworkPolicy policy3g = getPolicy(template3g);
|
||||
final NetworkPolicy policy4g = getPolicy(template4g);
|
||||
|
||||
final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
|
||||
: policy4g;
|
||||
@@ -190,7 +207,7 @@ public class NetworkPolicyEditor {
|
||||
|
||||
} else if (!beforeSplit && split) {
|
||||
// duplicate existing policy into two rules
|
||||
final NetworkPolicy policyAll = getPolicy(templateAll, false);
|
||||
final NetworkPolicy policyAll = getPolicy(templateAll);
|
||||
mPolicies.remove(policyAll);
|
||||
mPolicies.add(
|
||||
new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,
|
||||
|
@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
@@ -37,7 +38,7 @@ import com.android.settings.widget.ChartSweepView.OnSweepListener;
|
||||
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
|
||||
* with {@link ChartSweepView} for inspection ranges and warning/limits.
|
||||
*/
|
||||
public class DataUsageChartView extends ChartView {
|
||||
public class ChartDataUsageView extends ChartView {
|
||||
|
||||
private static final long KB_IN_BYTES = 1024;
|
||||
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
|
||||
@@ -46,6 +47,8 @@ public class DataUsageChartView extends ChartView {
|
||||
private static final int MSG_UPDATE_AXIS = 100;
|
||||
private static final long DELAY_MILLIS = 250;
|
||||
|
||||
private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
|
||||
|
||||
private ChartGridView mGrid;
|
||||
private ChartNetworkSeriesView mSeries;
|
||||
private ChartNetworkSeriesView mDetailSeries;
|
||||
@@ -70,15 +73,15 @@ public class DataUsageChartView extends ChartView {
|
||||
|
||||
private DataUsageChartListener mListener;
|
||||
|
||||
public DataUsageChartView(Context context) {
|
||||
public ChartDataUsageView(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public DataUsageChartView(Context context, AttributeSet attrs) {
|
||||
public ChartDataUsageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
|
||||
public ChartDataUsageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
|
||||
|
||||
@@ -186,6 +189,7 @@ public class DataUsageChartView extends ChartView {
|
||||
|
||||
updateVertAxisBounds(null);
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +198,8 @@ public class DataUsageChartView extends ChartView {
|
||||
*/
|
||||
private void updateVertAxisBounds(ChartSweepView activeSweep) {
|
||||
final long max = mVertMax;
|
||||
final long newMax;
|
||||
|
||||
long newMax = 0;
|
||||
if (activeSweep != null) {
|
||||
final int adjustAxis = activeSweep.shouldAdjustAxis();
|
||||
if (adjustAxis > 0) {
|
||||
@@ -206,14 +211,14 @@ public class DataUsageChartView extends ChartView {
|
||||
} else {
|
||||
newMax = max;
|
||||
}
|
||||
|
||||
} else {
|
||||
// try showing all known data and policy
|
||||
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
||||
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
|
||||
newMax = Math.max(maxVisible, 2 * GB_IN_BYTES);
|
||||
}
|
||||
|
||||
// always show known data and policy lines
|
||||
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
||||
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
|
||||
final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
|
||||
newMax = Math.max(maxDefault, newMax);
|
||||
|
||||
// only invalidate when vertMax actually changed
|
||||
if (newMax != mVertMax) {
|
||||
mVertMax = newMax;
|
||||
@@ -231,6 +236,16 @@ public class DataUsageChartView extends ChartView {
|
||||
if (activeSweep != null) {
|
||||
activeSweep.updateValueFromPosition();
|
||||
}
|
||||
|
||||
// layout other sweeps to match changed axis
|
||||
// TODO: find cleaner way of doing this, such as requesting full
|
||||
// layout and making activeSweep discard its tracking MotionEvent.
|
||||
if (mSweepLimit != activeSweep) {
|
||||
layoutSweep(mSweepLimit);
|
||||
}
|
||||
if (mSweepWarning != activeSweep) {
|
||||
layoutSweep(mSweepWarning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,9 +361,14 @@ public class DataUsageChartView extends ChartView {
|
||||
final long validStart = Math.max(visibleStart, getStatsStart());
|
||||
final long validEnd = Math.min(visibleEnd, getStatsEnd());
|
||||
|
||||
// prevent time sweeps from leaving valid data
|
||||
mSweepLeft.setValidRange(validStart, validEnd);
|
||||
mSweepRight.setValidRange(validStart, validEnd);
|
||||
if (LIMIT_SWEEPS_TO_VALID_DATA) {
|
||||
// prevent time sweeps from leaving valid data
|
||||
mSweepLeft.setValidRange(validStart, validEnd);
|
||||
mSweepRight.setValidRange(validStart, validEnd);
|
||||
} else {
|
||||
mSweepLeft.setValidRange(visibleStart, visibleEnd);
|
||||
mSweepRight.setValidRange(visibleStart, visibleEnd);
|
||||
}
|
||||
|
||||
// default sweeps to last week of data
|
||||
final long halfRange = (visibleEnd + visibleStart) / 2;
|
||||
@@ -424,7 +444,7 @@ public class DataUsageChartView extends ChartView {
|
||||
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
|
||||
final float[] tickPoints = new float[tickCount];
|
||||
for (int i = 0; i < tickCount; i++) {
|
||||
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i));
|
||||
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * (i + 1)));
|
||||
}
|
||||
return tickPoints;
|
||||
}
|
||||
@@ -501,7 +521,14 @@ public class DataUsageChartView extends ChartView {
|
||||
/** {@inheritDoc} */
|
||||
public float[] getTickPoints() {
|
||||
final long range = mMax - mMin;
|
||||
final long tickJump = 256 * MB_IN_BYTES;
|
||||
final long tickJump;
|
||||
if (range < 6 * GB_IN_BYTES) {
|
||||
tickJump = 256 * MB_IN_BYTES;
|
||||
} else if (range < 12 * GB_IN_BYTES) {
|
||||
tickJump = 512 * MB_IN_BYTES;
|
||||
} else {
|
||||
tickJump = 1 * GB_IN_BYTES;
|
||||
}
|
||||
|
||||
final int tickCount = (int) (range / tickJump);
|
||||
final float[] tickPoints = new float[tickCount];
|
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -42,8 +43,14 @@ import com.google.common.base.Preconditions;
|
||||
*/
|
||||
public class ChartSweepView extends View {
|
||||
|
||||
private static final boolean DRAW_OUTLINE = false;
|
||||
|
||||
private Drawable mSweep;
|
||||
private Rect mSweepPadding = new Rect();
|
||||
|
||||
/** Offset of content inside this view. */
|
||||
private Point mContentOffset = new Point();
|
||||
/** Offset of {@link #mSweep} inside this view. */
|
||||
private Point mSweepOffset = new Point();
|
||||
|
||||
private Rect mMargins = new Rect();
|
||||
@@ -66,6 +73,8 @@ public class ChartSweepView extends View {
|
||||
private ChartSweepView mValidAfterDynamic;
|
||||
private ChartSweepView mValidBeforeDynamic;
|
||||
|
||||
private Paint mOutlinePaint = new Paint();
|
||||
|
||||
public static final int HORIZONTAL = 0;
|
||||
public static final int VERTICAL = 1;
|
||||
|
||||
@@ -98,6 +107,10 @@ public class ChartSweepView extends View {
|
||||
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
|
||||
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
|
||||
|
||||
mOutlinePaint.setColor(Color.RED);
|
||||
mOutlinePaint.setStrokeWidth(1f);
|
||||
mOutlinePaint.setStyle(Style.STROKE);
|
||||
|
||||
a.recycle();
|
||||
|
||||
setWillNotDraw(false);
|
||||
@@ -123,11 +136,11 @@ public class ChartSweepView extends View {
|
||||
if (mFollowAxis == VERTICAL) {
|
||||
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
|
||||
- mSweepPadding.bottom;
|
||||
return mSweepPadding.top + (targetHeight / 2);
|
||||
return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
|
||||
} else {
|
||||
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
|
||||
- mSweepPadding.right;
|
||||
return mSweepPadding.left + (targetWidth / 2);
|
||||
return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +208,7 @@ public class ChartSweepView extends View {
|
||||
paint.density = getResources().getDisplayMetrics().density;
|
||||
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
|
||||
paint.setColor(mLabelColor);
|
||||
paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
|
||||
|
||||
mLabelTemplate = new SpannableStringBuilder(template);
|
||||
mLabelLayout = new DynamicLayout(
|
||||
@@ -283,6 +297,26 @@ public class ChartSweepView extends View {
|
||||
mValidBeforeDynamic = validBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given {@link MotionEvent} is closer to another
|
||||
* {@link ChartSweepView} compared to ourselves.
|
||||
*/
|
||||
public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
|
||||
if (another == null) return false;
|
||||
|
||||
if (mFollowAxis == HORIZONTAL) {
|
||||
final float selfDist = Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
|
||||
final float anotherDist = Math.abs(
|
||||
eventInParent.getX() - (another.getX() + another.getTargetInset()));
|
||||
return anotherDist < selfDist;
|
||||
} else {
|
||||
final float selfDist = Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
|
||||
final float anotherDist = Math.abs(
|
||||
eventInParent.getY() - (another.getY() + another.getTargetInset()));
|
||||
return anotherDist < selfDist;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!isEnabled()) return false;
|
||||
@@ -294,9 +328,18 @@ public class ChartSweepView extends View {
|
||||
// only start tracking when in sweet spot
|
||||
final boolean accept;
|
||||
if (mFollowAxis == VERTICAL) {
|
||||
accept = event.getX() > getWidth() - (mSweepPadding.right * 2);
|
||||
accept = event.getX() > getWidth() - (mSweepPadding.right * 3);
|
||||
} else {
|
||||
accept = event.getY() > getHeight() - (mSweepPadding.bottom * 2);
|
||||
accept = event.getY() > getHeight() - (mSweepPadding.bottom * 3);
|
||||
}
|
||||
|
||||
final MotionEvent eventInParent = event.copy();
|
||||
eventInParent.offsetLocation(getLeft(), getTop());
|
||||
|
||||
// ignore event when closer to a neighbor
|
||||
if (isTouchCloserTo(eventInParent, mValidAfterDynamic)
|
||||
|| isTouchCloserTo(eventInParent, mValidBeforeDynamic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (accept) {
|
||||
@@ -460,6 +503,7 @@ public class ChartSweepView extends View {
|
||||
final int templateHeight = mLabelLayout.getHeight();
|
||||
|
||||
mSweepOffset.x = 0;
|
||||
mSweepOffset.y = 0;
|
||||
mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
|
||||
setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
|
||||
|
||||
@@ -485,6 +529,23 @@ public class ChartSweepView extends View {
|
||||
mMargins.bottom = mSweepPadding.bottom;
|
||||
}
|
||||
|
||||
mContentOffset.x = 0;
|
||||
mContentOffset.y = 0;
|
||||
|
||||
// make touch target area larger
|
||||
if (mFollowAxis == HORIZONTAL) {
|
||||
final int widthBefore = getMeasuredWidth();
|
||||
final int widthAfter = widthBefore * 3;
|
||||
setMeasuredDimension(widthAfter, getMeasuredHeight());
|
||||
mContentOffset.offset((widthAfter - widthBefore) / 2, 0);
|
||||
} else {
|
||||
final int heightBefore = getMeasuredHeight();
|
||||
final int heightAfter = heightBefore * 3;
|
||||
setMeasuredDimension(getMeasuredWidth(), heightAfter);
|
||||
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
|
||||
}
|
||||
|
||||
mSweepOffset.offset(mContentOffset.x, mContentOffset.y);
|
||||
mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
|
||||
}
|
||||
|
||||
@@ -493,9 +554,43 @@ public class ChartSweepView extends View {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
if (DRAW_OUTLINE) {
|
||||
canvas.drawRect(0, 0, width, height, mOutlinePaint);
|
||||
}
|
||||
|
||||
// when overlapping with neighbor, split difference and push label
|
||||
float margin;
|
||||
float labelOffset = 0;
|
||||
if (mFollowAxis == VERTICAL) {
|
||||
if (mValidAfterDynamic != null) {
|
||||
margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
|
||||
if (margin < 0) {
|
||||
labelOffset = margin / 2;
|
||||
}
|
||||
} else if (mValidBeforeDynamic != null) {
|
||||
margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
|
||||
if (margin < 0) {
|
||||
labelOffset = -margin / 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: implement horizontal labels
|
||||
}
|
||||
|
||||
// when offsetting label, neighbor probably needs to offset too
|
||||
if (labelOffset != 0) {
|
||||
if (mValidAfterDynamic != null) mValidAfterDynamic.invalidate();
|
||||
if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidate();
|
||||
}
|
||||
|
||||
final int labelSize;
|
||||
if (isEnabled() && mLabelLayout != null) {
|
||||
mLabelLayout.draw(canvas);
|
||||
final int count = canvas.save();
|
||||
{
|
||||
canvas.translate(mContentOffset.x, mContentOffset.y + labelOffset);
|
||||
mLabelLayout.draw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
labelSize = mLabelSize;
|
||||
} else {
|
||||
labelSize = 0;
|
||||
@@ -512,4 +607,11 @@ public class ChartSweepView extends View {
|
||||
mSweep.draw(canvas);
|
||||
}
|
||||
|
||||
public static float getLabelTop(ChartSweepView view) {
|
||||
return view.getY() + view.mContentOffset.y;
|
||||
}
|
||||
|
||||
public static float getLabelBottom(ChartSweepView view) {
|
||||
return getLabelTop(view) + view.mLabelLayout.getHeight();
|
||||
}
|
||||
}
|
||||
|
@@ -36,8 +36,6 @@ import com.android.settings.R;
|
||||
* and screen coordinates.
|
||||
*/
|
||||
public class ChartView extends FrameLayout {
|
||||
private static final String TAG = "ChartView";
|
||||
|
||||
// TODO: extend something that supports two-dimensional scrolling
|
||||
|
||||
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
|
||||
@@ -122,29 +120,39 @@ public class ChartView extends FrameLayout {
|
||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||
|
||||
} else if (child instanceof ChartSweepView) {
|
||||
// sweep is always placed along specific dimension
|
||||
final ChartSweepView sweep = (ChartSweepView) child;
|
||||
final Rect sweepMargins = sweep.getMargins();
|
||||
|
||||
if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
|
||||
parentRect.top += sweepMargins.top + (int) sweep.getPoint();
|
||||
parentRect.bottom = parentRect.top;
|
||||
parentRect.left += sweepMargins.left;
|
||||
parentRect.right += sweepMargins.right;
|
||||
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
|
||||
parentRect, childRect);
|
||||
|
||||
} else {
|
||||
parentRect.left += sweepMargins.left + (int) sweep.getPoint();
|
||||
parentRect.right = parentRect.left;
|
||||
parentRect.top += sweepMargins.top;
|
||||
parentRect.bottom += sweepMargins.bottom;
|
||||
Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
|
||||
parentRect, childRect);
|
||||
}
|
||||
layoutSweep((ChartSweepView) child, parentRect, childRect);
|
||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||
protected void layoutSweep(ChartSweepView sweep) {
|
||||
final Rect parentRect = new Rect(mContent);
|
||||
final Rect childRect = new Rect();
|
||||
|
||||
layoutSweep(sweep, parentRect, childRect);
|
||||
sweep.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||
}
|
||||
|
||||
protected void layoutSweep(ChartSweepView sweep, Rect parentRect, Rect childRect) {
|
||||
final Rect sweepMargins = sweep.getMargins();
|
||||
|
||||
// sweep is always placed along specific dimension
|
||||
if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
|
||||
parentRect.top += sweepMargins.top + (int) sweep.getPoint();
|
||||
parentRect.bottom = parentRect.top;
|
||||
parentRect.left += sweepMargins.left;
|
||||
parentRect.right += sweepMargins.right;
|
||||
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
|
||||
parentRect, childRect);
|
||||
|
||||
} else {
|
||||
parentRect.left += sweepMargins.left + (int) sweep.getPoint();
|
||||
parentRect.right = parentRect.left;
|
||||
parentRect.top += sweepMargins.top;
|
||||
parentRect.bottom += sweepMargins.bottom;
|
||||
Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
|
||||
parentRect, childRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user