1 /* 2 * Copyright (C) 2020 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.managedprovisioning.common; 18 19 import static android.content.Context.BIND_AUTO_CREATE; 20 21 import android.annotation.IntDef; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.os.Bundle; 27 import android.os.IBinder; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import com.google.android.setupwizard.util.INetworkInterceptService; 32 33 /** 34 * This wrapper performs system configuration that is necessary for some DPCs to run properly, and 35 * reverts the system to its previous state after the DPC has finished. One call should be made to 36 * {@link #triggerDpcStart(Context, Runnable)} to start the DPC, and one call should be made to 37 * {@link #dpcFinished()} after the DPC activity returns a result. Whenever the activity that 38 * hosts this connection is destroyed, {@link #unbind(Context)} should also be called. 39 */ 40 public class StartDpcInsideSuwServiceConnection implements ServiceConnection { 41 @VisibleForTesting 42 static final String NETWORK_INTERCEPT_SERVICE_ACTION = 43 "com.google.android.setupwizard.NetworkInterceptService.BIND"; 44 45 @VisibleForTesting 46 static final String SETUP_WIZARD_PACKAGE_NAME = "com.google.android.setupwizard"; 47 48 private static final String DPC_STATE_KEY = "dpc_state"; 49 private static final String NETWORK_INTERCEPT_SERVICE_BINDING_INITIATED_KEY = 50 "network_intercept_service_binding_initiated"; 51 private static final String NETWORK_INTERCEPT_WAS_INITIALLY_ENABLED_KEY = 52 "network_intercept_was_initially_enabled"; 53 54 private static final int DPC_STATE_NOT_STARTED = 1; 55 private static final int DPC_STATE_STARTED = 2; 56 private static final int DPC_STATE_FINISHED = 3; 57 58 @IntDef({DPC_STATE_NOT_STARTED, 59 DPC_STATE_STARTED, 60 DPC_STATE_FINISHED}) 61 private @interface DpcState {} 62 63 private Runnable mDpcIntentSender; 64 private INetworkInterceptService mNetworkInterceptService; 65 private @DpcState int mDpcState; 66 private boolean mNetworkInterceptServiceBindingInitiated; 67 private boolean mNetworkInterceptWasInitiallyEnabled; 68 StartDpcInsideSuwServiceConnection()69 public StartDpcInsideSuwServiceConnection() { 70 mDpcState = DPC_STATE_NOT_STARTED; 71 mNetworkInterceptServiceBindingInitiated = false; 72 mNetworkInterceptWasInitiallyEnabled = true; 73 } 74 StartDpcInsideSuwServiceConnection(Context context, Bundle savedInstanceState, Runnable dpcIntentSender)75 public StartDpcInsideSuwServiceConnection(Context context, Bundle savedInstanceState, 76 Runnable dpcIntentSender) { 77 // Three statements are required to assign an int value from a bundle into an IntDef 78 final int savedDpcState = savedInstanceState.getInt(DPC_STATE_KEY, DPC_STATE_NOT_STARTED); 79 final @DpcState int dpcStateAsIntDef = savedDpcState; 80 mDpcState = dpcStateAsIntDef; 81 82 mNetworkInterceptServiceBindingInitiated = savedInstanceState.getBoolean( 83 NETWORK_INTERCEPT_SERVICE_BINDING_INITIATED_KEY, false); 84 mNetworkInterceptWasInitiallyEnabled = savedInstanceState.getBoolean( 85 NETWORK_INTERCEPT_WAS_INITIALLY_ENABLED_KEY, true); 86 87 if (mNetworkInterceptServiceBindingInitiated) { 88 // bindService() succeeded previously, which implies that triggerDpc() was previously 89 // called. Attempt to bind again, so that we have a service connection ready for when 90 // dpcFinished() is called. Additionally, if we never received the onServiceConnected() 91 // callback previously, it means that the DPC was never actually started, so the DPC 92 // will be started when this new service connection is established. 93 mNetworkInterceptServiceBindingInitiated = false; 94 if (mDpcState != DPC_STATE_FINISHED) { 95 if (mDpcState == DPC_STATE_NOT_STARTED) { 96 mDpcIntentSender = dpcIntentSender; 97 } 98 tryBindService(context); 99 } 100 } 101 } 102 103 /** 104 * This should be called when onSaveInstanceState() is invoked on the host activity (the one 105 * that holds this connection). 106 */ saveInstanceState(Bundle outState)107 public void saveInstanceState(Bundle outState) { 108 outState.putInt(DPC_STATE_KEY, mDpcState); 109 outState.putBoolean(NETWORK_INTERCEPT_SERVICE_BINDING_INITIATED_KEY, 110 mNetworkInterceptServiceBindingInitiated); 111 outState.putBoolean(NETWORK_INTERCEPT_WAS_INITIALLY_ENABLED_KEY, 112 mNetworkInterceptWasInitiallyEnabled); 113 } 114 115 /** 116 * Configure Setup Wizard to allow the DPC setup activity to send network intents, then start 117 * the DPC setup activity. This should only be called once - subsequent calls have no effect. 118 */ triggerDpcStart(Context context, Runnable dpcIntentSender)119 public void triggerDpcStart(Context context, Runnable dpcIntentSender) { 120 if (mNetworkInterceptServiceBindingInitiated || mDpcState != DPC_STATE_NOT_STARTED) { 121 ProvisionLogger.loge("Duplicate calls to triggerDpcStart() - ignoring"); 122 return; 123 } 124 125 mDpcIntentSender = dpcIntentSender; 126 127 tryBindService(context); 128 129 // If binding to NetworkInterceptService succeeds, DPC will be started from one of the 130 // ServiceConnection's callbacks. If binding failed, we need to start the DPC ourselves 131 // here. Without the service binding, the DPC setup activity may fail, in which case it is 132 // the DPC's responsibility to report what happened and decide how to handle the failure. 133 if (!mNetworkInterceptServiceBindingInitiated) { 134 sendDpcIntentIfNotAlreadySent(); 135 } 136 } 137 tryBindService(Context context)138 private void tryBindService(Context context) { 139 final Intent networkInterceptServiceIntent = new Intent(NETWORK_INTERCEPT_SERVICE_ACTION); 140 networkInterceptServiceIntent.setPackage(SETUP_WIZARD_PACKAGE_NAME); 141 142 try { 143 if (context.bindService(networkInterceptServiceIntent, this, BIND_AUTO_CREATE)) { 144 mNetworkInterceptServiceBindingInitiated = true; 145 } else { 146 ProvisionLogger.loge("Failed to bind to SUW NetworkInterceptService"); 147 } 148 } catch(SecurityException e) { 149 ProvisionLogger.loge("Access denied to SUW NetworkInterceptService", e); 150 } 151 } 152 153 /** 154 * Reset network interception to its original state. 155 */ dpcFinished()156 public void dpcFinished() { 157 if (mNetworkInterceptServiceBindingInitiated && mNetworkInterceptWasInitiallyEnabled) { 158 enableNetworkIntentIntercept(); 159 } 160 mDpcState = DPC_STATE_FINISHED; 161 } 162 163 /** 164 * Unbind from the SUW NetworkInterceptService. This should be called whenever the activity 165 * that hosts this service connection is destroyed, in order to avoid leaking the service 166 * connection. 167 */ unbind(Context context)168 public void unbind(Context context) { 169 if (mNetworkInterceptServiceBindingInitiated) { 170 context.unbindService(this); 171 mNetworkInterceptServiceBindingInitiated = false; 172 } 173 174 mNetworkInterceptService = null; 175 } 176 177 @Override onServiceConnected(ComponentName className, IBinder service)178 public void onServiceConnected(ComponentName className, IBinder service) { 179 ProvisionLogger.logi("Connection to SUW NetworkInterceptService established"); 180 181 mNetworkInterceptService = INetworkInterceptService.Stub.asInterface(service); 182 183 // If the service disconnects and reconnects, don't cache the initial network intercept 184 // setting again, since we changed this when we made the first connection. 185 if (mDpcState != DPC_STATE_NOT_STARTED) { 186 return; 187 } 188 189 cacheInitialNetworkInterceptSetting(); 190 191 if (mNetworkInterceptWasInitiallyEnabled) { 192 disableNetworkIntentIntercept(); 193 } 194 195 sendDpcIntentIfNotAlreadySent(); 196 } 197 198 @Override onServiceDisconnected(ComponentName name)199 public void onServiceDisconnected(ComponentName name) { 200 ProvisionLogger.logw("Connection to SUW NetworkInterceptService lost"); 201 mNetworkInterceptService = null; 202 } 203 204 @Override onBindingDied(ComponentName name)205 public void onBindingDied(ComponentName name) { 206 ProvisionLogger.logw("Connection to SUW NetworkInterceptService died"); 207 mNetworkInterceptService = null; 208 } 209 210 @Override onNullBinding(ComponentName name)211 public void onNullBinding(ComponentName name) { 212 ProvisionLogger.loge("Binding to SUW NetworkInterceptService returned null"); 213 sendDpcIntentIfNotAlreadySent(); 214 } 215 sendDpcIntentIfNotAlreadySent()216 private void sendDpcIntentIfNotAlreadySent() { 217 if (mDpcState == DPC_STATE_NOT_STARTED && mDpcIntentSender != null) { 218 mDpcIntentSender.run(); 219 mDpcState = DPC_STATE_STARTED; 220 // Release any reference that mDpcIntentSender has to the host activity 221 mDpcIntentSender = null; 222 } 223 } 224 cacheInitialNetworkInterceptSetting()225 private void cacheInitialNetworkInterceptSetting() { 226 if (mNetworkInterceptService == null) { 227 ProvisionLogger.loge("Attempt to cache network interception when service is null"); 228 return; 229 } 230 try { 231 mNetworkInterceptWasInitiallyEnabled = 232 mNetworkInterceptService.isNetworkIntentIntercepted(); 233 } catch (Exception e) { 234 ProvisionLogger.loge("Exception from SUW NetworkInterceptService", e); 235 } 236 } 237 disableNetworkIntentIntercept()238 private void disableNetworkIntentIntercept() { 239 if (mNetworkInterceptService == null) { 240 ProvisionLogger.loge("Attempt to disable network interception when service is null"); 241 return; 242 } 243 try { 244 if (!mNetworkInterceptService.disableNetworkIntentIntercept()) { 245 ProvisionLogger.loge( 246 "Service call to disable SUW network intent interception failed"); 247 } 248 } catch (Exception e) { 249 ProvisionLogger.loge("Exception from SUW NetworkInterceptService", e); 250 } 251 } 252 enableNetworkIntentIntercept()253 private void enableNetworkIntentIntercept() { 254 if (mNetworkInterceptService == null) { 255 ProvisionLogger.logw( 256 "Attempt to re-enable SUW network intent interception when service is null"); 257 return; 258 } 259 260 try { 261 if (!mNetworkInterceptService.enableNetworkIntentIntercept()) { 262 ProvisionLogger.logw( 263 "Service call to re-enable SUW network intent interception failed"); 264 } 265 } catch (Exception e) { 266 ProvisionLogger.logw("Exception from SUW NetworkInterceptService", e); 267 } 268 } 269 } 270