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
17package com.android.tv.tuner.util;
18
19import android.content.Context;
20import android.location.Address;
21import android.support.annotation.NonNull;
22import android.support.annotation.Nullable;
23import android.text.TextUtils;
24import android.util.Log;
25import com.android.tv.tuner.TunerPreferences;
26import com.android.tv.util.LocationUtils;
27
28import java.io.IOException;
29import java.util.HashMap;
30import java.util.Locale;
31import java.util.Map;
32import java.util.regex.Pattern;
33
34/**
35 * A utility class to update, get, and set the last known postal or zip code.
36 */
37public 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}