Loading AndroidManifest.xml +10 −0 Original line number Diff line number Diff line Loading @@ -4942,6 +4942,16 @@ </intent-filter> </activity> <activity android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity" android:permission="android.permission.BLUETOOTH_CONNECT" android:exported="false"> <intent-filter> <action android:name="android.settings.BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".spa.SpaActivity" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK; import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Bundle; import android.util.Log; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; /** * Finding a broadcast through QR code. * * <p>To use intent action {@link * BluetoothBroadcastUtils#ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER}, specify the bluetooth device * sink of the broadcast to be provisioned in {@link * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_DEVICE_SINK} and check the operation for all coordinated * set members throughout one session or not by {@link * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_SINK_IS_GROUP}. */ public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity { private static final boolean DEBUG = BluetoothUtils.D; private static final String TAG = "QrCodeScanModeActivity"; private boolean mIsGroupOp; private BluetoothDevice mSink; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void handleIntent(Intent intent) { String action = intent != null ? intent.getAction() : null; if (DEBUG) { Log.d(TAG, "handleIntent(), action = " + action); } if (action == null) { finish(); return; } switch (action) { case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER: showQrCodeScannerFragment(intent); break; default: if (DEBUG) { Log.e(TAG, "Launch with an invalid action"); } finish(); } } protected void showQrCodeScannerFragment(Intent intent) { if (intent == null) { if (DEBUG) { Log.d(TAG, "intent is null, can not get bluetooth information from intent."); } return; } if (DEBUG) { Log.d(TAG, "showQrCodeScannerFragment"); } mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK); mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false); if (DEBUG) { Log.d(TAG, "get extra from intent"); } QrCodeScanModeFragment fragment = (QrCodeScanModeFragment) mFragmentManager.findFragmentByTag( BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); if (fragment == null) { fragment = new QrCodeScanModeFragment(); } else { if (fragment.isVisible()) { return; } // When the fragment in back stack but not on top of the stack, we can simply pop // stack because current fragment transactions are arranged in an order mFragmentManager.popBackStackImmediate(); return; } final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); fragmentTransaction.replace( R.id.fragment_container, fragment, BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); fragmentTransaction.commit(); } } src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import android.content.Intent; import android.os.Bundle; import android.os.SystemProperties; import androidx.fragment.app.FragmentManager; import com.android.settings.R; import com.android.settingslib.core.lifecycle.ObservableActivity; import com.google.android.setupdesign.util.ThemeHelper; import com.google.android.setupdesign.util.ThemeResolver; public abstract class QrCodeScanModeBaseActivity extends ObservableActivity { private static final String THEME_KEY = "setupwizard.theme"; private static final String THEME_DEFAULT_VALUE = "SudThemeGlifV3_DayNight"; protected FragmentManager mFragmentManager; protected abstract void handleIntent(Intent intent); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int defaultTheme = ThemeHelper.isSetupWizardDayNightEnabled(this) ? com.google.android.setupdesign.R.style.SudThemeGlifV3_DayNight : com.google.android.setupdesign.R.style.SudThemeGlifV3_Light; ThemeResolver themeResolver = new ThemeResolver.Builder(ThemeResolver.getDefault()) .setDefaultTheme(defaultTheme) .setUseDayNight(true) .build(); setTheme( themeResolver.resolve( SystemProperties.get(THEME_KEY, THEME_DEFAULT_VALUE), /* suppressDayNight= */ !ThemeHelper.isSetupWizardDayNightEnabled(this))); setContentView(R.layout.qrcode_scan_mode_activity); mFragmentManager = getSupportFragmentManager(); if (savedInstanceState == null) { handleIntent(getIntent()); } } } src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java 0 → 100644 +268 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import android.util.Size; import android.view.LayoutInflater; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.qrcode.QrCamera; import java.time.Duration; public class QrCodeScanModeFragment extends InstrumentedFragment implements TextureView.SurfaceTextureListener, QrCamera.ScannerCallback { private static final boolean DEBUG = BluetoothUtils.D; private static final String TAG = "QrCodeScanModeFragment"; /** Message sent to hide error message */ private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1; /** Message sent to show error message */ private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2; /** Message sent to broadcast QR code */ private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3; private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3); public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata"; private int mCornerRadius; private String mBroadcastMetadata; private Context mContext; private QrCamera mCamera; private TextureView mTextureView; private TextView mSummary; private TextView mErrorMessage; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getContext(); } @Override public final View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate( R.layout.qrcode_scanner_fragment, container, /* attachToRoot */ false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { mTextureView = view.findViewById(R.id.preview_view); mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.qrcode_preview_radius); mTextureView.setSurfaceTextureListener(this); mTextureView.setOutlineProvider( new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRoundRect( 0, 0, view.getWidth(), view.getHeight(), mCornerRadius); } }); mTextureView.setClipToOutline(true); mErrorMessage = view.findViewById(R.id.error_message); } private void initCamera(SurfaceTexture surface) { // Check if the camera has already created. if (mCamera == null) { mCamera = new QrCamera(mContext, this); mCamera.start(surface); } } private void destroyCamera() { if (mCamera != null) { mCamera.stop(); mCamera = null; } } @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { initCamera(surface); } @Override public void onSurfaceTextureSizeChanged( @NonNull SurfaceTexture surface, int width, int height) {} @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { destroyCamera(); return true; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} @Override public void handleSuccessfulResult(String qrCode) { if (DEBUG) { Log.d(TAG, "handleSuccessfulResult(), get the qr code string."); } mBroadcastMetadata = qrCode; handleBtLeAudioScanner(); } @Override public void handleCameraFailure() { destroyCamera(); } @Override public Size getViewSize() { return new Size(mTextureView.getWidth(), mTextureView.getHeight()); } @Override public Rect getFramePosition(Size previewSize, int cameraOrientation) { return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); } @Override public void setTransform(Matrix transform) { mTextureView.setTransform(transform); } @Override public boolean isValid(String qrCode) { if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) { return true; } else { showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format); return false; } } protected boolean isDecodeTaskAlive() { return mCamera != null && mCamera.isDecodeTaskAlive(); } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_HIDE_ERROR_MESSAGE: mErrorMessage.setVisibility(View.INVISIBLE); break; case MESSAGE_SHOW_ERROR_MESSAGE: final String errorMessage = (String) msg.obj; mErrorMessage.setVisibility(View.VISIBLE); mErrorMessage.setText(errorMessage); mErrorMessage.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); // Cancel any pending messages to hide error view and requeue the // message so // user has time to see error removeMessages(MESSAGE_HIDE_ERROR_MESSAGE); sendEmptyMessageDelayed( MESSAGE_HIDE_ERROR_MESSAGE, SHOW_ERROR_MESSAGE_INTERVAL); break; case MESSAGE_SCAN_BROADCAST_SUCCESS: Log.d(TAG, "scan success"); final Intent resultIntent = new Intent(); resultIntent.putExtra(KEY_BROADCAST_METADATA, mBroadcastMetadata); getActivity().setResult(Activity.RESULT_OK, resultIntent); notifyUserForQrCodeRecognition(); break; default: } } }; private void notifyUserForQrCodeRecognition() { if (mCamera != null) { mCamera.stop(); } mErrorMessage.setVisibility(View.INVISIBLE); triggerVibrationForQrCodeRecognition(getContext()); getActivity().finish(); } private static void triggerVibrationForQrCodeRecognition(Context context) { Vibrator vibrator = context.getSystemService(Vibrator.class); if (vibrator == null) { return; } vibrator.vibrate( VibrationEffect.createOneShot( VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(), VibrationEffect.DEFAULT_AMPLITUDE)); } private void showErrorMessage(@StringRes int messageResId) { final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, getString(messageResId)); message.sendToTarget(); } private void handleBtLeAudioScanner() { Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS); mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL); } private void updateSummary() { mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner)); } @Override public int getMetricsCategory() { return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE; } } Loading
AndroidManifest.xml +10 −0 Original line number Diff line number Diff line Loading @@ -4942,6 +4942,16 @@ </intent-filter> </activity> <activity android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity" android:permission="android.permission.BLUETOOTH_CONNECT" android:exported="false"> <intent-filter> <action android:name="android.settings.BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".spa.SpaActivity" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK; import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Bundle; import android.util.Log; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; /** * Finding a broadcast through QR code. * * <p>To use intent action {@link * BluetoothBroadcastUtils#ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER}, specify the bluetooth device * sink of the broadcast to be provisioned in {@link * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_DEVICE_SINK} and check the operation for all coordinated * set members throughout one session or not by {@link * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_SINK_IS_GROUP}. */ public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity { private static final boolean DEBUG = BluetoothUtils.D; private static final String TAG = "QrCodeScanModeActivity"; private boolean mIsGroupOp; private BluetoothDevice mSink; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void handleIntent(Intent intent) { String action = intent != null ? intent.getAction() : null; if (DEBUG) { Log.d(TAG, "handleIntent(), action = " + action); } if (action == null) { finish(); return; } switch (action) { case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER: showQrCodeScannerFragment(intent); break; default: if (DEBUG) { Log.e(TAG, "Launch with an invalid action"); } finish(); } } protected void showQrCodeScannerFragment(Intent intent) { if (intent == null) { if (DEBUG) { Log.d(TAG, "intent is null, can not get bluetooth information from intent."); } return; } if (DEBUG) { Log.d(TAG, "showQrCodeScannerFragment"); } mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK); mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false); if (DEBUG) { Log.d(TAG, "get extra from intent"); } QrCodeScanModeFragment fragment = (QrCodeScanModeFragment) mFragmentManager.findFragmentByTag( BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); if (fragment == null) { fragment = new QrCodeScanModeFragment(); } else { if (fragment.isVisible()) { return; } // When the fragment in back stack but not on top of the stack, we can simply pop // stack because current fragment transactions are arranged in an order mFragmentManager.popBackStackImmediate(); return; } final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); fragmentTransaction.replace( R.id.fragment_container, fragment, BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); fragmentTransaction.commit(); } }
src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import android.content.Intent; import android.os.Bundle; import android.os.SystemProperties; import androidx.fragment.app.FragmentManager; import com.android.settings.R; import com.android.settingslib.core.lifecycle.ObservableActivity; import com.google.android.setupdesign.util.ThemeHelper; import com.google.android.setupdesign.util.ThemeResolver; public abstract class QrCodeScanModeBaseActivity extends ObservableActivity { private static final String THEME_KEY = "setupwizard.theme"; private static final String THEME_DEFAULT_VALUE = "SudThemeGlifV3_DayNight"; protected FragmentManager mFragmentManager; protected abstract void handleIntent(Intent intent); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int defaultTheme = ThemeHelper.isSetupWizardDayNightEnabled(this) ? com.google.android.setupdesign.R.style.SudThemeGlifV3_DayNight : com.google.android.setupdesign.R.style.SudThemeGlifV3_Light; ThemeResolver themeResolver = new ThemeResolver.Builder(ThemeResolver.getDefault()) .setDefaultTheme(defaultTheme) .setUseDayNight(true) .build(); setTheme( themeResolver.resolve( SystemProperties.get(THEME_KEY, THEME_DEFAULT_VALUE), /* suppressDayNight= */ !ThemeHelper.isSetupWizardDayNightEnabled(this))); setContentView(R.layout.qrcode_scan_mode_activity); mFragmentManager = getSupportFragmentManager(); if (savedInstanceState == null) { handleIntent(getIntent()); } } }
src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java 0 → 100644 +268 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import android.util.Size; import android.view.LayoutInflater; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.qrcode.QrCamera; import java.time.Duration; public class QrCodeScanModeFragment extends InstrumentedFragment implements TextureView.SurfaceTextureListener, QrCamera.ScannerCallback { private static final boolean DEBUG = BluetoothUtils.D; private static final String TAG = "QrCodeScanModeFragment"; /** Message sent to hide error message */ private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1; /** Message sent to show error message */ private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2; /** Message sent to broadcast QR code */ private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3; private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3); public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata"; private int mCornerRadius; private String mBroadcastMetadata; private Context mContext; private QrCamera mCamera; private TextureView mTextureView; private TextView mSummary; private TextView mErrorMessage; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getContext(); } @Override public final View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate( R.layout.qrcode_scanner_fragment, container, /* attachToRoot */ false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { mTextureView = view.findViewById(R.id.preview_view); mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.qrcode_preview_radius); mTextureView.setSurfaceTextureListener(this); mTextureView.setOutlineProvider( new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRoundRect( 0, 0, view.getWidth(), view.getHeight(), mCornerRadius); } }); mTextureView.setClipToOutline(true); mErrorMessage = view.findViewById(R.id.error_message); } private void initCamera(SurfaceTexture surface) { // Check if the camera has already created. if (mCamera == null) { mCamera = new QrCamera(mContext, this); mCamera.start(surface); } } private void destroyCamera() { if (mCamera != null) { mCamera.stop(); mCamera = null; } } @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { initCamera(surface); } @Override public void onSurfaceTextureSizeChanged( @NonNull SurfaceTexture surface, int width, int height) {} @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { destroyCamera(); return true; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} @Override public void handleSuccessfulResult(String qrCode) { if (DEBUG) { Log.d(TAG, "handleSuccessfulResult(), get the qr code string."); } mBroadcastMetadata = qrCode; handleBtLeAudioScanner(); } @Override public void handleCameraFailure() { destroyCamera(); } @Override public Size getViewSize() { return new Size(mTextureView.getWidth(), mTextureView.getHeight()); } @Override public Rect getFramePosition(Size previewSize, int cameraOrientation) { return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); } @Override public void setTransform(Matrix transform) { mTextureView.setTransform(transform); } @Override public boolean isValid(String qrCode) { if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) { return true; } else { showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format); return false; } } protected boolean isDecodeTaskAlive() { return mCamera != null && mCamera.isDecodeTaskAlive(); } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_HIDE_ERROR_MESSAGE: mErrorMessage.setVisibility(View.INVISIBLE); break; case MESSAGE_SHOW_ERROR_MESSAGE: final String errorMessage = (String) msg.obj; mErrorMessage.setVisibility(View.VISIBLE); mErrorMessage.setText(errorMessage); mErrorMessage.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); // Cancel any pending messages to hide error view and requeue the // message so // user has time to see error removeMessages(MESSAGE_HIDE_ERROR_MESSAGE); sendEmptyMessageDelayed( MESSAGE_HIDE_ERROR_MESSAGE, SHOW_ERROR_MESSAGE_INTERVAL); break; case MESSAGE_SCAN_BROADCAST_SUCCESS: Log.d(TAG, "scan success"); final Intent resultIntent = new Intent(); resultIntent.putExtra(KEY_BROADCAST_METADATA, mBroadcastMetadata); getActivity().setResult(Activity.RESULT_OK, resultIntent); notifyUserForQrCodeRecognition(); break; default: } } }; private void notifyUserForQrCodeRecognition() { if (mCamera != null) { mCamera.stop(); } mErrorMessage.setVisibility(View.INVISIBLE); triggerVibrationForQrCodeRecognition(getContext()); getActivity().finish(); } private static void triggerVibrationForQrCodeRecognition(Context context) { Vibrator vibrator = context.getSystemService(Vibrator.class); if (vibrator == null) { return; } vibrator.vibrate( VibrationEffect.createOneShot( VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(), VibrationEffect.DEFAULT_AMPLITUDE)); } private void showErrorMessage(@StringRes int messageResId) { final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, getString(messageResId)); message.sendToTarget(); } private void handleBtLeAudioScanner() { Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS); mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL); } private void updateSummary() { mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner)); } @Override public int getMetricsCategory() { return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE; } }