/* * 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.datausage; import android.content.Context; import android.net.NetworkPolicy; import android.net.NetworkStatsHistory; import android.text.format.DateUtils; import android.widget.AdapterView; import android.widget.ArrayAdapter; import com.android.settings.R; import com.android.settings.Utils; import com.android.settingslib.net.ChartData; import libcore.util.Objects; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> { private final SpinnerInterface mSpinner; private final AdapterView.OnItemSelectedListener mListener; public CycleAdapter(Context context, SpinnerInterface spinner, AdapterView.OnItemSelectedListener listener, boolean isHeader) { super(context, isHeader ? R.layout.filter_spinner_item : R.layout.data_usage_cycle_item); setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSpinner = spinner; mListener = listener; mSpinner.setAdapter(this); mSpinner.setOnItemSelectedListener(mListener); } /** * Find position of {@link CycleItem} in this adapter which is nearest * the given {@link CycleItem}. */ public int findNearestPosition(CycleItem target) { if (target != null) { final int count = getCount(); for (int i = count - 1; i >= 0; i--) { final CycleItem item = getItem(i); if (item.compareTo(target) >= 0) { return i; } } } return 0; } /** * Rebuild list based on {@link NetworkPolicy#cycleDay} * and available {@link NetworkStatsHistory} data. Always selects the newest * item, updating the inspection range on chartData. */ public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { // stash away currently selected cycle to try restoring below final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) mSpinner.getSelectedItem(); clear(); final Context context = getContext(); NetworkStatsHistory.Entry entry = null; long historyStart = Long.MAX_VALUE; long historyEnd = Long.MIN_VALUE; if (chartData != null) { historyStart = chartData.network.getStart(); historyEnd = chartData.network.getEnd(); } final long now = System.currentTimeMillis(); if (historyStart == Long.MAX_VALUE) historyStart = now; if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; boolean hasCycles = false; if (policy != null) { // find the next cycle boundary long cycleEnd = computeNextCycleBoundary(historyEnd, policy); // walk backwards, generating all valid cycle ranges while (cycleEnd > historyStart) { final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); final boolean includeCycle; if (chartData != null) { entry = chartData.network.getValues(cycleStart, cycleEnd, entry); includeCycle = (entry.rxBytes + entry.txBytes) > 0; } else { includeCycle = true; } if (includeCycle) { add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); hasCycles = true; } cycleEnd = cycleStart; } } if (!hasCycles) { // 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); final boolean includeCycle; if (chartData != null) { entry = chartData.network.getValues(cycleStart, cycleEnd, entry); includeCycle = (entry.rxBytes + entry.txBytes) > 0; } else { includeCycle = true; } if (includeCycle) { add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); } cycleEnd = cycleStart; } } // force pick the current cycle (first item) if (getCount() > 0) { final int position = findNearestPosition(previousItem); mSpinner.setSelection(position); // only force-update cycle when changed; skipping preserves any // user-defined inspection region. final CycleAdapter.CycleItem selectedItem = getItem(position); if (!Objects.equal(selectedItem, previousItem)) { mListener.onItemSelected(null, null, position, 0); return false; } } return true; } /** * List item that reflects a specific data usage cycle. */ public static class CycleItem implements Comparable<CycleItem> { public CharSequence label; public long start; public long end; public CycleItem(CharSequence label) { this.label = label; } public CycleItem(Context context, long start, long end) { this.label = Utils.formatDateRange(context, start, end); this.start = start; this.end = end; } @Override public String toString() { return label.toString(); } @Override public boolean equals(Object o) { if (o instanceof CycleItem) { final CycleItem another = (CycleItem) o; return start == another.start && end == another.end; } return false; } @Override public int compareTo(CycleItem another) { return Long.compare(start, another.start); } } public interface SpinnerInterface { void setAdapter(CycleAdapter cycleAdapter); void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); Object getSelectedItem(); void setSelection(int position); } }