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 */ 14 package com.android.settings.datausage; 15 16 import android.content.Context; 17 import android.net.NetworkPolicy; 18 import android.net.NetworkPolicyManager; 19 import android.net.NetworkStatsHistory; 20 import android.text.format.DateUtils; 21 import android.util.Pair; 22 import android.widget.AdapterView; 23 24 import com.android.settings.Utils; 25 import com.android.settingslib.net.ChartData; 26 import com.android.settingslib.net.NetworkCycleData; 27 import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter; 28 29 import java.time.ZonedDateTime; 30 import java.util.Iterator; 31 import java.util.List; 32 import java.util.Objects; 33 34 public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> { 35 36 private final SpinnerInterface mSpinner; 37 private final AdapterView.OnItemSelectedListener mListener; 38 CycleAdapter(Context context, SpinnerInterface spinner, AdapterView.OnItemSelectedListener listener)39 public CycleAdapter(Context context, SpinnerInterface spinner, 40 AdapterView.OnItemSelectedListener listener) { 41 super(context); 42 mSpinner = spinner; 43 mListener = listener; 44 mSpinner.setAdapter(this); 45 mSpinner.setOnItemSelectedListener(mListener); 46 } 47 48 /** 49 * Find position of {@link CycleItem} in this adapter which is nearest 50 * the given {@link CycleItem}. 51 */ findNearestPosition(CycleItem target)52 public int findNearestPosition(CycleItem target) { 53 if (target != null) { 54 final int count = getCount(); 55 for (int i = count - 1; i >= 0; i--) { 56 final CycleItem item = getItem(i); 57 if (item.compareTo(target) >= 0) { 58 return i; 59 } 60 } 61 } 62 return 0; 63 } 64 65 /** 66 * Rebuild list based on {@link NetworkPolicy} and available 67 * {@link NetworkStatsHistory} data. Always selects the newest item, 68 * updating the inspection range on chartData. 69 */ 70 @Deprecated updateCycleList(NetworkPolicy policy, ChartData chartData)71 public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { 72 // stash away currently selected cycle to try restoring below 73 final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) 74 mSpinner.getSelectedItem(); 75 clear(); 76 77 final Context context = getContext(); 78 NetworkStatsHistory.Entry entry = null; 79 80 long historyStart = Long.MAX_VALUE; 81 long historyEnd = Long.MIN_VALUE; 82 if (chartData != null) { 83 historyStart = chartData.network.getStart(); 84 historyEnd = chartData.network.getEnd(); 85 } 86 87 final long now = System.currentTimeMillis(); 88 if (historyStart == Long.MAX_VALUE) historyStart = now; 89 if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; 90 91 boolean hasCycles = false; 92 if (policy != null) { 93 final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager 94 .cycleIterator(policy); 95 while (it.hasNext()) { 96 final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next(); 97 final long cycleStart = cycle.first.toInstant().toEpochMilli(); 98 final long cycleEnd = cycle.second.toInstant().toEpochMilli(); 99 100 final boolean includeCycle; 101 if (chartData != null) { 102 entry = chartData.network.getValues(cycleStart, cycleEnd, entry); 103 includeCycle = (entry.rxBytes + entry.txBytes) > 0; 104 } else { 105 includeCycle = true; 106 } 107 108 if (includeCycle) { 109 add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); 110 hasCycles = true; 111 } 112 } 113 } 114 115 if (!hasCycles) { 116 // no policy defined cycles; show entry for each four-week period 117 long cycleEnd = historyEnd; 118 while (cycleEnd > historyStart) { 119 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); 120 121 final boolean includeCycle; 122 if (chartData != null) { 123 entry = chartData.network.getValues(cycleStart, cycleEnd, entry); 124 includeCycle = (entry.rxBytes + entry.txBytes) > 0; 125 } else { 126 includeCycle = true; 127 } 128 129 if (includeCycle) { 130 add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); 131 } 132 cycleEnd = cycleStart; 133 } 134 } 135 136 // force pick the current cycle (first item) 137 if (getCount() > 0) { 138 final int position = findNearestPosition(previousItem); 139 mSpinner.setSelection(position); 140 141 // only force-update cycle when changed; skipping preserves any 142 // user-defined inspection region. 143 final CycleAdapter.CycleItem selectedItem = getItem(position); 144 if (!Objects.equals(selectedItem, previousItem)) { 145 mListener.onItemSelected(null, null, position, 0); 146 return false; 147 } 148 } 149 return true; 150 } 151 152 /** 153 * Rebuild list based on network data. Always selects the newest item, 154 * updating the inspection range on chartData. 155 */ updateCycleList(List<? extends NetworkCycleData> cycleData)156 public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { 157 // stash away currently selected cycle to try restoring below 158 final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) 159 mSpinner.getSelectedItem(); 160 clear(); 161 162 final Context context = getContext(); 163 for (NetworkCycleData data : cycleData) { 164 add(new CycleAdapter.CycleItem(context, data.getStartTime(), data.getEndTime())); 165 } 166 167 // force pick the current cycle (first item) 168 if (getCount() > 0) { 169 final int position = findNearestPosition(previousItem); 170 mSpinner.setSelection(position); 171 172 // only force-update cycle when changed; skipping preserves any 173 // user-defined inspection region. 174 final CycleAdapter.CycleItem selectedItem = getItem(position); 175 if (!Objects.equals(selectedItem, previousItem)) { 176 mListener.onItemSelected(null, null, position, 0); 177 return false; 178 } 179 } 180 return true; 181 } 182 183 /** 184 * List item that reflects a specific data usage cycle. 185 */ 186 public static class CycleItem implements Comparable<CycleItem> { 187 public CharSequence label; 188 public long start; 189 public long end; 190 CycleItem(CharSequence label)191 public CycleItem(CharSequence label) { 192 this.label = label; 193 } 194 CycleItem(Context context, long start, long end)195 public CycleItem(Context context, long start, long end) { 196 this.label = Utils.formatDateRange(context, start, end); 197 this.start = start; 198 this.end = end; 199 } 200 201 @Override toString()202 public String toString() { 203 return label.toString(); 204 } 205 206 @Override equals(Object o)207 public boolean equals(Object o) { 208 if (o instanceof CycleItem) { 209 final CycleItem another = (CycleItem) o; 210 return start == another.start && end == another.end; 211 } 212 return false; 213 } 214 215 @Override compareTo(CycleItem another)216 public int compareTo(CycleItem another) { 217 return Long.compare(start, another.start); 218 } 219 } 220 221 public interface SpinnerInterface { setAdapter(CycleAdapter cycleAdapter)222 void setAdapter(CycleAdapter cycleAdapter); 223 setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener)224 void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); 225 getSelectedItem()226 Object getSelectedItem(); 227 setSelection(int position)228 void setSelection(int position); 229 } 230 } 231