blob: 2a78f908649bdb93c04e022e5d4f512154808e8c [file] [log] [blame]
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001/*
2 * Copyright (C) 2014 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.server;
18
19import android.Manifest.permission;
Jeff Davidson7842f642014-11-23 13:48:12 -080020import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070021import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070022import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070023import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070024import android.content.Intent;
Jeff Davidson7842f642014-11-23 13:48:12 -080025import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070026import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.pm.PackageManager;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070028import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070029import android.net.INetworkScoreService;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070030import android.net.NetworkScoreManager;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070032import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070033import android.net.ScoredNetwork;
Jeff Davidson26fd1432014-07-29 09:39:52 -070034import android.os.Binder;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070035import android.os.IBinder;
Jeff Davidson7842f642014-11-23 13:48:12 -080036import android.os.PatternMatcher;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070037import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070038import android.os.UserHandle;
Jeff Davidson56f9f732014-08-14 16:47:23 -070039import android.provider.Settings;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070040import android.text.TextUtils;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070041import android.util.Log;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070042
43import com.android.internal.R;
Jeff Davidson7842f642014-11-23 13:48:12 -080044import com.android.internal.annotations.GuardedBy;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070045
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070048import java.util.ArrayList;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070049import java.util.HashMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070050import java.util.HashSet;
51import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070052import java.util.Map;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070053import java.util.Set;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070054
55/**
56 * Backing service for {@link android.net.NetworkScoreManager}.
57 * @hide
58 */
59public class NetworkScoreService extends INetworkScoreService.Stub {
60 private static final String TAG = "NetworkScoreService";
Jeremy Joslindd251ef2016-03-14 11:17:41 -070061 private static final boolean DBG = false;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070062
Jeff Davidson6a4b2202014-04-16 17:29:40 -070063 private final Context mContext;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064 private final Map<Integer, INetworkScoreCache> mScoreCaches;
Jeff Davidson7842f642014-11-23 13:48:12 -080065 /** Lock used to update mReceiver when scorer package changes occur. */
Jeremy Joslindd251ef2016-03-14 11:17:41 -070066 private final Object mReceiverLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080067
68 /** Clears scores when the active scorer package is no longer valid. */
69 @GuardedBy("mReceiverLock")
70 private ScorerChangedReceiver mReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070071 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080072
73 private class ScorerChangedReceiver extends BroadcastReceiver {
74 final String mRegisteredPackage;
75
76 ScorerChangedReceiver(String packageName) {
77 mRegisteredPackage = packageName;
78 }
79
80 @Override
81 public void onReceive(Context context, Intent intent) {
82 String action = intent.getAction();
Jeremy Joslindd251ef2016-03-14 11:17:41 -070083 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
84 || Intent.ACTION_PACKAGE_REPLACED.equals(action)
85 || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
86 NetworkScorerAppData activeScorer =
87 NetworkScorerAppManager.getActiveScorer(mContext);
88 if (activeScorer == null) {
89 // Package change has invalidated a scorer.
90 Log.i(TAG, "Package " + mRegisteredPackage +
91 " is no longer valid, disabling scoring.");
92 setScorerInternal(null);
93 } else if (activeScorer.mScoringServiceClassName == null) {
94 // The scoring service is not available, make sure it's unbound.
95 unbindFromScoringServiceIfNeeded();
96 } else {
97 // The scoring service may have changed or been added.
98 bindToScoringServiceIfNeeded(activeScorer);
99 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800100 }
101 }
102 }
103
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700104 public NetworkScoreService(Context context) {
105 mContext = context;
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700106 mScoreCaches = new HashMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700107 }
108
109 /** Called when the system is ready to run third-party code but before it actually does so. */
110 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700111 if (DBG) Log.d(TAG, "systemReady");
Jeff Davidson56f9f732014-08-14 16:47:23 -0700112 ContentResolver cr = mContext.getContentResolver();
113 if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700114 // On first run, we try to initialize the scorer to the one configured at build time.
115 // This will be a no-op if the scorer isn't actually valid.
116 String defaultPackage = mContext.getResources().getString(
117 R.string.config_defaultNetworkScorerPackageName);
118 if (!TextUtils.isEmpty(defaultPackage)) {
119 NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
120 }
Jeff Davidson56f9f732014-08-14 16:47:23 -0700121 Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700122 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800123
124 registerPackageReceiverIfNeeded();
125 }
126
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700127 /** Called when the system is ready for us to start third-party code. */
128 void systemRunning() {
129 if (DBG) Log.d(TAG, "systemRunning");
130 bindToScoringServiceIfNeeded();
131 }
132
Jeff Davidson7842f642014-11-23 13:48:12 -0800133 private void registerPackageReceiverIfNeeded() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700134 if (DBG) Log.d(TAG, "registerPackageReceiverIfNeeded");
Jeff Davidson7842f642014-11-23 13:48:12 -0800135 NetworkScorerAppData scorer = NetworkScorerAppManager.getActiveScorer(mContext);
136 synchronized (mReceiverLock) {
137 // Unregister the receiver if the current scorer has changed since last registration.
138 if (mReceiver != null) {
139 if (Log.isLoggable(TAG, Log.VERBOSE)) {
140 Log.v(TAG, "Unregistering receiver for " + mReceiver.mRegisteredPackage);
141 }
142 mContext.unregisterReceiver(mReceiver);
143 mReceiver = null;
144 }
145
146 // Register receiver if a scorer is active.
147 if (scorer != null) {
148 IntentFilter filter = new IntentFilter();
149 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
150 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
151 filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
152 filter.addDataScheme("package");
153 filter.addDataSchemeSpecificPart(scorer.mPackageName,
154 PatternMatcher.PATTERN_LITERAL);
155 mReceiver = new ScorerChangedReceiver(scorer.mPackageName);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700156 // TODO: Need to update when we support per-user scorers. http://b/23422763
157 mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, null, null);
Jeff Davidson7842f642014-11-23 13:48:12 -0800158 if (Log.isLoggable(TAG, Log.VERBOSE)) {
159 Log.v(TAG, "Registered receiver for " + scorer.mPackageName);
160 }
161 }
162 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700163 }
164
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700165 private void bindToScoringServiceIfNeeded() {
166 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
167 NetworkScorerAppData scorerData = NetworkScorerAppManager.getActiveScorer(mContext);
168 bindToScoringServiceIfNeeded(scorerData);
169 }
170
171 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
172 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
173 if (scorerData != null && scorerData.mScoringServiceClassName != null) {
174 ComponentName componentName =
175 new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
176 // If we're connected to a different component then drop it.
177 if (mServiceConnection != null
178 && !mServiceConnection.mComponentName.equals(componentName)) {
179 unbindFromScoringServiceIfNeeded();
180 }
181
182 // If we're not connected at all then create a new connection.
183 if (mServiceConnection == null) {
184 mServiceConnection = new ScoringServiceConnection(componentName);
185 }
186
187 // Make sure the connection is connected (idempotent)
188 mServiceConnection.connect(mContext);
189 }
190 }
191
192 private void unbindFromScoringServiceIfNeeded() {
193 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
194 if (mServiceConnection != null) {
195 mServiceConnection.disconnect(mContext);
196 }
197 mServiceConnection = null;
198 }
199
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700200 @Override
201 public boolean updateScores(ScoredNetwork[] networks) {
202 if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
203 throw new SecurityException("Caller with UID " + getCallingUid() +
204 " is not the active scorer.");
205 }
206
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700207 // Separate networks by type.
208 Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700209 for (ScoredNetwork network : networks) {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700210 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
211 if (networkList == null) {
212 networkList = new ArrayList<>();
213 networksByType.put(network.networkKey.type, networkList);
214 }
215 networkList.add(network);
216 }
217
218 // Pass the scores of each type down to the appropriate network scorer.
219 for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
220 INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
221 if (scoreCache != null) {
222 try {
223 scoreCache.updateScores(entry.getValue());
224 } catch (RemoteException e) {
225 if (Log.isLoggable(TAG, Log.VERBOSE)) {
226 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
227 }
228 }
229 } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
230 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
231 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700232 }
233
234 return true;
235 }
236
237 @Override
238 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800239 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
240 // should be allowed to flush all scores.
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700241 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800242 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700243 PackageManager.PERMISSION_GRANTED) {
244 clearInternal();
245 return true;
246 } else {
247 throw new SecurityException(
248 "Caller is neither the active scorer nor the scorer manager.");
249 }
250 }
251
252 @Override
253 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800254 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
255 // to directly set the scorer app rather than having to use the consent dialog. The
256 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
257 // do the right thing and not enable this feature without explaining it to the user.
258 // In the future, should this API be opened to 3p apps, we will need to lock this down and
259 // figure out another way to streamline the UX.
260
261 // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
262 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
263
Jeff Davidson26fd1432014-07-29 09:39:52 -0700264 return setScorerInternal(packageName);
265 }
266
267 @Override
268 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800269 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
270 // should be allowed to disable scoring.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700271 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800272 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700273 PackageManager.PERMISSION_GRANTED) {
274 // The return value is discarded here because at this point, the call should always
275 // succeed. The only reason for failure is if the new package is not a valid scorer, but
276 // we're disabling scoring altogether here.
277 setScorerInternal(null /* packageName */);
278 } else {
279 throw new SecurityException(
280 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700281 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700282 }
283
284 /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
285 private boolean setScorerInternal(String packageName) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700286 if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
Jeff Davidson26fd1432014-07-29 09:39:52 -0700287 long token = Binder.clearCallingIdentity();
288 try {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700289 unbindFromScoringServiceIfNeeded();
Jeff Davidson26fd1432014-07-29 09:39:52 -0700290 // Preemptively clear scores even though the set operation could fail. We do this for
291 // safety as scores should never be compared across apps; in practice, Settings should
292 // only be allowing valid apps to be set as scorers, so failure here should be rare.
293 clearInternal();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800294 // Get the scorer that is about to be replaced, if any, so we can notify it directly.
295 NetworkScorerAppData prevScorer = NetworkScorerAppManager.getActiveScorer(mContext);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700296 boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700297 // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
298 // then we'll attempt to restore the previous binding (if any), otherwise an attempt
299 // will be made to bind to the new scorer.
300 bindToScoringServiceIfNeeded();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800301 if (result) { // new scorer successfully set
Jeff Davidson7842f642014-11-23 13:48:12 -0800302 registerPackageReceiverIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700303
Jeff Davidson26fd1432014-07-29 09:39:52 -0700304 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800305 if (prevScorer != null) { // Directly notify the old scorer.
306 intent.setPackage(prevScorer.mPackageName);
307 // TODO: Need to update when we support per-user scorers. http://b/23422763
308 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
309 }
310
311 if (packageName != null) { // Then notify the new scorer
312 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
313 intent.setPackage(packageName);
314 // TODO: Need to update when we support per-user scorers. http://b/23422763
315 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
316 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700317 }
318 return result;
319 } finally {
320 Binder.restoreCallingIdentity(token);
321 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700322 }
323
324 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
325 private void clearInternal() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700326 Set<INetworkScoreCache> cachesToClear = getScoreCaches();
327
328 for (INetworkScoreCache scoreCache : cachesToClear) {
329 try {
330 scoreCache.clearScores();
331 } catch (RemoteException e) {
332 if (Log.isLoggable(TAG, Log.VERBOSE)) {
333 Log.v(TAG, "Unable to clear scores", e);
334 }
335 }
336 }
337 }
338
339 @Override
340 public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeff Davidson16197792014-11-03 17:39:54 -0800341 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700342 synchronized (mScoreCaches) {
343 if (mScoreCaches.containsKey(networkType)) {
344 throw new IllegalArgumentException(
345 "Score cache already registered for type " + networkType);
346 }
347 mScoreCaches.put(networkType, scoreCache);
348 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700349 }
350
351 @Override
352 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
353 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeff Davidsonc7415532014-06-23 18:15:34 -0700354 NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700355 if (currentScorer == null) {
356 writer.println("Scoring is disabled.");
357 return;
358 }
Jeff Davidsonc7415532014-06-23 18:15:34 -0700359 writer.println("Current scorer: " + currentScorer.mPackageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700360
361 for (INetworkScoreCache scoreCache : getScoreCaches()) {
362 try {
363 scoreCache.asBinder().dump(fd, args);
364 } catch (RemoteException e) {
365 writer.println("Unable to dump score cache");
366 if (Log.isLoggable(TAG, Log.VERBOSE)) {
367 Log.v(TAG, "Unable to dump score cache", e);
368 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700369 }
370 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700371 if (mServiceConnection != null) {
372 mServiceConnection.dump(fd, writer, args);
373 } else {
374 writer.println("ScoringServiceConnection: null");
375 }
376 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700377 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700378
379 /**
380 * Returns a set of all score caches that are currently active.
381 *
382 * <p>May be used to perform an action on all score caches without potentially strange behavior
383 * if a new scorer is registered during that action's execution.
384 */
385 private Set<INetworkScoreCache> getScoreCaches() {
386 synchronized (mScoreCaches) {
387 return new HashSet<>(mScoreCaches.values());
388 }
389 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700390
391 private static class ScoringServiceConnection implements ServiceConnection {
392 private final ComponentName mComponentName;
393 private boolean mBound = false;
394
395 ScoringServiceConnection(ComponentName componentName) {
396 mComponentName = componentName;
397 }
398
399 void connect(Context context) {
400 disconnect(context);
401 Intent service = new Intent();
402 service.setComponent(mComponentName);
403 mBound = context.bindServiceAsUser(service, this,
404 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
405 UserHandle.SYSTEM);
406 if (!mBound) {
407 Log.w(TAG, "Bind call failed for " + service);
408 }
409 }
410
411 void disconnect(Context context) {
412 try {
413 if (mBound) {
414 mBound = false;
415 context.unbindService(this);
416 }
417 } catch (RuntimeException e) {
418 Log.e(TAG, "Unbind failed.", e);
419 }
420 }
421
422 @Override
423 public void onServiceConnected(ComponentName name, IBinder service) {
424 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
425 }
426
427 @Override
428 public void onServiceDisconnected(ComponentName name) {
429 if (DBG) Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
430 }
431
432 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
433 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound);
434 }
435 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700436}