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.NetworkStatsHistory;
19import android.text.format.DateUtils;
20import android.widget.AdapterView;
21import android.widget.ArrayAdapter;
22import com.android.settings.R;
23import com.android.settings.Utils;
24import com.android.settingslib.net.ChartData;
25import libcore.util.Objects;
26
27import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
28import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
29
30public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
31
32    private final SpinnerInterface mSpinner;
33    private final AdapterView.OnItemSelectedListener mListener;
34
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     */
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     */
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
158        public CycleItem(CharSequence label) {
159            this.label = label;
160        }
161
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
169        public String toString() {
170            return label.toString();
171        }
172
173        @Override
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
183        public int compareTo(CycleItem another) {
184            return Long.compare(start, another.start);
185        }
186    }
187
188    public interface SpinnerInterface {
189        void setAdapter(CycleAdapter cycleAdapter);
190        void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener);
191        Object getSelectedItem();
192        void setSelection(int position);
193    }
194}
195