• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2022 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.bluetooth;
18 
19 import android.app.Activity;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Matrix;
24 import android.graphics.Outline;
25 import android.graphics.Rect;
26 import android.graphics.SurfaceTexture;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.os.VibrationEffect;
31 import android.os.Vibrator;
32 import android.util.Log;
33 import android.util.Size;
34 import android.view.LayoutInflater;
35 import android.view.TextureView;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewOutlineProvider;
39 import android.view.accessibility.AccessibilityEvent;
40 import android.widget.TextView;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.StringRes;
44 
45 import com.android.settings.R;
46 import com.android.settings.core.InstrumentedFragment;
47 import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
48 import com.android.settingslib.bluetooth.BluetoothUtils;
49 import com.android.settingslib.qrcode.QrCamera;
50 
51 import java.time.Duration;
52 
53 public class QrCodeScanModeFragment extends InstrumentedFragment implements
54         TextureView.SurfaceTextureListener,
55         QrCamera.ScannerCallback {
56     private static final boolean DEBUG = BluetoothUtils.D;
57     private static final String TAG = "QrCodeScanModeFragment";
58 
59     /** Message sent to hide error message */
60     private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
61     /** Message sent to show error message */
62     private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
63     /** Message sent to broadcast QR code */
64     private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
65 
66     private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
67     private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
68 
69     private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
70 
71     public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata";
72 
73     private int mCornerRadius;
74     private String mBroadcastMetadata;
75     private Context mContext;
76     private QrCamera mCamera;
77     private TextureView mTextureView;
78     private TextView mSummary;
79     private TextView mErrorMessage;
80 
81     @Override
onCreate(Bundle savedInstanceState)82     public void onCreate(Bundle savedInstanceState) {
83         super.onCreate(savedInstanceState);
84         mContext = getContext();
85     }
86 
87     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)88     public final View onCreateView(LayoutInflater inflater, ViewGroup container,
89             Bundle savedInstanceState) {
90         return inflater.inflate(R.layout.qrcode_scanner_fragment, container,
91                 /* attachToRoot */ false);
92     }
93 
94     @Override
onViewCreated(View view, Bundle savedInstanceState)95     public void onViewCreated(View view, Bundle savedInstanceState) {
96         mTextureView = view.findViewById(R.id.preview_view);
97         mCornerRadius = mContext.getResources().getDimensionPixelSize(
98                 R.dimen.qrcode_preview_radius);
99         mTextureView.setSurfaceTextureListener(this);
100         mTextureView.setOutlineProvider(new ViewOutlineProvider() {
101             @Override
102             public void getOutline(View view, Outline outline) {
103                 outline.setRoundRect(0,0, view.getWidth(), view.getHeight(), mCornerRadius);
104             }
105         });
106         mTextureView.setClipToOutline(true);
107         mErrorMessage = view.findViewById(R.id.error_message);
108     }
109 
initCamera(SurfaceTexture surface)110     private void initCamera(SurfaceTexture surface) {
111         // Check if the camera has already created.
112         if (mCamera == null) {
113             mCamera = new QrCamera(mContext, this);
114             mCamera.start(surface);
115         }
116     }
117 
destroyCamera()118     private void destroyCamera() {
119         if (mCamera != null) {
120             mCamera.stop();
121             mCamera = null;
122         }
123     }
124 
125     @Override
onSurfaceTextureAvailable(@onNull SurfaceTexture surface, int width, int height)126     public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
127         initCamera(surface);
128     }
129 
130     @Override
onSurfaceTextureSizeChanged(@onNull SurfaceTexture surface, int width, int height)131     public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width,
132             int height) {
133     }
134 
135     @Override
onSurfaceTextureDestroyed(@onNull SurfaceTexture surface)136     public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
137         destroyCamera();
138         return true;
139     }
140 
141     @Override
onSurfaceTextureUpdated(@onNull SurfaceTexture surface)142     public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
143     }
144 
145     @Override
handleSuccessfulResult(String qrCode)146     public void handleSuccessfulResult(String qrCode) {
147         if (DEBUG) {
148             Log.d(TAG, "handleSuccessfulResult(), get the qr code string.");
149         }
150         mBroadcastMetadata = qrCode;
151         handleBtLeAudioScanner();
152     }
153 
154     @Override
handleCameraFailure()155     public void handleCameraFailure() {
156         destroyCamera();
157     }
158 
159     @Override
getViewSize()160     public Size getViewSize() {
161         return new Size(mTextureView.getWidth(), mTextureView.getHeight());
162     }
163 
164     @Override
getFramePosition(Size previewSize, int cameraOrientation)165     public Rect getFramePosition(Size previewSize, int cameraOrientation) {
166         return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
167     }
168 
169     @Override
setTransform(Matrix transform)170     public void setTransform(Matrix transform) {
171         mTextureView.setTransform(transform);
172     }
173 
174     @Override
isValid(String qrCode)175     public boolean isValid(String qrCode) {
176         if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) {
177             return true;
178         } else {
179             showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format);
180             return false;
181         }
182     }
183 
isDecodeTaskAlive()184     protected boolean isDecodeTaskAlive() {
185         return mCamera != null && mCamera.isDecodeTaskAlive();
186     }
187 
188     private final Handler mHandler = new Handler() {
189         @Override
190         public void handleMessage(Message msg) {
191             switch (msg.what) {
192                 case MESSAGE_HIDE_ERROR_MESSAGE:
193                     mErrorMessage.setVisibility(View.INVISIBLE);
194                     break;
195 
196                 case MESSAGE_SHOW_ERROR_MESSAGE:
197                     final String errorMessage = (String) msg.obj;
198 
199                     mErrorMessage.setVisibility(View.VISIBLE);
200                     mErrorMessage.setText(errorMessage);
201                     mErrorMessage.sendAccessibilityEvent(
202                             AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
203 
204                     // Cancel any pending messages to hide error view and requeue the message so
205                     // user has time to see error
206                     removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
207                     sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
208                             SHOW_ERROR_MESSAGE_INTERVAL);
209                     break;
210 
211                 case MESSAGE_SCAN_BROADCAST_SUCCESS:
212                     Log.d(TAG, "scan success");
213                     final Intent resultIntent = new Intent();
214                     resultIntent.putExtra(KEY_BROADCAST_METADATA, mBroadcastMetadata);
215                     getActivity().setResult(Activity.RESULT_OK, resultIntent);
216                     notifyUserForQrCodeRecognition();
217                     break;
218                 default:
219             }
220         }
221     };
222 
notifyUserForQrCodeRecognition()223     private void notifyUserForQrCodeRecognition() {
224         if (mCamera != null) {
225             mCamera.stop();
226         }
227 
228         mErrorMessage.setVisibility(View.INVISIBLE);
229 
230         triggerVibrationForQrCodeRecognition(getContext());
231 
232         getActivity().finish();
233     }
234 
triggerVibrationForQrCodeRecognition(Context context)235     private static void triggerVibrationForQrCodeRecognition(Context context) {
236         Vibrator vibrator = context.getSystemService(Vibrator.class);
237         if (vibrator == null) {
238             return;
239         }
240         vibrator.vibrate(VibrationEffect.createOneShot(
241                 VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(),
242                 VibrationEffect.DEFAULT_AMPLITUDE));
243     }
244 
showErrorMessage(@tringRes int messageResId)245     private void showErrorMessage(@StringRes int messageResId) {
246         final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
247                 getString(messageResId));
248         message.sendToTarget();
249     }
250 
handleBtLeAudioScanner()251     private void handleBtLeAudioScanner() {
252         Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
253         mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
254     }
255 
updateSummary()256     private void updateSummary() {
257         mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner));
258     }
259 
260     @Override
getMetricsCategory()261     public int getMetricsCategory() {
262         return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE;
263     }
264 }
265