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.settings.development; 18 19 import android.app.Activity; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.debug.AdbManager; 25 import android.debug.IAdbManager; 26 import android.graphics.Matrix; 27 import android.graphics.Rect; 28 import android.graphics.SurfaceTexture; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.util.Size; 37 import android.view.LayoutInflater; 38 import android.view.TextureView; 39 import android.view.TextureView.SurfaceTextureListener; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.accessibility.AccessibilityEvent; 43 import android.widget.ProgressBar; 44 import android.widget.TextView; 45 46 import androidx.annotation.StringRes; 47 48 import com.android.settings.R; 49 import com.android.settings.wifi.dpp.WifiDppQrCodeBaseFragment; 50 import com.android.settings.wifi.dpp.WifiNetworkConfig; 51 import com.android.settings.wifi.dpp.WifiQrCode; 52 import com.android.settings.wifi.qrcode.QrCamera; 53 import com.android.settings.wifi.qrcode.QrDecorateView; 54 55 /** 56 * Fragment shown when clicking on the "Pair by QR code" preference in 57 * the Wireless Debugging fragment. 58 */ 59 public class AdbQrcodeScannerFragment extends WifiDppQrCodeBaseFragment implements 60 SurfaceTextureListener, 61 QrCamera.ScannerCallback { 62 private static final String TAG = "AdbQrcodeScannerFrag"; 63 64 /** Message sent to hide error message */ 65 private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1; 66 67 /** Message sent to show error message */ 68 private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2; 69 70 private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; 71 private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; 72 73 private ProgressBar mProgressBar; 74 private QrCamera mCamera; 75 private TextureView mTextureView; 76 private QrDecorateView mDecorateView; 77 private View mQrCameraView; 78 private View mVerifyingView; 79 private TextView mErrorMessage; 80 81 /** QR code data scanned by camera */ 82 private WifiQrCode mAdbQrCode; 83 private WifiNetworkConfig mAdbConfig; 84 85 private IAdbManager mAdbManager; 86 87 private IntentFilter mIntentFilter; 88 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 89 @Override 90 public void onReceive(Context context, Intent intent) { 91 String action = intent.getAction(); 92 if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) { 93 Integer res = intent.getIntExtra( 94 AdbManager.WIRELESS_STATUS_EXTRA, 95 AdbManager.WIRELESS_STATUS_FAIL); 96 if (res.equals(AdbManager.WIRELESS_STATUS_SUCCESS)) { 97 Intent i = new Intent(); 98 i.putExtra( 99 WirelessDebuggingFragment.PAIRING_DEVICE_REQUEST_TYPE, 100 WirelessDebuggingFragment.SUCCESS_ACTION); 101 getActivity().setResult(Activity.RESULT_OK, i); 102 getActivity().finish(); 103 } else if (res.equals(AdbManager.WIRELESS_STATUS_FAIL)) { 104 Intent i = new Intent(); 105 i.putExtra( 106 WirelessDebuggingFragment.PAIRING_DEVICE_REQUEST_TYPE, 107 WirelessDebuggingFragment.FAIL_ACTION); 108 getActivity().setResult(Activity.RESULT_OK, i); 109 getActivity().finish(); 110 } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) { 111 int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0); 112 Log.i(TAG, "Got Qr pairing code port=" + port); 113 } 114 } 115 } 116 }; 117 118 private final Handler mHandler = new Handler() { 119 @Override 120 public void handleMessage(Message msg) { 121 switch (msg.what) { 122 case MESSAGE_HIDE_ERROR_MESSAGE: 123 mErrorMessage.setVisibility(View.INVISIBLE); 124 break; 125 126 case MESSAGE_SHOW_ERROR_MESSAGE: 127 final String errorMessage = (String) msg.obj; 128 129 mErrorMessage.setVisibility(View.VISIBLE); 130 mErrorMessage.setText(errorMessage); 131 mErrorMessage.sendAccessibilityEvent( 132 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 133 134 // Cancel any pending messages to hide error view and requeue the message so 135 // user has time to see error 136 removeMessages(MESSAGE_HIDE_ERROR_MESSAGE); 137 sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE, 138 SHOW_ERROR_MESSAGE_INTERVAL); 139 break; 140 141 default: 142 return; 143 } 144 } 145 }; 146 147 @Override onCreate(Bundle savedInstanceState)148 public void onCreate(Bundle savedInstanceState) { 149 super.onCreate(savedInstanceState); 150 151 mIntentFilter = new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); 152 } 153 154 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)155 public final View onCreateView(LayoutInflater inflater, ViewGroup container, 156 Bundle savedInstanceState) { 157 return inflater.inflate(R.layout.adb_qrcode_scanner_fragment, container, false); 158 } 159 160 @Override onViewCreated(View view, Bundle savedInstanceState)161 public void onViewCreated(View view, Bundle savedInstanceState) { 162 super.onViewCreated(view, savedInstanceState); 163 164 mTextureView = (TextureView) view.findViewById(R.id.preview_view); 165 mTextureView.setSurfaceTextureListener(this); 166 167 mDecorateView = (QrDecorateView) view.findViewById(R.id.decorate_view); 168 169 setHeaderIconImageResource(R.drawable.ic_scan_24dp); 170 171 mProgressBar = view.findViewById(R.id.indeterminate_bar); 172 mProgressBar.setVisibility(View.INVISIBLE); 173 174 mQrCameraView = view.findViewById(R.id.camera_layout); 175 mVerifyingView = view.findViewById(R.id.verifying_layout); 176 177 mTitle.setText(R.string.wifi_dpp_scan_qr_code); 178 mSummary.setText(R.string.adb_wireless_qrcode_pairing_description); 179 180 mErrorMessage = view.findViewById(R.id.error_message); 181 } 182 183 @Override onResume()184 public void onResume() { 185 super.onResume(); 186 187 mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService(Context.ADB_SERVICE)); 188 getActivity().registerReceiver(mReceiver, mIntentFilter); 189 } 190 191 @Override onPause()192 public void onPause() { 193 super.onPause(); 194 195 getActivity().unregisterReceiver(mReceiver); 196 try { 197 mAdbManager.disablePairing(); 198 } catch (RemoteException e) { 199 Log.e(TAG, "Unable to cancel pairing"); 200 } 201 getActivity().finish(); 202 } 203 204 @Override onSurfaceTextureUpdated(SurfaceTexture surface)205 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 206 // Do nothing 207 } 208 209 @Override onAttach(Context context)210 public void onAttach(Context context) { 211 super.onAttach(context); 212 } 213 214 @Override getMetricsCategory()215 public int getMetricsCategory() { 216 return 0; 217 } 218 219 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)220 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 221 initCamera(surface); 222 } 223 224 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)225 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 226 // Do nothing 227 } 228 229 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)230 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 231 destroyCamera(); 232 return true; 233 } 234 235 @Override getViewSize()236 public Size getViewSize() { 237 return new Size(mTextureView.getWidth(), mTextureView.getHeight()); 238 } 239 240 @Override setTransform(Matrix transform)241 public void setTransform(Matrix transform) { 242 mTextureView.setTransform(transform); 243 } 244 245 @Override getFramePosition(Size previewSize, int cameraOrientation)246 public Rect getFramePosition(Size previewSize, int cameraOrientation) { 247 return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); 248 } 249 250 @Override isValid(String qrCode)251 public boolean isValid(String qrCode) { 252 try { 253 // WIFI:T:ADB;S:myname;P:mypass;; 254 mAdbQrCode = new WifiQrCode(qrCode); 255 } catch (IllegalArgumentException e) { 256 showErrorMessage(R.string.wifi_dpp_could_not_detect_valid_qr_code); 257 return false; 258 } 259 260 // Only accept the zxing format. 261 if (!WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG.equals(mAdbQrCode.getScheme())) { 262 showErrorMessage(R.string.wifi_dpp_could_not_detect_valid_qr_code); 263 Log.w(TAG, "DPP format not supported for ADB QR code"); 264 return false; 265 } 266 267 mAdbConfig = mAdbQrCode.getWifiNetworkConfig(); 268 if (!WifiQrCode.SECURITY_ADB.equals(mAdbConfig.getSecurity())) { 269 showErrorMessage(R.string.wifi_dpp_could_not_detect_valid_qr_code); 270 Log.w(TAG, "Invalid security type"); 271 return false; 272 } 273 274 if (TextUtils.isEmpty(mAdbConfig.getSsid())) { 275 showErrorMessage(R.string.wifi_dpp_could_not_detect_valid_qr_code); 276 Log.w(TAG, "Empty password"); 277 return false; 278 } 279 280 if (TextUtils.isEmpty(mAdbConfig.getPreSharedKey())) { 281 showErrorMessage(R.string.wifi_dpp_could_not_detect_valid_qr_code); 282 Log.w(TAG, "Empty password"); 283 return false; 284 } 285 286 return true; 287 } 288 289 @Override handleSuccessfulResult(String qrCode)290 public void handleSuccessfulResult(String qrCode) { 291 destroyCamera(); 292 mDecorateView.setFocused(true); 293 mQrCameraView.setVisibility(View.GONE); 294 mVerifyingView.setVisibility(View.VISIBLE); 295 try { 296 mAdbManager.enablePairingByQrCode(mAdbConfig.getSsid(), 297 mAdbConfig.getPreSharedKey()); 298 } catch (RemoteException e) { 299 Log.e(TAG, "Unable to enable QR code pairing"); 300 getActivity().finish(); 301 } 302 } 303 304 @Override handleCameraFailure()305 public void handleCameraFailure() { 306 destroyCamera(); 307 } 308 initCamera(SurfaceTexture surface)309 private void initCamera(SurfaceTexture surface) { 310 // Check if the camera has alread been created. 311 if (mCamera == null) { 312 mCamera = new QrCamera(getContext(), this); 313 mCamera.start(surface); 314 } 315 } 316 317 /** 318 * To resume camera decoding task after handshake fail or Wi-Fi connection fail. 319 */ restartCamera()320 private void restartCamera() { 321 if (mCamera == null) { 322 Log.d(TAG, "mCamera is not available for restarting camera"); 323 return; 324 } 325 326 if (mCamera.isDecodeTaskAlive()) { 327 mCamera.stop(); 328 } 329 330 final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); 331 if (surfaceTexture == null) { 332 throw new IllegalStateException("SurfaceTexture is not ready for restarting camera"); 333 } 334 335 mCamera.start(surfaceTexture); 336 } 337 destroyCamera()338 private void destroyCamera() { 339 if (mCamera != null) { 340 mCamera.stop(); 341 mCamera = null; 342 } 343 } 344 showErrorMessage(@tringRes int messageResId)345 private void showErrorMessage(@StringRes int messageResId) { 346 final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, 347 getString(messageResId)); 348 message.sendToTarget(); 349 } 350 351 } 352