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