Home | History | Annotate | Download | only in datausage
      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 import android.widget.ArrayAdapter;
     24 
     25 import com.android.settings.R;
     26 import com.android.settings.Utils;
     27 import com.android.settingslib.net.ChartData;
     28 
     29 import java.time.ZonedDateTime;
     30 import java.util.Iterator;
     31 import java.util.Objects;
     32 
     33 public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
     34 
     35     private final SpinnerInterface mSpinner;
     36     private final AdapterView.OnItemSelectedListener mListener;
     37 
     38     public CycleAdapter(Context context, SpinnerInterface spinner,
     39             AdapterView.OnItemSelectedListener listener, boolean isHeader) {
     40         super(context, isHeader ? R.layout.filter_spinner_item
     41                 : R.layout.data_usage_cycle_item);
     42         setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
     43         mSpinner = spinner;
     44         mListener = listener;
     45         mSpinner.setAdapter(this);
     46         mSpinner.setOnItemSelectedListener(mListener);
     47     }
     48 
     49     /**
     50      * Find position of {@link CycleItem} in this adapter which is nearest
     51      * the given {@link CycleItem}.
     52      */
     53     public int findNearestPosition(CycleItem target) {
     54         if (target != null) {
     55             final int count = getCount();
     56             for (int i = count - 1; i >= 0; i--) {
     57                 final CycleItem item = getItem(i);
     58                 if (item.compareTo(target) >= 0) {
     59                     return i;
     60                 }
     61             }
     62         }
     63         return 0;
     64     }
     65 
     66     /**
     67      * Rebuild list based on {@link NetworkPolicy} and available
     68      * {@link NetworkStatsHistory} data. Always selects the newest item,
     69      * updating the inspection range on chartData.
     70      */
     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      * List item that reflects a specific data usage cycle.
    154      */
    155     public static class CycleItem implements Comparable<CycleItem> {
    156         public CharSequence label;
    157         public long start;
    158         public long end;
    159 
    160         public CycleItem(CharSequence label) {
    161             this.label = label;
    162         }
    163 
    164         public CycleItem(Context context, long start, long end) {
    165             this.label = Utils.formatDateRange(context, start, end);
    166             this.start = start;
    167             this.end = end;
    168         }
    169 
    170         @Override
    171         public String toString() {
    172             return label.toString();
    173         }
    174 
    175         @Override
    176         public boolean equals(Object o) {
    177             if (o instanceof CycleItem) {
    178                 final CycleItem another = (CycleItem) o;
    179                 return start == another.start && end == another.end;
    180             }
    181             return false;
    182         }
    183 
    184         @Override
    185         public int compareTo(CycleItem another) {
    186             return Long.compare(start, another.start);
    187         }
    188     }
    189 
    190     public interface SpinnerInterface {
    191         void setAdapter(CycleAdapter cycleAdapter);
    192         void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener);
    193         Object getSelectedItem();
    194         void setSelection(int position);
    195     }
    196 }
    197