leds = AdvertisingInfo.stringToLedColors(
55 | prefs.getString(KEY_ROBOCAR_LED_SEQUENCE, null));
56 | if (id == null || (leds == null || leds.size() < 1)) {
57 | // insufficient or invalid data
58 | return null;
59 | }
60 | String pairToken = prefs.getString(KEY_ROBOCAR_PAIR_TOKEN, null);
61 | return new AdvertisingInfo(id, leds, pairToken);
62 | }
63 |
64 | public static void saveDiscovererInfo(SharedPreferences prefs, DiscovererInfo info) {
65 | if (info == null) {
66 | return;
67 | }
68 | SharedPreferences.Editor editor = prefs.edit()
69 | .putString(KEY_COMPANION_ID, info.mCompanionId);
70 | if (info.mIsPaired) {
71 | editor.putString(KEY_COMPANION_PAIR_TOKEN, info.mPairToken);
72 | } else {
73 | editor.remove(KEY_COMPANION_PAIR_TOKEN);
74 | }
75 | editor.apply();
76 | }
77 |
78 | public static @Nullable DiscovererInfo loadDiscovererInfo(SharedPreferences prefs) {
79 | String id = prefs.getString(KEY_COMPANION_ID, null);
80 | if (id == null) {
81 | return null;
82 | }
83 | String pairToken = prefs.getString(KEY_COMPANION_PAIR_TOKEN, null);
84 | return new DiscovererInfo(id, pairToken);
85 | }
86 |
87 | public static void clearDicovererInfo(SharedPreferences prefs) {
88 | prefs.edit().remove(KEY_COMPANION_ID).remove(KEY_COMPANION_PAIR_TOKEN).apply();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/example/androidthings/robocar/shared/lifecycle/ConfigResistantObserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc.
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 | package com.example.androidthings.robocar.shared.lifecycle;
17 |
18 | import android.arch.lifecycle.Lifecycle.Event;
19 | import android.arch.lifecycle.LifecycleObserver;
20 | import android.arch.lifecycle.OnLifecycleEvent;
21 | import android.os.Handler;
22 |
23 |
24 | /**
25 | * {@link LifecycleObserver} that avoids spurious stops and starts due to configuration changes.
26 | * Generally this would be used with a retained Fragment. If using with an Activity, take note that
27 | * the same instance of the observer needs to be used by the Activity instances on either side of a
28 | * configuration change.
29 | *
30 | * This trick is borrowed from {@link android.arch.lifecycle.ProcessLifecycleOwner}.
31 | */
32 | public class ConfigResistantObserver implements LifecycleObserver {
33 |
34 | private static final long TIMEOUT_MS = 700; //ms
35 |
36 | private Handler mHandler = new Handler();
37 | private boolean mStopSent = true;
38 | private int mStartCount = 0;
39 |
40 | private Runnable mDelayedStopRunnable = new Runnable() {
41 | @Override
42 | public void run() {
43 | if (mStartCount == 0) {
44 | mStopSent = true;
45 | onReallyStop();
46 | }
47 | }
48 | };
49 |
50 | @OnLifecycleEvent(Event.ON_START)
51 | public final void onStart() {
52 | mStartCount++;
53 | if (mStartCount == 1 && mStopSent) {
54 | mStopSent = false;
55 | onReallyStart();
56 | } else {
57 | mHandler.removeCallbacks(mDelayedStopRunnable);
58 | }
59 | }
60 |
61 | @OnLifecycleEvent(Event.ON_STOP)
62 | public final void onStop() {
63 | mStartCount--;
64 | if (mStartCount == 0) {
65 | mHandler.postDelayed(mDelayedStopRunnable, TIMEOUT_MS);
66 | }
67 | }
68 |
69 | protected void onReallyStart() {}
70 |
71 | protected void onReallyStop() {}
72 | }
73 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/example/androidthings/robocar/shared/model/AdvertisingInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc.
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 | package com.example.androidthings.robocar.shared.model;
17 |
18 | import android.graphics.Color;
19 | import android.text.TextUtils;
20 |
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.Locale;
25 | import java.util.Random;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 | /**
30 | * Immutable class representing advertising data parsed from a Robocar's advertising name.
31 | */
32 | public class AdvertisingInfo {
33 | private static final String ROBOCAR = "Robocar";
34 | private static final String SEPARATOR_COLON = ":";
35 | private static final String SEPARATOR_HYPHEN = "-";
36 |
37 | // Robocar:(\d{4}-\d{4}):([a-zA-Z-]+)(:(\S{5}))?
38 | private static final Pattern ADVERTISING_PATTERN = Pattern.compile(ROBOCAR + SEPARATOR_COLON
39 | + "(\\d{4}" + SEPARATOR_HYPHEN + "\\d{4})" + SEPARATOR_COLON
40 | + "([a-zA-z" + SEPARATOR_HYPHEN + "]+)(" + SEPARATOR_COLON + "(\\S{5}))?"
41 | );
42 |
43 | public enum LedColor {
44 | RED(Color.RED, "red"),
45 | GREEN(Color.GREEN, "green"),
46 | BLUE(Color.BLUE, "blue"),
47 | CYAN(Color.CYAN, "cyan"),
48 | MAGENTA(Color.MAGENTA, "magenta"),
49 | YELLOW(Color.YELLOW, "yellow"),
50 | WHITE(Color.WHITE, "white");
51 |
52 | final int mColor;
53 | final String mName; // TODO replace with resource IDs
54 |
55 | LedColor(int color, String name) {
56 | mColor = color;
57 | mName = name;
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | return mName;
63 | }
64 | }
65 |
66 | public static AdvertisingInfo generateAdvertisingInfo() {
67 | return new AdvertisingInfo(generateId(), generateLedSequence(), null);
68 | }
69 |
70 | static String generateId() {
71 | // generate an 8 digit id in the form ####-####
72 | Random r = new Random();
73 | return String.format(Locale.US,
74 | "%04d" + SEPARATOR_HYPHEN + "%04d", r.nextInt(10000), r.nextInt(10000));
75 | }
76 |
77 | static List generateLedSequence() {
78 | final int size = 4;
79 | List list = new ArrayList<>(size);
80 |
81 | final LedColor[] colors = LedColor.values();
82 | Random random = new Random();
83 | LedColor previous = null;
84 | for (int i = 0; i < size; i++) {
85 | LedColor color = colors[random.nextInt(colors.length)];
86 | if (color == previous) {
87 | i--; // pick again
88 | } else {
89 | list.add(color);
90 | previous = color;
91 | }
92 | }
93 | return list;
94 | }
95 |
96 | public static String ledColorsToString(List colors) {
97 | if (colors == null) {
98 | return "";
99 | }
100 |
101 | StringBuilder builder = new StringBuilder();
102 | for (LedColor ledColor : colors) {
103 | if (builder.length() > 0) {
104 | builder.append(SEPARATOR_HYPHEN);
105 | }
106 | builder.append(ledColor.name());
107 | }
108 | return builder.toString();
109 | }
110 |
111 | public static List stringToLedColors(String input) {
112 | if (input == null) {
113 | return Collections.emptyList();
114 | }
115 | String[] tokens = input.split(SEPARATOR_HYPHEN + "+");
116 | if (tokens.length < 1) {
117 | return Collections.emptyList();
118 | }
119 | List list = new ArrayList<>();
120 | for (String token : tokens) {
121 | try {
122 | LedColor color = LedColor.valueOf(token);
123 | list.add(color);
124 | } catch (IllegalArgumentException ignored) {
125 | }
126 | }
127 | return list;
128 | }
129 |
130 | public static AdvertisingInfo parseAdvertisingName(String name) {
131 | Matcher matcher = ADVERTISING_PATTERN.matcher(name);
132 | if (!matcher.matches()) {
133 | return null;
134 | }
135 | String id = matcher.group(1);
136 | List colors = stringToLedColors(matcher.group(2));
137 | String pairToken = matcher.group(4);
138 | return new AdvertisingInfo(id, colors, pairToken);
139 | }
140 |
141 | public final String mRobocarId;
142 | public final List mLedSequence;
143 |
144 | public final String mPairToken;
145 | public final boolean mIsPaired;
146 |
147 | private int mHashcode; // cache hashcode
148 |
149 | public AdvertisingInfo(String robocarId, List ledSequence, String pairToken) {
150 | if (robocarId == null) {
151 | throw new IllegalArgumentException("Robocar ID cannot be null");
152 | }
153 | mRobocarId = robocarId;
154 | mLedSequence = Collections.unmodifiableList(ledSequence);
155 | mPairToken = pairToken;
156 | mIsPaired = !TextUtils.isEmpty(mPairToken);
157 | }
158 |
159 | public String getAdvertisingName() {
160 | StringBuilder builder = new StringBuilder();
161 | builder.append(ROBOCAR)
162 | .append(SEPARATOR_COLON)
163 | .append(mRobocarId)
164 | .append(SEPARATOR_COLON)
165 | .append(ledColorsToString(mLedSequence));
166 | if (mPairToken != null) {
167 | builder.append(SEPARATOR_COLON)
168 | .append(mPairToken);
169 | }
170 | return builder.toString();
171 | }
172 |
173 | @Override
174 | public boolean equals(Object obj) {
175 | if (this == obj) {
176 | return true;
177 | }
178 | if (obj == null || !obj.getClass().equals(AdvertisingInfo.class)) {
179 | return false;
180 | }
181 | AdvertisingInfo other = (AdvertisingInfo) obj;
182 | return mRobocarId.equals(other.mRobocarId)
183 | && mLedSequence.equals(other.mLedSequence)
184 | && TextUtils.equals(mPairToken, other.mPairToken);
185 | }
186 |
187 | @Override
188 | public int hashCode() {
189 | int h = mHashcode;
190 | if (h == 0) {
191 | h = mRobocarId.hashCode();
192 | h *= 1000003;
193 | h ^= mLedSequence.hashCode();
194 | h *= 1000003;
195 | h ^= mPairToken == null ? 0 : mPairToken.hashCode();
196 | mHashcode = h;
197 | }
198 | return mHashcode;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/example/androidthings/robocar/shared/model/DiscovererInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc.
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 | package com.example.androidthings.robocar.shared.model;
17 |
18 | import android.text.TextUtils;
19 |
20 | import java.util.Random;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | public class DiscovererInfo {
25 |
26 | private static final String ROBOCAR_COMPANION = "RobocarCompanion";
27 | private static final String SEPARATOR_COLON = ":";
28 | private static final String HEX_CHARS = "0123456789abcdef";
29 | private static final int ID_LENGTH = 12;
30 |
31 | // RobocarCompanion:([0123456789abcdef]{12})(:(\S{5}))?
32 | private static final Pattern COMPANION_PATTERN = Pattern.compile(ROBOCAR_COMPANION
33 | + SEPARATOR_COLON + "([" + HEX_CHARS + "]{" + ID_LENGTH + "})("
34 | + SEPARATOR_COLON + "(\\S{5}))?"
35 | );
36 |
37 | private static String generateId() {
38 | char[] c = new char[ID_LENGTH];
39 | Random r = new Random();
40 | for (int i = 0; i < c.length; i++) {
41 | c[i] = HEX_CHARS.charAt(r.nextInt(HEX_CHARS.length()));
42 | }
43 | return new String(c);
44 | }
45 |
46 | public static DiscovererInfo generateDiscoveryInfo() {
47 | return new DiscovererInfo(generateId(), null);
48 | }
49 |
50 | public static DiscovererInfo parse(String name) {
51 | Matcher matcher = COMPANION_PATTERN.matcher(name);
52 | if (!matcher.matches()) {
53 | return null;
54 | }
55 | String id = matcher.group(1);
56 | String pairToken = matcher.group(3);
57 | return new DiscovererInfo(id, pairToken);
58 | }
59 |
60 | public final String mCompanionId;
61 | public final String mPairToken;
62 | public final boolean mIsPaired;
63 |
64 | private int mHashcode; // cache hashcode
65 |
66 | public DiscovererInfo(String companionId, String pairToken) {
67 | if (companionId == null) {
68 | throw new IllegalArgumentException("Companion ID cannot be null");
69 | }
70 | mCompanionId = companionId;
71 | mPairToken = pairToken;
72 | mIsPaired = !TextUtils.isEmpty(mPairToken);
73 | }
74 |
75 | public String getAdvertisingName() {
76 | StringBuilder builder = new StringBuilder(ROBOCAR_COMPANION)
77 | .append(SEPARATOR_COLON)
78 | .append(mCompanionId);
79 | if (mIsPaired) {
80 | builder.append(SEPARATOR_COLON)
81 | .append(mPairToken);
82 | }
83 | return builder.toString();
84 | }
85 |
86 | @Override
87 | public boolean equals(Object obj) {
88 | if (this == obj) {
89 | return true;
90 | }
91 | if (obj == null || !obj.getClass().equals(DiscovererInfo.class)) {
92 | return false;
93 | }
94 | DiscovererInfo other = (DiscovererInfo) obj;
95 | return mCompanionId.equals(other.mCompanionId)
96 | && TextUtils.equals(mPairToken, other.mPairToken);
97 | }
98 |
99 | @Override
100 | public int hashCode() {
101 | int h = mHashcode;
102 | if (h == 0) {
103 | h = mCompanionId.hashCode();
104 | h *= 1000003;
105 | h ^= mPairToken == null ? 0 : mPairToken.hashCode();
106 | mHashcode = h;
107 | }
108 | return mHashcode;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Shared
19 |
20 |
--------------------------------------------------------------------------------