summaryrefslogtreecommitdiff
path: root/zxing-android-embedded/src/com/google/zxing/client/android/DecodeHintManager.java
blob: 02c230c451b571fecbf7d39a6d8096cf590fb0bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
 * Copyright (C) 2013 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.zxing.client.android;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import com.google.zxing.DecodeHintType;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author Lachezar Dobrev
 */
public final class DecodeHintManager {

    private static final String TAG = DecodeHintManager.class.getSimpleName();

    // This pattern is used in decoding integer arrays.
    private static final Pattern COMMA = Pattern.compile(",");

    private DecodeHintManager() {
    }

    /**
     * <p>Split a query string into a list of name-value pairs.</p>
     *
     * <p>This is an alternative to the {@link Uri#getQueryParameterNames()} and
     * {@link Uri#getQueryParameters(String)}, which are quirky and not suitable
     * for exist-only Uri parameters.</p>
     *
     * <p>This method ignores multiple parameters with the same name and returns the
     * first one only. This is technically incorrect, but should be acceptable due
     * to the method of processing Hints: no multiple values for a hint.</p>
     *
     * @param query query to split
     * @return name-value pairs
     */
    private static Map<String, String> splitQuery(String query) {
        Map<String, String> map = new HashMap<>();
        int pos = 0;
        while (pos < query.length()) {
            if (query.charAt(pos) == '&') {
                // Skip consecutive ampersand separators.
                pos++;
                continue;
            }
            int amp = query.indexOf('&', pos);
            int equ = query.indexOf('=', pos);
            if (amp < 0) {
                // This is the last element in the query, no more ampersand elements.
                String name;
                String text;
                if (equ < 0) {
                    // No equal sign
                    name = query.substring(pos);
                    name = name.replace('+', ' '); // Preemptively decode +
                    name = Uri.decode(name);
                    text = "";
                } else {
                    // Split name and text.
                    name = query.substring(pos, equ);
                    name = name.replace('+', ' '); // Preemptively decode +
                    name = Uri.decode(name);
                    text = query.substring(equ + 1);
                    text = text.replace('+', ' '); // Preemptively decode +
                    text = Uri.decode(text);
                }
                if (!map.containsKey(name)) {
                    map.put(name, text);
                }
                break;
            }
            if (equ < 0 || equ > amp) {
                // No equal sign until the &: this is a simple parameter with no value.
                String name = query.substring(pos, amp);
                name = name.replace('+', ' '); // Preemptively decode +
                name = Uri.decode(name);
                if (!map.containsKey(name)) {
                    map.put(name, "");
                }
                pos = amp + 1;
                continue;
            }
            String name = query.substring(pos, equ);
            name = name.replace('+', ' '); // Preemptively decode +
            name = Uri.decode(name);
            String text = query.substring(equ + 1, amp);
            text = text.replace('+', ' '); // Preemptively decode +
            text = Uri.decode(text);
            if (!map.containsKey(name)) {
                map.put(name, text);
            }
            pos = amp + 1;
        }
        return map;
    }

    static Map<DecodeHintType, ?> parseDecodeHints(Uri inputUri) {
        String query = inputUri.getEncodedQuery();
        if (query == null || query.isEmpty()) {
            return null;
        }

        // Extract parameters
        Map<String, String> parameters = splitQuery(query);

        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);

        for (DecodeHintType hintType : DecodeHintType.values()) {

            if (hintType == DecodeHintType.CHARACTER_SET ||
                    hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
                    hintType == DecodeHintType.POSSIBLE_FORMATS) {
                continue; // This hint is specified in another way
            }

            String parameterName = hintType.name();
            String parameterText = parameters.get(parameterName);
            if (parameterText == null) {
                continue;
            }
            if (hintType.getValueType().equals(Object.class)) {
                // This is an unspecified type of hint content. Use the value as is.
                // TODO: Can we make a different assumption on this?
                hints.put(hintType, parameterText);
                continue;
            }
            if (hintType.getValueType().equals(Void.class)) {
                // Void hints are just flags: use the constant specified by DecodeHintType
                hints.put(hintType, Boolean.TRUE);
                continue;
            }
            if (hintType.getValueType().equals(String.class)) {
                // A string hint: use the decoded value.
                hints.put(hintType, parameterText);
                continue;
            }
            if (hintType.getValueType().equals(Boolean.class)) {
                // A boolean hint: a few values for false, everything else is true.
                // An empty parameter is simply a flag-style parameter, assuming true
                if (parameterText.isEmpty()) {
                    hints.put(hintType, Boolean.TRUE);
                } else if ("0".equals(parameterText) ||
                        "false".equalsIgnoreCase(parameterText) ||
                        "no".equalsIgnoreCase(parameterText)) {
                    hints.put(hintType, Boolean.FALSE);
                } else {
                    hints.put(hintType, Boolean.TRUE);
                }

                continue;
            }
            if (hintType.getValueType().equals(int[].class)) {
                // An integer array. Used to specify valid lengths.
                // Strip a trailing comma as in Java style array initialisers.
                if (!parameterText.isEmpty() && parameterText.charAt(parameterText.length() - 1) == ',') {
                    parameterText = parameterText.substring(0, parameterText.length() - 1);
                }
                String[] values = COMMA.split(parameterText);
                int[] array = new int[values.length];
                for (int i = 0; i < values.length; i++) {
                    try {
                        array[i] = Integer.parseInt(values[i]);
                    } catch (NumberFormatException ignored) {
                        Log.w(TAG, "Skipping array of integers hint " + hintType + " due to invalid numeric value: '" + values[i] + '\'');
                        array = null;
                        break;
                    }
                }
                if (array != null) {
                    hints.put(hintType, array);
                }
                continue;
            }
            Log.w(TAG, "Unsupported hint type '" + hintType + "' of type " + hintType.getValueType());
        }

        Log.i(TAG, "Hints from the URI: " + hints);
        return hints;
    }

    public static Map<DecodeHintType, Object> parseDecodeHints(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras == null || extras.isEmpty()) {
            return null;
        }
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);

        for (DecodeHintType hintType : DecodeHintType.values()) {

            if (hintType == DecodeHintType.CHARACTER_SET ||
                    hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
                    hintType == DecodeHintType.POSSIBLE_FORMATS) {
                continue; // This hint is specified in another way
            }

            String hintName = hintType.name();
            if (extras.containsKey(hintName)) {
                if (hintType.getValueType().equals(Void.class)) {
                    // Void hints are just flags: use the constant specified by the DecodeHintType
                    hints.put(hintType, Boolean.TRUE);
                } else {
                    Object hintData = extras.get(hintName);
                    if (hintType.getValueType().isInstance(hintData)) {
                        hints.put(hintType, hintData);
                    } else {
                        Log.w(TAG, "Ignoring hint " + hintType + " because it is not assignable from " + hintData);
                    }
                }
            }
        }

        Log.i(TAG, "Hints from the Intent: " + hints);
        return hints;
    }
}