blob: 53854c23baac1bc2b5647781cf446439ac9835ed [file] [log] [blame]
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -07001/*
2 * Copyright (C) 2011 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 android.net;
18
Jeff Sharkey4414cea2011-06-24 17:05:24 -070019import static android.content.pm.PackageManager.GET_SIGNATURES;
Jeff Sharkeycd2ca402011-06-10 15:14:07 -070020
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060021import android.annotation.SystemService;
Sudheer Shankae359c3d2017-02-22 18:41:29 -080022import android.app.ActivityManager;
Artur Satayev33f92172019-12-10 17:47:52 +000023import android.compat.annotation.UnsupportedAppUsage;
Jeff Sharkeyeedcb952011-05-17 14:55:15 -070024import android.content.Context;
Jeff Sharkey14711eb2011-06-15 10:29:17 -070025import android.content.Intent;
Jeff Sharkey4414cea2011-06-24 17:05:24 -070026import android.content.pm.PackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.Signature;
Jeff Sharkey43d2a172017-07-12 10:50:42 -060029import android.net.wifi.WifiConfiguration;
30import android.net.wifi.WifiInfo;
Mathew Inwood55418ea2018-12-20 15:30:45 +000031import android.os.Build;
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -070032import android.os.RemoteException;
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -070033import android.os.UserHandle;
Felipe Leme46c4fc32016-05-04 09:21:43 -070034import android.util.DebugUtils;
Jeff Sharkey53313d72017-07-13 16:47:32 -060035import android.util.Pair;
Jeff Sharkey0fc6d032018-03-30 16:25:11 -060036import android.util.Range;
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -070037
Jeff Sharkey4414cea2011-06-24 17:05:24 -070038import com.google.android.collect.Sets;
39
Jeff Sharkey53313d72017-07-13 16:47:32 -060040import java.time.ZonedDateTime;
Jeff Sharkey4414cea2011-06-24 17:05:24 -070041import java.util.HashSet;
Jeff Sharkey53313d72017-07-13 16:47:32 -060042import java.util.Iterator;
Jeff Sharkey1b861272011-05-22 00:34:52 -070043
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -070044/**
45 * Manager for creating and modifying network policy rules.
46 *
47 * {@hide}
48 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060049@SystemService(Context.NETWORK_POLICY_SERVICE)
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -070050public class NetworkPolicyManager {
51
Felipe Leme46b451f2016-08-19 08:46:17 -070052 /* POLICY_* are masks and can be ORed, although currently they are not.*/
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -070053 /** No specific network policy, use system default. */
54 public static final int POLICY_NONE = 0x0;
Jeff Sharkeyfdfef572011-06-16 15:07:48 -070055 /** Reject network usage on metered networks when application in background. */
56 public static final int POLICY_REJECT_METERED_BACKGROUND = 0x1;
Felipe Leme46b451f2016-08-19 08:46:17 -070057 /** Allow metered network use in the background even when in data usage save mode. */
58 public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4;
Jeff Sharkeyc006f1a2011-05-19 17:12:49 -070059
Felipe Leme46c4fc32016-05-04 09:21:43 -070060 /*
61 * Rules defining whether an uid has access to a network given its type (metered / non-metered).
62 *
63 * These rules are bits and can be used in bitmask operations; in particular:
64 * - rule & RULE_MASK_METERED: returns the metered-networks status.
65 * - rule & RULE_MASK_ALL: returns the all-networks status.
66 *
67 * The RULE_xxx_ALL rules applies to all networks (metered or non-metered), but on
68 * metered networks, the RULE_xxx_METERED rules should be checked first. For example,
69 * if the device is on Battery Saver Mode and Data Saver Mode simulatenously, and a uid
70 * is whitelisted for the former but not the latter, its status would be
71 * RULE_REJECT_METERED | RULE_ALLOW_ALL, meaning it could have access to non-metered
72 * networks but not to metered networks.
73 *
74 * See network-policy-restrictions.md for more info.
75 */
76 /** No specific rule was set */
77 public static final int RULE_NONE = 0;
Felipe Leme70c57c22016-03-29 10:45:13 -070078 /** Allow traffic on metered networks. */
Felipe Leme46c4fc32016-05-04 09:21:43 -070079 public static final int RULE_ALLOW_METERED = 1 << 0;
Felipe Leme70c57c22016-03-29 10:45:13 -070080 /** Temporarily allow traffic on metered networks because app is on foreground. */
Felipe Leme46c4fc32016-05-04 09:21:43 -070081 public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1;
82 /** Reject traffic on metered networks. */
83 public static final int RULE_REJECT_METERED = 1 << 2;
84 /** Network traffic should be allowed on all networks (metered or non-metered), although
85 * metered-network restrictions could still apply. */
86 public static final int RULE_ALLOW_ALL = 1 << 5;
87 /** Reject traffic on all networks. */
88 public static final int RULE_REJECT_ALL = 1 << 6;
89 /** Mask used to get the {@code RULE_xxx_METERED} rules */
90 public static final int MASK_METERED_NETWORKS = 0b00001111;
91 /** Mask used to get the {@code RULE_xxx_ALL} rules */
92 public static final int MASK_ALL_NETWORKS = 0b11110000;
Amith Yamasani15e472352015-04-24 19:06:07 -070093
94 public static final int FIREWALL_RULE_DEFAULT = 0;
Xiaohui Chenb41c9f72015-06-17 15:55:37 -070095
96 public static final String FIREWALL_CHAIN_NAME_NONE = "none";
97 public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable";
98 public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby";
Felipe Leme011b98f2016-02-10 17:28:31 -080099 public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
Xiaohui Chenb41c9f72015-06-17 15:55:37 -0700100
Jeff Sharkeyb3f19ca2011-06-29 23:54:13 -0700101 private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
102
Sudheer Shankad993dcf2018-02-11 12:22:16 -0800103 public static final int FOREGROUND_THRESHOLD_STATE =
104 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
105
Jeff Sharkey14711eb2011-06-15 10:29:17 -0700106 /**
Jeff Sharkey41ff7ec2011-07-25 15:21:22 -0700107 * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
108 * applies to.
Jeff Sharkey14711eb2011-06-15 10:29:17 -0700109 */
Jeff Sharkey41ff7ec2011-07-25 15:21:22 -0700110 public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE";
Jeff Sharkey14711eb2011-06-15 10:29:17 -0700111
Jeff Sharkey75d31892018-01-18 22:01:59 +0900112 public static final int OVERRIDE_UNMETERED = 1 << 0;
113 public static final int OVERRIDE_CONGESTED = 1 << 1;
114
Svet Ganov16a16892015-04-16 10:32:04 -0700115 private final Context mContext;
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100116 @UnsupportedAppUsage
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700117 private INetworkPolicyManager mService;
118
Svet Ganov16a16892015-04-16 10:32:04 -0700119 public NetworkPolicyManager(Context context, INetworkPolicyManager service) {
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700120 if (service == null) {
121 throw new IllegalArgumentException("missing INetworkPolicyManager");
122 }
Svet Ganov16a16892015-04-16 10:32:04 -0700123 mContext = context;
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700124 mService = service;
125 }
126
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100127 @UnsupportedAppUsage
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700128 public static NetworkPolicyManager from(Context context) {
Jeff Sharkeyeedcb952011-05-17 14:55:15 -0700129 return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE);
130 }
131
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700132 /**
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700133 * Set policy flags for specific UID.
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700134 *
Felipe Leme8546a442016-08-23 09:38:20 -0700135 * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
136 * although it is not validated.
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700137 */
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100138 @UnsupportedAppUsage
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700139 public void setUidPolicy(int uid, int policy) {
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700140 try {
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700141 mService.setUidPolicy(uid, policy);
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700142 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700143 throw e.rethrowFromSystemServer();
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700144 }
145 }
146
Dianne Hackbornbe7c50e2014-06-30 14:43:28 -0700147 /**
Felipe Leme8546a442016-08-23 09:38:20 -0700148 * Add policy flags for specific UID.
149 *
150 * <p>The given policy bits will be set for the uid.
151 *
152 * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
153 * although it is not validated.
Dianne Hackbornbe7c50e2014-06-30 14:43:28 -0700154 */
155 public void addUidPolicy(int uid, int policy) {
156 try {
157 mService.addUidPolicy(uid, policy);
158 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700159 throw e.rethrowFromSystemServer();
Dianne Hackbornbe7c50e2014-06-30 14:43:28 -0700160 }
161 }
162
163 /**
Felipe Leme8546a442016-08-23 09:38:20 -0700164 * Clear/remove policy flags for specific UID.
165 *
166 * <p>The given policy bits will be set for the uid.
167 *
168 * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
169 * although it is not validated.
Dianne Hackbornbe7c50e2014-06-30 14:43:28 -0700170 */
171 public void removeUidPolicy(int uid, int policy) {
172 try {
173 mService.removeUidPolicy(uid, policy);
174 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700175 throw e.rethrowFromSystemServer();
Dianne Hackbornbe7c50e2014-06-30 14:43:28 -0700176 }
177 }
178
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100179 @UnsupportedAppUsage
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700180 public int getUidPolicy(int uid) {
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700181 try {
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700182 return mService.getUidPolicy(uid);
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700183 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700184 throw e.rethrowFromSystemServer();
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700185 }
186 }
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700187
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100188 @UnsupportedAppUsage
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700189 public int[] getUidsWithPolicy(int policy) {
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700190 try {
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700191 return mService.getUidsWithPolicy(policy);
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700192 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700193 throw e.rethrowFromSystemServer();
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700194 }
195 }
196
Mathew Inwood55418ea2018-12-20 15:30:45 +0000197 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Jeff Sharkey1a303952011-06-16 13:04:20 -0700198 public void registerListener(INetworkPolicyListener listener) {
199 try {
200 mService.registerListener(listener);
201 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700202 throw e.rethrowFromSystemServer();
Jeff Sharkey1a303952011-06-16 13:04:20 -0700203 }
204 }
205
Mathew Inwood55418ea2018-12-20 15:30:45 +0000206 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Jeff Sharkey1a303952011-06-16 13:04:20 -0700207 public void unregisterListener(INetworkPolicyListener listener) {
208 try {
209 mService.unregisterListener(listener);
210 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700211 throw e.rethrowFromSystemServer();
Jeff Sharkey1a303952011-06-16 13:04:20 -0700212 }
213 }
214
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700215 public void setNetworkPolicies(NetworkPolicy[] policies) {
216 try {
217 mService.setNetworkPolicies(policies);
218 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700219 throw e.rethrowFromSystemServer();
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700220 }
221 }
222
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100223 @UnsupportedAppUsage
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700224 public NetworkPolicy[] getNetworkPolicies() {
225 try {
Svet Ganov16a16892015-04-16 10:32:04 -0700226 return mService.getNetworkPolicies(mContext.getOpPackageName());
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700227 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700228 throw e.rethrowFromSystemServer();
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700229 }
230 }
231
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100232 @UnsupportedAppUsage
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700233 public void setRestrictBackground(boolean restrictBackground) {
234 try {
235 mService.setRestrictBackground(restrictBackground);
236 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700237 throw e.rethrowFromSystemServer();
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700238 }
239 }
240
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100241 @UnsupportedAppUsage
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700242 public boolean getRestrictBackground() {
243 try {
244 return mService.getRestrictBackground();
245 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700246 throw e.rethrowFromSystemServer();
Jeff Sharkey8fc27e82012-04-04 20:40:58 -0700247 }
248 }
249
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700250 /**
Stuart Scott984dc852015-03-30 13:17:11 -0700251 * Resets network policy settings back to factory defaults.
252 *
253 * @hide
254 */
255 public void factoryReset(String subscriber) {
Stuart Scottf1fb3972015-04-02 18:00:02 -0700256 try {
257 mService.factoryReset(subscriber);
258 } catch (RemoteException e) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700259 throw e.rethrowFromSystemServer();
Stuart Scott984dc852015-03-30 13:17:11 -0700260 }
261 }
262
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700263 /** {@hide} */
Jeff Sharkey0fc6d032018-03-30 16:25:11 -0600264 @Deprecated
Jeff Sharkey53313d72017-07-13 16:47:32 -0600265 public static Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator(NetworkPolicy policy) {
Jeff Sharkey0fc6d032018-03-30 16:25:11 -0600266 final Iterator<Range<ZonedDateTime>> it = policy.cycleIterator();
267 return new Iterator<Pair<ZonedDateTime, ZonedDateTime>>() {
268 @Override
269 public boolean hasNext() {
270 return it.hasNext();
271 }
272
273 @Override
274 public Pair<ZonedDateTime, ZonedDateTime> next() {
Jeff Sharkeye67463d2018-04-13 14:38:01 -0600275 if (hasNext()) {
276 final Range<ZonedDateTime> r = it.next();
277 return Pair.create(r.getLower(), r.getUpper());
278 } else {
279 return Pair.create(null, null);
280 }
Jeff Sharkey0fc6d032018-03-30 16:25:11 -0600281 }
282 };
Jeff Sharkeycd2ca402011-06-10 15:14:07 -0700283 }
284
Jeff Sharkey497e4432011-06-14 17:27:29 -0700285 /**
286 * Check if given UID can have a {@link #setUidPolicy(int, int)} defined,
287 * usually to protect critical system services.
288 */
Jeff Sharkey8a8b5812012-03-21 18:13:36 -0700289 @Deprecated
Jeff Sharkey497e4432011-06-14 17:27:29 -0700290 public static boolean isUidValidForPolicy(Context context, int uid) {
Jeff Sharkey4414cea2011-06-24 17:05:24 -0700291 // first, quick-reject non-applications
Jeff Sharkeyd0c6ccb2012-09-14 16:26:37 -0700292 if (!UserHandle.isApp(uid)) {
Jeff Sharkey4414cea2011-06-24 17:05:24 -0700293 return false;
294 }
295
Jeff Sharkeyb3f19ca2011-06-29 23:54:13 -0700296 if (!ALLOW_PLATFORM_APP_POLICY) {
297 final PackageManager pm = context.getPackageManager();
298 final HashSet<Signature> systemSignature;
299 try {
300 systemSignature = Sets.newHashSet(
301 pm.getPackageInfo("android", GET_SIGNATURES).signatures);
302 } catch (NameNotFoundException e) {
303 throw new RuntimeException("problem finding system signature", e);
Jeff Sharkey4414cea2011-06-24 17:05:24 -0700304 }
Jeff Sharkeyb3f19ca2011-06-29 23:54:13 -0700305
306 try {
307 // reject apps signed with platform cert
308 for (String packageName : pm.getPackagesForUid(uid)) {
309 final HashSet<Signature> packageSignature = Sets.newHashSet(
310 pm.getPackageInfo(packageName, GET_SIGNATURES).signatures);
311 if (packageSignature.containsAll(systemSignature)) {
312 return false;
313 }
314 }
315 } catch (NameNotFoundException e) {
316 }
Jeff Sharkey4414cea2011-06-24 17:05:24 -0700317 }
318
319 // nothing found above; we can apply policy to UID
320 return true;
Jeff Sharkey497e4432011-06-14 17:27:29 -0700321 }
Felipe Leme46c4fc32016-05-04 09:21:43 -0700322
Felipe Lemeb146f762016-08-19 09:52:16 -0700323 /**
Felipe Leme46c4fc32016-05-04 09:21:43 -0700324 * @hide
325 */
326 public static String uidRulesToString(int uidRules) {
327 final StringBuilder string = new StringBuilder().append(uidRules).append(" (");
328 if (uidRules == RULE_NONE) {
329 string.append("NONE");
330 } else {
331 string.append(DebugUtils.flagsToString(NetworkPolicyManager.class, "RULE_", uidRules));
332 }
333 string.append(")");
334 return string.toString();
335 }
Felipe Lemeb146f762016-08-19 09:52:16 -0700336
337 /**
338 * @hide
339 */
340 public static String uidPoliciesToString(int uidPolicies) {
341 final StringBuilder string = new StringBuilder().append(uidPolicies).append(" (");
342 if (uidPolicies == POLICY_NONE) {
343 string.append("NONE");
344 } else {
345 string.append(DebugUtils.flagsToString(NetworkPolicyManager.class,
346 "POLICY_", uidPolicies));
347 }
348 string.append(")");
349 return string.toString();
350 }
Sudheer Shankae359c3d2017-02-22 18:41:29 -0800351
352 /**
353 * Returns true if {@param procState} is considered foreground and as such will be allowed
354 * to access network when the device is idle or in battery saver mode. Otherwise, false.
355 */
356 public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
Sudheer Shankad993dcf2018-02-11 12:22:16 -0800357 return procState <= FOREGROUND_THRESHOLD_STATE;
Sudheer Shankae359c3d2017-02-22 18:41:29 -0800358 }
359
360 /**
361 * Returns true if {@param procState} is considered foreground and as such will be allowed
362 * to access network when the device is in data saver mode. Otherwise, false.
363 */
364 public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
Sudheer Shankad993dcf2018-02-11 12:22:16 -0800365 return procState <= FOREGROUND_THRESHOLD_STATE;
Sudheer Shankae359c3d2017-02-22 18:41:29 -0800366 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600367
368 public static String resolveNetworkId(WifiConfiguration config) {
369 return WifiInfo.removeDoubleQuotes(config.isPasspoint()
370 ? config.providerFriendlyName : config.SSID);
371 }
372
373 public static String resolveNetworkId(String ssid) {
374 return WifiInfo.removeDoubleQuotes(ssid);
375 }
Jeff Sharkey75d31892018-01-18 22:01:59 +0900376
377 /** {@hide} */
378 public static class Listener extends INetworkPolicyListener.Stub {
379 @Override public void onUidRulesChanged(int uid, int uidRules) { }
380 @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
381 @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
382 @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
Sarah Chin1431ab22019-08-19 18:08:21 -0700383 @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
384 long networkTypeMask) { }
Jeff Sharkey75d31892018-01-18 22:01:59 +0900385 }
Jeff Sharkeyd5cdd592011-05-03 20:27:17 -0700386}