1 package com.android.hotspot2.flow; 2 3 import android.net.wifi.ScanResult; 4 import android.os.Parcel; 5 import android.os.Parcelable; 6 import android.util.Log; 7 8 import com.android.anqp.HSIconFileElement; 9 import com.android.anqp.I18Name; 10 import com.android.anqp.IconInfo; 11 import com.android.anqp.OSUProvider; 12 import com.android.hotspot2.Utils; 13 import com.android.hotspot2.osu.OSUManager; 14 15 import java.io.IOException; 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.HashSet; 19 import java.util.List; 20 import java.util.ListIterator; 21 import java.util.Locale; 22 import java.util.Set; 23 24 public 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