1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.data;
18 
19 import android.support.annotation.NonNull;
20 import android.text.TextUtils;
21 import android.view.KeyEvent;
22 import com.android.tv.common.util.StringUtils;
23 import com.android.tv.data.api.Channel;
24 import java.util.Objects;
25 
26 /** A convenience class to handle channel number. */
27 public final class ChannelNumber implements Comparable<ChannelNumber> {
28     private static final int[] CHANNEL_DELIMITER_KEYCODES = {
29         KeyEvent.KEYCODE_MINUS,
30         KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
31         KeyEvent.KEYCODE_PERIOD,
32         KeyEvent.KEYCODE_NUMPAD_DOT,
33         KeyEvent.KEYCODE_SPACE
34     };
35 
36     /** The major part of the channel number. */
37     public String majorNumber;
38     /** The flag which indicates whether it has a delimiter or not. */
39     public boolean hasDelimiter;
40     /** The major part of the channel number. */
41     public String minorNumber;
42 
ChannelNumber()43     public ChannelNumber() {
44         reset();
45     }
46 
47     /**
48      * {@code lhs} and {@code rhs} are equivalent if {@link ChannelNumber#compare(String, String)}
49      * is 0 or if only one has a delimiter and both {@link ChannelNumber#majorNumber} equals.
50      */
equivalent(String lhs, String rhs)51     public static boolean equivalent(String lhs, String rhs) {
52         if (compare(lhs, rhs) == 0) {
53             return true;
54         }
55         // Match if only one has delimiter
56         ChannelNumber lhsNumber = parseChannelNumber(lhs);
57         ChannelNumber rhsNumber = parseChannelNumber(rhs);
58         return lhsNumber != null
59                 && rhsNumber != null
60                 && lhsNumber.hasDelimiter != rhsNumber.hasDelimiter
61                 && lhsNumber.majorNumber.equals(rhsNumber.majorNumber);
62     }
63 
reset()64     public void reset() {
65         setChannelNumber("", false, "");
66     }
67 
setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber)68     private void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) {
69         this.majorNumber = majorNumber;
70         this.hasDelimiter = hasDelimiter;
71         this.minorNumber = minorNumber;
72     }
73 
74     @Override
toString()75     public String toString() {
76         if (hasDelimiter) {
77             return majorNumber + Channel.CHANNEL_NUMBER_DELIMITER + minorNumber;
78         }
79         return majorNumber;
80     }
81 
82     @Override
compareTo(@onNull ChannelNumber another)83     public int compareTo(@NonNull ChannelNumber another) {
84         int major = Integer.parseInt(majorNumber);
85         int minor = hasDelimiter ? Integer.parseInt(minorNumber) : 0;
86 
87         int opponentMajor = Integer.parseInt(another.majorNumber);
88         int opponentMinor = another.hasDelimiter ? Integer.parseInt(another.minorNumber) : 0;
89         if (major == opponentMajor) {
90             return minor - opponentMinor;
91         }
92         return major - opponentMajor;
93     }
94 
95     @Override
equals(Object obj)96     public boolean equals(Object obj) {
97         if (obj instanceof ChannelNumber) {
98             ChannelNumber channelNumber = (ChannelNumber) obj;
99             return TextUtils.equals(majorNumber, channelNumber.majorNumber)
100                     && TextUtils.equals(minorNumber, channelNumber.minorNumber)
101                     && hasDelimiter == channelNumber.hasDelimiter;
102         }
103         return super.equals(obj);
104     }
105 
106     @Override
hashCode()107     public int hashCode() {
108         return Objects.hash(majorNumber, hasDelimiter, minorNumber);
109     }
110 
isChannelNumberDelimiterKey(int keyCode)111     public static boolean isChannelNumberDelimiterKey(int keyCode) {
112         for (int delimiterKeyCode : CHANNEL_DELIMITER_KEYCODES) {
113             if (delimiterKeyCode == keyCode) {
114                 return true;
115             }
116         }
117         return false;
118     }
119 
120     /**
121      * Returns the ChannelNumber instance.
122      *
123      * <p>Note that all the channel number argument should be normalized by {@link
124      * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
125      * are already normalized.
126      */
parseChannelNumber(String number)127     public static ChannelNumber parseChannelNumber(String number) {
128         if (number == null) {
129             return null;
130         }
131         ChannelNumber ret = new ChannelNumber();
132         int indexOfDelimiter = number.indexOf(Channel.CHANNEL_NUMBER_DELIMITER);
133         if (indexOfDelimiter == 0 || indexOfDelimiter == number.length() - 1) {
134             return null;
135         } else if (indexOfDelimiter < 0) {
136             ret.majorNumber = number;
137             if (!isInteger(ret.majorNumber)) {
138                 return null;
139             }
140         } else {
141             ret.hasDelimiter = true;
142             ret.majorNumber = number.substring(0, indexOfDelimiter);
143             ret.minorNumber = number.substring(indexOfDelimiter + 1);
144             if (!isInteger(ret.majorNumber) || !isInteger(ret.minorNumber)) {
145                 return null;
146             }
147         }
148         return ret;
149     }
150 
151     /**
152      * Compares the channel numbers.
153      *
154      * <p>Note that all the channel number arguments should be normalized by {@link
155      * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
156      * are already normalized.
157      */
compare(String lhs, String rhs)158     public static int compare(String lhs, String rhs) {
159         ChannelNumber lhsNumber = parseChannelNumber(lhs);
160         ChannelNumber rhsNumber = parseChannelNumber(rhs);
161         // Null first
162         if (lhsNumber == null && rhsNumber == null) {
163             return StringUtils.compare(lhs, rhs);
164         } else if (lhsNumber == null /* && rhsNumber != null */) {
165             return -1;
166         } else if (rhsNumber == null) {
167             return 1;
168         }
169         return lhsNumber.compareTo(rhsNumber);
170     }
171 
isInteger(String string)172     private static boolean isInteger(String string) {
173         try {
174             Integer.parseInt(string);
175         } catch (NumberFormatException | NullPointerException e) {
176             return false;
177         }
178         return true;
179     }
180 }
181