1 /* 2 * Copyright (C) 2017 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.tuner.util; 18 19 import android.content.Context; 20 import android.location.Address; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.Nullable; 23 import android.text.TextUtils; 24 import android.util.Log; 25 import com.android.tv.tuner.TunerPreferences; 26 import com.android.tv.util.LocationUtils; 27 28 import java.io.IOException; 29 import java.util.HashMap; 30 import java.util.Locale; 31 import java.util.Map; 32 import java.util.regex.Pattern; 33 34 /** 35 * A utility class to update, get, and set the last known postal or zip code. 36 */ 37 public class PostalCodeUtils { 38 private static final String TAG = "PostalCodeUtils"; 39 40 // Postcode formats, where A signifies a letter and 9 a digit: 41 // US zip code format: 99999 42 private static final String POSTCODE_REGEX_US = "^(\\d{5})"; 43 // UK postcode district formats: A9, A99, AA9, AA99 44 // Full UK postcode format: Postcode District + space + 9AA 45 // Should be able to handle both postcode district and full postcode 46 private static final String POSTCODE_REGEX_GB = 47 "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$"; 48 private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode 49 50 private static final Map<String, Pattern> REGION_PATTERN = new HashMap<>(); 51 private static final Map<String, Integer> REGION_MAX_LENGTH = new HashMap<>(); 52 53 static { 54 REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US)); 55 REGION_PATTERN.put( 56 Locale.UK.getCountry(), 57 Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR)); 58 REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5); 59 REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8); 60 } 61 62 // The longest postcode number is 10-character-long. 63 // Use a larger number to accommodate future changes. 64 private static final int DEFAULT_MAX_LENGTH = 16; 65 66 /** Returns {@code true} if postal code has been changed */ 67 public static boolean updatePostalCode(Context context) 68 throws IOException, SecurityException, NoPostalCodeException { 69 String postalCode = getPostalCode(context); 70 String lastPostalCode = getLastPostalCode(context); 71 if (TextUtils.isEmpty(postalCode)) { 72 if (TextUtils.isEmpty(lastPostalCode)) { 73 throw new NoPostalCodeException(); 74 } 75 } else if (!TextUtils.equals(postalCode, lastPostalCode)) { 76 setLastPostalCode(context, postalCode); 77 return true; 78 } 79 return false; 80 } 81 82 /** 83 * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or 84 * input by users. 85 */ 86 public static String getLastPostalCode(Context context) { 87 return TunerPreferences.getLastPostalCode(context); 88 } 89 90 /** 91 * Sets the last stored postal or zip code. This method will overwrite the value written by 92 * calling {@link #updatePostalCode(Context)}. 93 */ 94 public static void setLastPostalCode(Context context, String postalCode) { 95 Log.i(TAG, "Set Postal Code:" + postalCode); 96 TunerPreferences.setLastPostalCode(context, postalCode); 97 } 98 99 @Nullable 100 private static String getPostalCode(Context context) throws IOException, SecurityException { 101 Address address = LocationUtils.getCurrentAddress(context); 102 if (address != null) { 103 Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", " 104 + address.getPostalCode()); 105 return address.getPostalCode(); 106 } 107 return null; 108 } 109 110 /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ 111 public static class NoPostalCodeException extends Exception { 112 public NoPostalCodeException() { 113 } 114 } 115 116 /** 117 * Checks whether a postcode matches the format of the specific region. 118 * 119 * @return {@code false} if the region is supported and the postcode doesn't match; {@code true} 120 * otherwise 121 */ 122 public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) { 123 Pattern pattern = REGION_PATTERN.get(region.toUpperCase()); 124 return pattern == null || pattern.matcher(postcode).matches(); 125 } 126 127 /** 128 * Gets the largest possible postcode length in the region. 129 * 130 * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH} 131 * otherwise 132 */ 133 public static int getRegionMaxLength(Context context) { 134 Integer maxLength = 135 REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); 136 return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; 137 } 138 }