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