| /* | |
| * 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); | |
| } | |
| } |