1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14package com.android.settings.datausage; 15 16import android.content.Context; 17import android.net.NetworkPolicy; 18import android.net.NetworkPolicyManager; 19import android.net.NetworkStatsHistory; 20import android.text.format.DateUtils; 21import android.util.Pair; 22import android.widget.AdapterView; 23import android.widget.ArrayAdapter; 24 25import com.android.settings.R; 26import com.android.settings.Utils; 27import com.android.settingslib.net.ChartData; 28 29import libcore.util.Objects; 30 31import java.time.ZonedDateTime; 32import java.util.Iterator; 33 34public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> { 35 36 private final SpinnerInterface mSpinner; 37 private final AdapterView.OnItemSelectedListener mListener; 38 39 public CycleAdapter(Context context, SpinnerInterface spinner, 40 AdapterView.OnItemSelectedListener listener, boolean isHeader) { 41 super(context, isHeader ? R.layout.filter_spinner_item 42 : R.layout.data_usage_cycle_item); 43 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 44 mSpinner = spinner; 45 mListener = listener; 46 mSpinner.setAdapter(this); 47 mSpinner.setOnItemSelectedListener(mListener); 48 } 49 50 /** 51 * Find position of {@link CycleItem} in this adapter which is nearest 52 * the given {@link CycleItem}. 53 */ 54 public int findNearestPosition(CycleItem target) { 55 if (target != null) { 56 final int count = getCount(); 57 for (int i = count - 1; i >= 0; i--) { 58 final CycleItem item = getItem(i); 59 if (item.compareTo(target) >= 0) { 60 return i; 61 } 62 } 63 } 64 return 0; 65 } 66 67 /** 68 * Rebuild list based on {@link NetworkPolicy} and available 69 * {@link NetworkStatsHistory} data. Always selects the newest item, 70 * updating the inspection range on chartData. 71 */ 72 public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { 73 // stash away currently selected cycle to try restoring below 74 final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) 75 mSpinner.getSelectedItem(); 76 clear(); 77 78 final Context context = getContext(); 79 NetworkStatsHistory.Entry entry = null; 80 81 long historyStart = Long.MAX_VALUE; 82 long historyEnd = Long.MIN_VALUE; 83 if (chartData != null) { 84 historyStart = chartData.network.getStart(); 85 historyEnd = chartData.network.getEnd(); 86 } 87 88 final long now = System.currentTimeMillis(); 89 if (historyStart == Long.MAX_VALUE) historyStart = now; 90 if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; 91 92 boolean hasCycles = false; 93 if (policy != null) { 94 final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager 95 .cycleIterator(policy); 96 while (it.hasNext()) { 97 final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next(); 98 final long cycleStart = cycle.first.toInstant().toEpochMilli(); 99 final long cycleEnd = cycle.second.toInstant().toEpochMilli(); 100 101 final boolean includeCycle; 102 if (chartData != null) { 103 entry = chartData.network.getValues(cycleStart, cycleEnd, entry); 104 includeCycle = (entry.rxBytes + entry.txBytes) > 0; 105 } else { 106 includeCycle = true; 107 } 108 109 if (includeCycle) { 110 add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); 111 hasCycles = true; 112 } 113 } 114 } 115 116 if (!hasCycles) { 117 // no policy defined cycles; show entry for each four-week period 118 long cycleEnd = historyEnd; 119 while (cycleEnd > historyStart) { 120 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); 121 122 final boolean includeCycle; 123 if (chartData != null) { 124 entry = chartData.network.getValues(cycleStart, cycleEnd, entry); 125 includeCycle = (entry.rxBytes + entry.txBytes) > 0; 126 } else { 127 includeCycle = true; 128 } 129 130 if (includeCycle) { 131 add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); 132 } 133 cycleEnd = cycleStart; 134 } 135 } 136 137 // force pick the current cycle (first item) 138 if (getCount() > 0) { 139 final int position = findNearestPosition(previousItem); 140 mSpinner.setSelection(position); 141 142 // only force-update cycle when changed; skipping preserves any 143 // user-defined inspection region. 144 final CycleAdapter.CycleItem selectedItem = getItem(position); 145 if (!Objects.equal(selectedItem, previousItem)) { 146 mListener.onItemSelected(null, null, position, 0); 147 return false; 148 } 149 } 150 return true; 151 } 152 153 /** 154 * List item that reflects a specific data usage cycle. 155 */ 156 public static class CycleItem implements Comparable<CycleItem> { 157 public CharSequence label; 158 public long start; 159 public long end; 160 161 public CycleItem(CharSequence label) { 162 this.label = label; 163 } 164 165 public CycleItem(Context context, long start, long end) { 166 this.label = Utils.formatDateRange(context, start, end); 167 this.start = start; 168 this.end = end; 169 } 170 171 @Override 172 public String toString() { 173 return label.toString(); 174 } 175 176 @Override 177 public boolean equals(Object o) { 178 if (o instanceof CycleItem) { 179 final CycleItem another = (CycleItem) o; 180 return start == another.start && end == another.end; 181 } 182 return false; 183 } 184 185 @Override 186 public int compareTo(CycleItem another) { 187 return Long.compare(start, another.start); 188 } 189 } 190 191 public interface SpinnerInterface { 192 void setAdapter(CycleAdapter cycleAdapter); 193 void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); 194 Object getSelectedItem(); 195 void setSelection(int position); 196 } 197} 198