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