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