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.settingslib.qrcode.QrCamera; 52 import com.android.settingslib.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(com.android.settingslib.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 Context.RECEIVER_EXPORTED_UNAUDITED); 194 } 195 196 @Override onPause()197 public void onPause() { 198 if (mCamera != null) { 199 mCamera.stop(); 200 } 201 202 super.onPause(); 203 204 getActivity().unregisterReceiver(mReceiver); 205 try { 206 mAdbManager.disablePairing(); 207 } catch (RemoteException e) { 208 Log.e(TAG, "Unable to cancel pairing"); 209 } 210 } 211 212 @Override onSurfaceTextureUpdated(SurfaceTexture surface)213 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 214 // Do nothing 215 } 216 217 @Override onAttach(Context context)218 public void onAttach(Context context) { 219 super.onAttach(context); 220 } 221 222 @Override onActivityCreated(Bundle savedInstanceState)223 public void onActivityCreated(Bundle savedInstanceState) { 224 super.onActivityCreated(savedInstanceState); 225 226 // setTitle for TalkBack 227 getActivity().setTitle(R.string.wifi_dpp_scan_qr_code); 228 } 229 230 @Override getMetricsCategory()231 public int getMetricsCategory() { 232 return 0; 233 } 234 235 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)236 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 237 initCamera(surface); 238 } 239 240 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)241 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 242 // Do nothing 243 } 244 245 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)246 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 247 destroyCamera(); 248 return true; 249 } 250 251 @Override getViewSize()252 public Size getViewSize() { 253 return new Size(mTextureView.getWidth(), mTextureView.getHeight()); 254 } 255 256 @Override setTransform(Matrix transform)257 public void setTransform(Matrix transform) { 258 mTextureView.setTransform(transform); 259 } 260 261 @Override getFramePosition(Size previewSize, int cameraOrientation)262 public Rect getFramePosition(Size previewSize, int cameraOrientation) { 263 return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); 264 } 265 266 @Override isValid(String qrCode)267 public boolean isValid(String qrCode) { 268 try { 269 // WIFI:T:ADB;S:myname;P:mypass;; 270 mAdbQrCode = new AdbQrCode(qrCode); 271 } catch (IllegalArgumentException e) { 272 showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format); 273 return false; 274 } 275 276 mAdbConfig = mAdbQrCode.getAdbNetworkConfig(); 277 278 return true; 279 } 280 281 @Override handleSuccessfulResult(String qrCode)282 public void handleSuccessfulResult(String qrCode) { 283 destroyCamera(); 284 mDecorateView.setFocused(true); 285 mQrCameraView.setVisibility(View.GONE); 286 mVerifyingView.setVisibility(View.VISIBLE); 287 AdbQrCode.triggerVibrationForQrCodeRecognition(getContext()); 288 mVerifyingTextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); 289 try { 290 mAdbManager.enablePairingByQrCode(mAdbConfig.getSsid(), 291 mAdbConfig.getPreSharedKey()); 292 } catch (RemoteException e) { 293 Log.e(TAG, "Unable to enable QR code pairing"); 294 getActivity().setResult(Activity.RESULT_CANCELED); 295 getActivity().finish(); 296 } 297 } 298 299 @Override handleCameraFailure()300 public void handleCameraFailure() { 301 destroyCamera(); 302 } 303 initCamera(SurfaceTexture surface)304 private void initCamera(SurfaceTexture surface) { 305 // Check if the camera has alread been created. 306 if (mCamera == null) { 307 mCamera = new QrCamera(getContext(), this); 308 mCamera.start(surface); 309 } 310 } 311 312 /** 313 * To resume camera decoding task after handshake fail or Wi-Fi connection fail. 314 */ restartCamera()315 private void restartCamera() { 316 if (mCamera == null) { 317 Log.d(TAG, "mCamera is not available for restarting camera"); 318 return; 319 } 320 321 if (mCamera.isDecodeTaskAlive()) { 322 mCamera.stop(); 323 } 324 325 final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); 326 if (surfaceTexture == null) { 327 throw new IllegalStateException("SurfaceTexture is not ready for restarting camera"); 328 } 329 330 mCamera.start(surfaceTexture); 331 } 332 destroyCamera()333 private void destroyCamera() { 334 if (mCamera != null) { 335 mCamera.stop(); 336 mCamera = null; 337 } 338 } 339 showErrorMessage(@tringRes int messageResId)340 private void showErrorMessage(@StringRes int messageResId) { 341 final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, 342 getString(messageResId)); 343 message.sendToTarget(); 344 } 345 346 @Override isFooterAvailable()347 protected boolean isFooterAvailable() { 348 return false; 349 } 350 } 351