1 /*
2  * Copyright (C) 2018 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 
17 package com.android.networkstack.tethering;
18 
19 import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
20 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
21 import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
22 import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE;
23 import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION;
24 import static android.net.TetheringConstants.EXTRA_TETHER_SUBID;
25 import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME;
26 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
27 import static android.net.TetheringManager.TETHERING_ETHERNET;
28 import static android.net.TetheringManager.TETHERING_INVALID;
29 import static android.net.TetheringManager.TETHERING_USB;
30 import static android.net.TetheringManager.TETHERING_WIFI;
31 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
32 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
33 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
34 
35 import android.app.AlarmManager;
36 import android.app.PendingIntent;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.net.util.SharedLog;
43 import android.os.Bundle;
44 import android.os.ConditionVariable;
45 import android.os.Handler;
46 import android.os.Parcel;
47 import android.os.PersistableBundle;
48 import android.os.ResultReceiver;
49 import android.os.SystemClock;
50 import android.os.SystemProperties;
51 import android.provider.Settings;
52 import android.telephony.CarrierConfigManager;
53 import android.util.SparseIntArray;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 
57 import java.io.PrintWriter;
58 import java.util.BitSet;
59 
60 /**
61  * Re-check tethering provisioning for enabled downstream tether types.
62  * Reference TetheringManager.TETHERING_{@code *} for each tether type.
63  *
64  * All methods of this class must be accessed from the thread of tethering
65  * state machine.
66  * @hide
67  */
68 public class EntitlementManager {
69     private static final String TAG = EntitlementManager.class.getSimpleName();
70     private static final boolean DBG = false;
71 
72     @VisibleForTesting
73     protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning";
74     private static final String ACTION_PROVISIONING_ALARM =
75             "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM";
76 
77     private final ComponentName mSilentProvisioningService;
78     private static final int MS_PER_HOUR = 60 * 60 * 1000;
79     private static final int DUMP_TIMEOUT = 10_000;
80 
81     // The BitSet is the bit map of each enabled downstream types, ex:
82     // {@link TetheringManager.TETHERING_WIFI}
83     // {@link TetheringManager.TETHERING_USB}
84     // {@link TetheringManager.TETHERING_BLUETOOTH}
85     private final BitSet mCurrentDownstreams;
86     private final BitSet mExemptedDownstreams;
87     private final Context mContext;
88     private final SharedLog mLog;
89     private final SparseIntArray mEntitlementCacheValue;
90     private final Handler mHandler;
91     // Key: TetheringManager.TETHERING_*(downstream).
92     // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
93     private final SparseIntArray mCurrentEntitlementResults;
94     private final Runnable mPermissionChangeCallback;
95     private PendingIntent mProvisioningRecheckAlarm;
96     private boolean mLastCellularUpstreamPermitted = true;
97     private boolean mUsingCellularAsUpstream = false;
98     private boolean mNeedReRunProvisioningUi = false;
99     private OnUiEntitlementFailedListener mListener;
100     private TetheringConfigurationFetcher mFetcher;
101 
EntitlementManager(Context ctx, Handler h, SharedLog log, Runnable callback)102     public EntitlementManager(Context ctx, Handler h, SharedLog log,
103             Runnable callback) {
104         mContext = ctx;
105         mLog = log.forSubComponent(TAG);
106         mCurrentDownstreams = new BitSet();
107         mExemptedDownstreams = new BitSet();
108         mCurrentEntitlementResults = new SparseIntArray();
109         mEntitlementCacheValue = new SparseIntArray();
110         mPermissionChangeCallback = callback;
111         mHandler = h;
112         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
113                 null, mHandler);
114         mSilentProvisioningService = ComponentName.unflattenFromString(
115                 mContext.getResources().getString(R.string.config_wifi_tether_enable));
116     }
117 
setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener)118     public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
119         mListener = listener;
120     }
121 
122     /** Callback fired when UI entitlement failed. */
123     public interface OnUiEntitlementFailedListener {
124         /**
125          * Ui entitlement check fails in |downstream|.
126          *
127          * @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
128          */
onUiEntitlementFailed(int downstream)129         void onUiEntitlementFailed(int downstream);
130     }
131 
setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher)132     public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) {
133         mFetcher = fetcher;
134     }
135 
136     /** Interface to fetch TetheringConfiguration. */
137     public interface TetheringConfigurationFetcher {
138         /**
139          * Fetch current tethering configuration. This will be called to ensure whether entitlement
140          * check is needed.
141          * @return TetheringConfiguration instance.
142          */
fetchTetheringConfiguration()143         TetheringConfiguration fetchTetheringConfiguration();
144     }
145 
146     /**
147      * Check if cellular upstream is permitted.
148      */
isCellularUpstreamPermitted()149     public boolean isCellularUpstreamPermitted() {
150         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
151 
152         return isCellularUpstreamPermitted(config);
153     }
154 
isCellularUpstreamPermitted(final TetheringConfiguration config)155     private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) {
156         if (!isTetherProvisioningRequired(config)) return true;
157 
158         // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular
159         // upstream should not be enabled. Enable cellular upstream for exempted downstreams only
160         // when there is no non-exempted downstream.
161         if (mCurrentDownstreams.isEmpty()) return !mExemptedDownstreams.isEmpty();
162 
163         return mCurrentEntitlementResults.indexOfValue(TETHER_ERROR_NO_ERROR) > -1;
164     }
165 
166     /**
167      * Set exempted downstream type. If there is only exempted downstream type active,
168      * corresponding entitlement check will not be run and cellular upstream will be permitted
169      * by default. If a privileged app enables tethering without a provisioning check, and then
170      * another app enables tethering of the same type but does not disable the provisioning check,
171      * then the downstream immediately loses exempt status and a provisioning check is run.
172      * If any non-exempted downstream type is active, the cellular upstream will be gated by the
173      * result of entitlement check from non-exempted downstreams. If entitlement check is still
174      * in progress on non-exempt downstreams, ceullar upstream would default be disabled. When any
175      * non-exempted downstream gets positive entitlement result, ceullar upstream will be enabled.
176      */
setExemptedDownstreamType(final int type)177     public void setExemptedDownstreamType(final int type) {
178         mExemptedDownstreams.set(type, true);
179     }
180 
181     /**
182      * This is called when tethering starts.
183      * Launch provisioning app if upstream is cellular.
184      *
185      * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *}
186      * @param showProvisioningUi a boolean indicating whether to show the
187      *        provisioning app UI if there is one.
188      */
startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi)189     public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) {
190         if (!isValidDownstreamType(downstreamType)) return;
191 
192         mCurrentDownstreams.set(downstreamType, true);
193 
194         mExemptedDownstreams.set(downstreamType, false);
195 
196         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
197         if (!isTetherProvisioningRequired(config)) return;
198 
199         // If upstream is not cellular, provisioning app would not be launched
200         // till upstream change to cellular.
201         if (mUsingCellularAsUpstream) {
202             if (showProvisioningUi) {
203                 runUiTetherProvisioning(downstreamType, config);
204             } else {
205                 runSilentTetherProvisioning(downstreamType, config);
206             }
207             mNeedReRunProvisioningUi = false;
208         } else {
209             mNeedReRunProvisioningUi |= showProvisioningUi;
210         }
211     }
212 
213     /**
214      * Tell EntitlementManager that a given type of tethering has been disabled
215      *
216      * @param type tethering type from TetheringManager.TETHERING_{@code *}
217      */
stopProvisioningIfNeeded(int downstreamType)218     public void stopProvisioningIfNeeded(int downstreamType) {
219         if (!isValidDownstreamType(downstreamType)) return;
220 
221         mCurrentDownstreams.set(downstreamType, false);
222         // There are lurking bugs where the notion of "provisioning required" or
223         // "tethering supported" may change without without tethering being notified properly.
224         // Remove the mapping all the time no matter provisioning is required or not.
225         removeDownstreamMapping(downstreamType);
226         mExemptedDownstreams.set(downstreamType, false);
227     }
228 
229     /**
230      * Notify EntitlementManager if upstream is cellular or not.
231      *
232      * @param isCellular whether tethering upstream is cellular.
233      */
notifyUpstream(boolean isCellular)234     public void notifyUpstream(boolean isCellular) {
235         if (DBG) {
236             mLog.i("notifyUpstream: " + isCellular
237                     + ", mLastCellularUpstreamPermitted: " + mLastCellularUpstreamPermitted
238                     + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi);
239         }
240         mUsingCellularAsUpstream = isCellular;
241 
242         if (mUsingCellularAsUpstream) {
243             final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
244             maybeRunProvisioning(config);
245         }
246     }
247 
248     /** Run provisioning if needed */
maybeRunProvisioning()249     public void maybeRunProvisioning() {
250         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
251         maybeRunProvisioning(config);
252     }
253 
maybeRunProvisioning(final TetheringConfiguration config)254     private void maybeRunProvisioning(final TetheringConfiguration config) {
255         if (mCurrentDownstreams.isEmpty() || !isTetherProvisioningRequired(config)) {
256             return;
257         }
258 
259         // Whenever any entitlement value changes, all downstreams will re-evaluate whether they
260         // are allowed. Therefore even if the silent check here ends in a failure and the UI later
261         // yields success, then the downstream that got a failure will re-evaluate as a result of
262         // the change and get the new correct value.
263         for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0;
264                 downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) {
265             if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) {
266                 if (mNeedReRunProvisioningUi) {
267                     mNeedReRunProvisioningUi = false;
268                     runUiTetherProvisioning(downstream, config);
269                 } else {
270                     runSilentTetherProvisioning(downstream, config);
271                 }
272             }
273         }
274     }
275 
276     /**
277      * Check if the device requires a provisioning check in order to enable tethering.
278      *
279      * @param config an object that encapsulates the various tethering configuration elements.
280      * @return a boolean - {@code true} indicating tether provisioning is required by the carrier.
281      */
282     @VisibleForTesting
isTetherProvisioningRequired(final TetheringConfiguration config)283     protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) {
284         if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
285                 || config.provisioningApp.length == 0) {
286             return false;
287         }
288         if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
289             return false;
290         }
291         return (config.provisioningApp.length == 2);
292     }
293 
294     /**
295      * Re-check tethering provisioning for all enabled tether types.
296      * Reference TetheringManager.TETHERING_{@code *} for each tether type.
297      *
298      * @param config an object that encapsulates the various tethering configuration elements.
299      * Note: this method is only called from @{link Tethering.TetherMainSM} on the handler thread.
300      * If there are new callers from different threads, the logic should move to
301      * @{link Tethering.TetherMainSM} handler to avoid race conditions.
302      */
reevaluateSimCardProvisioning(final TetheringConfiguration config)303     public void reevaluateSimCardProvisioning(final TetheringConfiguration config) {
304         if (DBG) mLog.i("reevaluateSimCardProvisioning");
305 
306         if (!mHandler.getLooper().isCurrentThread()) {
307             // Except for test, this log should not appear in normal flow.
308             mLog.log("reevaluateSimCardProvisioning() don't run in TetherMainSM thread");
309         }
310         mEntitlementCacheValue.clear();
311         mCurrentEntitlementResults.clear();
312 
313         // TODO: refine provisioning check to isTetherProvisioningRequired() ??
314         if (!config.hasMobileHotspotProvisionApp()
315                 || carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
316             evaluateCellularPermission(config);
317             return;
318         }
319 
320         if (mUsingCellularAsUpstream) {
321             maybeRunProvisioning(config);
322         }
323     }
324 
325     /**
326      * Get carrier configuration bundle.
327      * @param config an object that encapsulates the various tethering configuration elements.
328      * */
getCarrierConfig(final TetheringConfiguration config)329     public PersistableBundle getCarrierConfig(final TetheringConfiguration config) {
330         final CarrierConfigManager configManager = (CarrierConfigManager) mContext
331                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
332         if (configManager == null) return null;
333 
334         final PersistableBundle carrierConfig = configManager.getConfigForSubId(
335                 config.activeDataSubId);
336 
337         if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
338             return carrierConfig;
339         }
340 
341         return null;
342     }
343 
344     // The logic here is aimed solely at confirming that a CarrierConfig exists
345     // and affirms that entitlement checks are not required.
346     //
347     // TODO: find a better way to express this, or alter the checking process
348     // entirely so that this is more intuitive.
carrierConfigAffirmsEntitlementCheckNotRequired( final TetheringConfiguration config)349     private boolean carrierConfigAffirmsEntitlementCheckNotRequired(
350             final TetheringConfiguration config) {
351         // Check carrier config for entitlement checks
352         final PersistableBundle carrierConfig = getCarrierConfig(config);
353         if (carrierConfig == null) return false;
354 
355         // A CarrierConfigManager was found and it has a config.
356         final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
357                 CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
358         return !isEntitlementCheckRequired;
359     }
360 
361     /**
362      * Run no UI tethering provisioning check.
363      * @param type tethering type from TetheringManager.TETHERING_{@code *}
364      * @param subId default data subscription ID.
365      */
366     @VisibleForTesting
runSilentTetherProvisioning(int type, final TetheringConfiguration config)367     protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) {
368         if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
369         // For silent provisioning, settings would stop tethering when entitlement fail.
370         ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null);
371 
372         Intent intent = new Intent();
373         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
374         intent.putExtra(EXTRA_RUN_PROVISION, true);
375         intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi);
376         intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse);
377         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
378         intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
379         intent.setComponent(mSilentProvisioningService);
380         // Only admin user can change tethering and SilentTetherProvisioning don't need to
381         // show UI, it is fine to always start setting's background service as system user.
382         mContext.startService(intent);
383         return intent;
384     }
385 
runUiTetherProvisioning(int type, final TetheringConfiguration config)386     private void runUiTetherProvisioning(int type, final TetheringConfiguration config) {
387         ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null);
388         runUiTetherProvisioning(type, config, receiver);
389     }
390 
391     /**
392      * Run the UI-enabled tethering provisioning check.
393      * @param type tethering type from TetheringManager.TETHERING_{@code *}
394      * @param subId default data subscription ID.
395      * @param receiver to receive entitlement check result.
396      */
397     @VisibleForTesting
runUiTetherProvisioning(int type, final TetheringConfiguration config, ResultReceiver receiver)398     protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config,
399             ResultReceiver receiver) {
400         if (DBG) mLog.i("runUiTetherProvisioning: " + type);
401 
402         Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
403         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
404         intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp);
405         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
406         intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
407         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
408         // Only launch entitlement UI for system user. Entitlement UI should not appear for other
409         // user because only admin user is allowed to change tethering.
410         mContext.startActivity(intent);
411         return intent;
412     }
413 
414     // Not needed to check if this don't run on the handler thread because it's private.
scheduleProvisioningRechecks(final TetheringConfiguration config)415     private void scheduleProvisioningRechecks(final TetheringConfiguration config) {
416         if (mProvisioningRecheckAlarm == null) {
417             final int period = config.provisioningCheckPeriod;
418             if (period <= 0) return;
419 
420             Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
421             mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0);
422             AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
423                     Context.ALARM_SERVICE);
424             long periodMs = period * MS_PER_HOUR;
425             long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs;
426             alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs,
427                     mProvisioningRecheckAlarm);
428         }
429     }
430 
cancelTetherProvisioningRechecks()431     private void cancelTetherProvisioningRechecks() {
432         if (mProvisioningRecheckAlarm != null) {
433             AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
434                     Context.ALARM_SERVICE);
435             alarmManager.cancel(mProvisioningRecheckAlarm);
436             mProvisioningRecheckAlarm = null;
437         }
438     }
439 
evaluateCellularPermission(final TetheringConfiguration config)440     private void evaluateCellularPermission(final TetheringConfiguration config) {
441         final boolean permitted = isCellularUpstreamPermitted(config);
442 
443         if (DBG) {
444             mLog.i("Cellular permission change from " + mLastCellularUpstreamPermitted
445                     + " to " + permitted);
446         }
447 
448         if (mLastCellularUpstreamPermitted != permitted) {
449             mLog.log("Cellular permission change: " + permitted);
450             mPermissionChangeCallback.run();
451         }
452         // Only schedule periodic re-check when tether is provisioned
453         // and the result is ok.
454         if (permitted && mCurrentEntitlementResults.size() > 0) {
455             scheduleProvisioningRechecks(config);
456         } else {
457             cancelTetherProvisioningRechecks();
458         }
459         mLastCellularUpstreamPermitted = permitted;
460     }
461 
462     /**
463      * Add the mapping between provisioning result and tethering type.
464      * Notify UpstreamNetworkMonitor if Cellular permission changes.
465      *
466      * @param type tethering type from TetheringManager.TETHERING_{@code *}
467      * @param resultCode Provisioning result
468      */
addDownstreamMapping(int type, int resultCode)469     protected void addDownstreamMapping(int type, int resultCode) {
470         mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode
471                 + " ,TetherTypeRequested: " + mCurrentDownstreams.get(type));
472         if (!mCurrentDownstreams.get(type)) return;
473 
474         mCurrentEntitlementResults.put(type, resultCode);
475         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
476         evaluateCellularPermission(config);
477     }
478 
479     /**
480      * Remove the mapping for input tethering type.
481      * @param type tethering type from TetheringManager.TETHERING_{@code *}
482      */
removeDownstreamMapping(int type)483     protected void removeDownstreamMapping(int type) {
484         mLog.i("removeDownstreamMapping: " + type);
485         mCurrentEntitlementResults.delete(type);
486         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
487         evaluateCellularPermission(config);
488     }
489 
490     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
491         @Override
492         public void onReceive(Context context, Intent intent) {
493             if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) {
494                 mLog.log("Received provisioning alarm");
495                 final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
496                 reevaluateSimCardProvisioning(config);
497             }
498         }
499     };
500 
isValidDownstreamType(int type)501     private static boolean isValidDownstreamType(int type) {
502         switch (type) {
503             case TETHERING_BLUETOOTH:
504             case TETHERING_ETHERNET:
505             case TETHERING_USB:
506             case TETHERING_WIFI:
507                 return true;
508             default:
509                 return false;
510         }
511     }
512 
513     /**
514      * Dump the infromation of EntitlementManager.
515      * @param pw {@link PrintWriter} is used to print formatted
516      */
dump(PrintWriter pw)517     public void dump(PrintWriter pw) {
518         final ConditionVariable mWaiting = new ConditionVariable();
519         mHandler.post(() -> {
520             pw.print("isCellularUpstreamPermitted: ");
521             pw.println(isCellularUpstreamPermitted());
522             for (int type = mCurrentDownstreams.nextSetBit(0); type >= 0;
523                     type = mCurrentDownstreams.nextSetBit(type + 1)) {
524                 pw.print("Type: ");
525                 pw.print(typeString(type));
526                 if (mCurrentEntitlementResults.indexOfKey(type) > -1) {
527                     pw.print(", Value: ");
528                     pw.println(errorString(mCurrentEntitlementResults.get(type)));
529                 } else {
530                     pw.println(", Value: empty");
531                 }
532             }
533             mWaiting.open();
534         });
535         if (!mWaiting.block(DUMP_TIMEOUT)) {
536             pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms");
537         }
538         pw.print("Exempted: [");
539         for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0;
540                 type = mExemptedDownstreams.nextSetBit(type + 1)) {
541             pw.print(typeString(type));
542             pw.print(", ");
543         }
544         pw.println("]");
545     }
546 
typeString(int type)547     private static String typeString(int type) {
548         switch (type) {
549             case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH";
550             case TETHERING_INVALID: return "TETHERING_INVALID";
551             case TETHERING_USB: return "TETHERING_USB";
552             case TETHERING_WIFI: return "TETHERING_WIFI";
553             default:
554                 return String.format("TETHERING UNKNOWN TYPE (%d)", type);
555         }
556     }
557 
errorString(int value)558     private static String errorString(int value) {
559         switch (value) {
560             case TETHER_ERROR_ENTITLEMENT_UNKNOWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN";
561             case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR";
562             case TETHER_ERROR_PROVISIONING_FAILED: return "TETHER_ERROR_PROVISIONING_FAILED";
563             default:
564                 return String.format("UNKNOWN ERROR (%d)", value);
565         }
566     }
567 
buildProxyReceiver(int type, boolean notifyFail, final ResultReceiver receiver)568     private ResultReceiver buildProxyReceiver(int type, boolean notifyFail,
569             final ResultReceiver receiver) {
570         ResultReceiver rr = new ResultReceiver(mHandler) {
571             @Override
572             protected void onReceiveResult(int resultCode, Bundle resultData) {
573                 int updatedCacheValue = updateEntitlementCacheValue(type, resultCode);
574                 addDownstreamMapping(type, updatedCacheValue);
575                 if (updatedCacheValue == TETHER_ERROR_PROVISIONING_FAILED && notifyFail) {
576                     mListener.onUiEntitlementFailed(type);
577                 }
578                 if (receiver != null) receiver.send(updatedCacheValue, null);
579             }
580         };
581 
582         return writeToParcel(rr);
583     }
584 
585     // Instances of ResultReceiver need to be public classes for remote processes to be able
586     // to load them (otherwise, ClassNotFoundException). For private classes, this method
587     // performs a trick : round-trip parceling any instance of ResultReceiver will return a
588     // vanilla instance of ResultReceiver sharing the binder token with the original receiver.
589     // The binder token has a reference to the original instance of the private class and will
590     // still call its methods, and can be sent over. However it cannot be used for anything
591     // else than sending over a Binder call.
592     // While round-trip parceling is not great, there is currently no other way of generating
593     // a vanilla instance of ResultReceiver because all its fields are private.
writeToParcel(final ResultReceiver receiver)594     private ResultReceiver writeToParcel(final ResultReceiver receiver) {
595         Parcel parcel = Parcel.obtain();
596         receiver.writeToParcel(parcel, 0);
597         parcel.setDataPosition(0);
598         ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel);
599         parcel.recycle();
600         return receiverForSending;
601     }
602 
603     /**
604      * Update the last entitlement value to internal cache
605      *
606      * @param type tethering type from TetheringManager.TETHERING_{@code *}
607      * @param resultCode last entitlement value
608      * @return the last updated entitlement value
609      */
updateEntitlementCacheValue(int type, int resultCode)610     private int updateEntitlementCacheValue(int type, int resultCode) {
611         if (DBG) {
612             mLog.i("updateEntitlementCacheValue: " + type + ", result: " + resultCode);
613         }
614         if (resultCode == TETHER_ERROR_NO_ERROR) {
615             mEntitlementCacheValue.put(type, resultCode);
616             return resultCode;
617         } else {
618             mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISIONING_FAILED);
619             return TETHER_ERROR_PROVISIONING_FAILED;
620         }
621     }
622 
623     /** Get the last value of the tethering entitlement check. */
requestLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, boolean showEntitlementUi)624     public void requestLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,
625             boolean showEntitlementUi) {
626         if (!isValidDownstreamType(downstream)) {
627             receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null);
628             return;
629         }
630 
631         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
632         if (!isTetherProvisioningRequired(config)) {
633             receiver.send(TETHER_ERROR_NO_ERROR, null);
634             return;
635         }
636 
637         final int cacheValue = mEntitlementCacheValue.get(
638                 downstream, TETHER_ERROR_ENTITLEMENT_UNKNOWN);
639         if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) {
640             receiver.send(cacheValue, null);
641         } else {
642             ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
643             runUiTetherProvisioning(downstream, config, proxy);
644         }
645     }
646 }
647