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