1package com.android.hotspot2.flow;
2
3import android.net.wifi.ScanResult;
4import android.os.Parcel;
5import android.os.Parcelable;
6import android.util.Log;
7
8import com.android.anqp.HSIconFileElement;
9import com.android.anqp.I18Name;
10import com.android.anqp.IconInfo;
11import com.android.anqp.OSUProvider;
12import com.android.hotspot2.Utils;
13import com.android.hotspot2.osu.OSUManager;
14
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.HashSet;
19import java.util.List;
20import java.util.ListIterator;
21import java.util.Locale;
22import java.util.Set;
23
24public class OSUInfo implements Parcelable {
25    public static final String GenericLocale = "zxx";
26
27    public enum IconStatus {
28        NotQueried,     //
29        InProgress,     // Query pending
30        NotAvailable,   // Deterministically unavailable
31        Available       // Icon data retrieved
32    }
33
34    private final long mBSSID;
35    private final long mHESSID;
36    private final int mAnqpDomID;
37    private final String mAdvertisingSSID;
38    private final OSUProvider mOSUProvider;
39    private final int mOsuID;
40    private long mOSUBssid;
41    private IconStatus mIconStatus = IconStatus.NotQueried;
42    private HSIconFileElement mIconFileElement;
43    private String mIconFileName;
44    private IconInfo mIconInfo;
45
46    public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) {
47        mOsuID = osuID;
48        mBSSID = Utils.parseMac(scanResult.BSSID);
49        mHESSID = scanResult.hessid;
50        mAnqpDomID = scanResult.anqpDomainId;
51        mAdvertisingSSID = scanResult.SSID;
52        mOSUProvider = osuProvider;
53    }
54
55    public long getOSUBssid() {
56        return mOSUBssid;
57    }
58
59    public void setOSUBssid(long OSUBssid) {
60        mOSUBssid = OSUBssid;
61    }
62
63    public long getHESSID() {
64        return mHESSID;
65    }
66
67    public int getAnqpDomID() {
68        return mAnqpDomID;
69    }
70
71    public String getAdvertisingSsid() {
72        return mAdvertisingSSID;
73    }
74
75    public Set<Locale> getNameLocales() {
76        Set<Locale> locales = new HashSet<>(mOSUProvider.getNames().size());
77        for (I18Name name : mOSUProvider.getNames()) {
78            locales.add(name.getLocale());
79        }
80        return locales;
81    }
82
83    public Set<Locale> getServiceLocales() {
84        Set<Locale> locales = new HashSet<>(mOSUProvider.getServiceDescriptions().size());
85        for (I18Name name : mOSUProvider.getServiceDescriptions()) {
86            locales.add(name.getLocale());
87        }
88        return locales;
89    }
90
91    public Set<String> getIconLanguages() {
92        Set<String> locales = new HashSet<>(mOSUProvider.getIcons().size());
93        for (IconInfo iconInfo : mOSUProvider.getIcons()) {
94            locales.add(iconInfo.getLanguage());
95        }
96        return locales;
97    }
98
99    public String getName(Locale locale) {
100        List<ScoreEntry<String>> scoreList = new ArrayList<>();
101        for (I18Name name : mOSUProvider.getNames()) {
102            if (locale == null || name.getLocale().equals(locale)) {
103                return name.getText();
104            }
105            scoreList.add(new ScoreEntry<>(name.getText(),
106                    languageScore(name.getLanguage(), locale)));
107        }
108        Collections.sort(scoreList);
109        return scoreList.isEmpty() ? null : scoreList.get(scoreList.size() - 1).getData();
110    }
111
112    public String getServiceDescription(Locale locale) {
113        List<ScoreEntry<String>> scoreList = new ArrayList<>();
114        for (I18Name service : mOSUProvider.getServiceDescriptions()) {
115            if (locale == null || service.getLocale().equals(locale)) {
116                return service.getText();
117            }
118            scoreList.add(new ScoreEntry<>(service.getText(),
119                    languageScore(service.getLanguage(), locale)));
120        }
121        Collections.sort(scoreList);
122        return scoreList.isEmpty() ? null : scoreList.get(scoreList.size() - 1).getData();
123    }
124
125    public int getOsuID() {
126        return mOsuID;
127    }
128
129    public void setIconStatus(IconStatus iconStatus) {
130        synchronized (mOSUProvider) {
131            mIconStatus = iconStatus;
132        }
133    }
134
135    public IconStatus getIconStatus() {
136        synchronized (mOSUProvider) {
137            return mIconStatus;
138        }
139    }
140
141    public HSIconFileElement getIconFileElement() {
142        synchronized (mOSUProvider) {
143            return mIconFileElement;
144        }
145    }
146
147    public IconInfo getIconInfo() {
148        synchronized (mOSUProvider) {
149            return mIconInfo;
150        }
151    }
152
153    public String getIconFileName() {
154        return mIconFileName;
155    }
156
157    public void setIconFileElement(HSIconFileElement iconFileElement, String fileName) {
158        synchronized (mOSUProvider) {
159            mIconFileElement = iconFileElement;
160            for (IconInfo iconInfo : mOSUProvider.getIcons()) {
161                if (iconInfo.getFileName().equals(fileName)) {
162                    mIconInfo = iconInfo;
163                    mIconFileName = fileName;
164                    break;
165                }
166            }
167            mIconStatus = IconStatus.Available;
168        }
169    }
170
171    private static class ScoreEntry<T> implements Comparable<ScoreEntry> {
172        private final T mData;
173        private final int mScore;
174
175        private ScoreEntry(T data, int score) {
176            mData = data;
177            mScore = score;
178        }
179
180        public T getData() {
181            return mData;
182        }
183
184        @Override
185        public int compareTo(ScoreEntry other) {
186            return Integer.compare(mScore, other.mScore);
187        }
188
189        @Override
190        public String toString() {
191            return String.format("%d for '%s'", mScore, mData);
192        }
193    }
194
195    public List<IconInfo> getIconInfo(Locale locale, Set<String> types, int width, int height) {
196        if (mOSUProvider.getIcons().isEmpty()) {
197            return null;
198        }
199        Log.d(OSUManager.TAG, "Matching icons against " + locale
200                + ", types " + types + ", " + width + "*" + height);
201
202        List<ScoreEntry<IconInfo>> matches = new ArrayList<>();
203        for (IconInfo iconInfo : mOSUProvider.getIcons()) {
204            Log.d(OSUManager.TAG, "Checking icon " + iconInfo.toString());
205            if (!types.contains(iconInfo.getIconType())) {
206                continue;
207            }
208
209            int score = languageScore(iconInfo.getLanguage(), locale);
210            int delta = iconInfo.getWidth() - width;
211            // Best size score is 1024 for a exact match, i.e. 2048 if both sides match
212            if (delta >= 0) {
213                score += (256 - delta) * 4;  // Prefer down-scaling
214            } else {
215                score += 256 + delta;    // Before up-scaling
216            }
217            delta = iconInfo.getHeight() - height;
218            if (delta >= 0) {
219                score += (256 - delta) * 4;
220            } else {
221                score += 256 + delta;
222            }
223            matches.add(new ScoreEntry<>(iconInfo, score));
224        }
225        if (matches.isEmpty()) {
226            return Collections.emptyList();
227        }
228        Collections.sort(matches);
229        List<IconInfo> icons = new ArrayList<>(matches.size());
230        ListIterator<ScoreEntry<IconInfo>> matchIterator = matches.listIterator(matches.size());
231        while (matchIterator.hasPrevious()) {
232            icons.add(matchIterator.previous().getData());
233        }
234        return icons;
235    }
236
237    private static int languageScore(String language, Locale locale) {
238        if (language.length() == 3 && language.equalsIgnoreCase(locale.getISO3Language()) ||
239                language.length() == 2 && language.equalsIgnoreCase(locale.getLanguage())) {
240            return 4096;
241        } else if (language.equalsIgnoreCase(GenericLocale)) {
242            return 3072;
243        } else if (language.equalsIgnoreCase("eng")) {
244            return 2048;
245        } else {
246            return 1024;
247        }
248    }
249
250    public long getBSSID() {
251        return mBSSID;
252    }
253
254    public String getOsuSsid() {
255        return mOSUProvider.getSSID();
256    }
257
258    public OSUProvider getOSUProvider() {
259        return mOSUProvider;
260    }
261
262    @Override
263    public String toString() {
264        return String.format("OSU Info '%s' %012x -> %s, icon %s",
265                getOsuSsid(), mBSSID, getServiceDescription(null), mIconStatus);
266    }
267
268    private OSUInfo(Parcel in) {
269        mBSSID = in.readLong();
270        mHESSID = in.readLong();
271        mAnqpDomID = in.readInt();
272        mAdvertisingSSID = in.readString();
273        mOsuID = in.readInt();
274        mOSUBssid = in.readLong();
275        mIconFileName = in.readString();
276        mIconStatus = Utils.mapEnum(in.readInt(), IconStatus.class);
277        OSUProvider osuProvider;
278        try {
279            osuProvider = new OSUProvider(in);
280        } catch (IOException ioe) {
281            osuProvider = null;
282        }
283        mOSUProvider = osuProvider;
284        if (osuProvider == null) {
285            return;
286        }
287        mIconFileElement = new HSIconFileElement(in);
288        int iconIndex = in.readInt();
289        mIconInfo = iconIndex >= 0 && iconIndex < mOSUProvider.getIcons().size()
290                ? mOSUProvider.getIcons().get(iconIndex) : null;
291    }
292
293    public static final Parcelable.Creator<OSUInfo> CREATOR = new Parcelable.Creator<OSUInfo>() {
294        public OSUInfo createFromParcel(Parcel in) {
295            return new OSUInfo(in);
296        }
297
298        public OSUInfo[] newArray(int size) {
299            return new OSUInfo[size];
300        }
301    };
302
303    @Override
304    public int describeContents() {
305        return 0;
306    }
307
308    @Override
309    public void writeToParcel(Parcel dest, int flags) {
310        dest.writeLong(mBSSID);
311        dest.writeLong(mHESSID);
312        dest.writeInt(mAnqpDomID);
313        dest.writeString(mAdvertisingSSID);
314        dest.writeInt(mOsuID);
315        dest.writeLong(mOSUBssid);
316        dest.writeString(mIconFileName);
317        dest.writeInt(mIconStatus.ordinal());
318        mOSUProvider.writeParcel(dest);
319        mIconFileElement.writeParcel(dest);
320    }
321}
322