• 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.settings.SettingsEnums;
20 import android.bluetooth.BluetoothDevice;
21 import android.content.Context;
22 import android.graphics.Matrix;
23 import android.graphics.Outline;
24 import android.graphics.Rect;
25 import android.graphics.SurfaceTexture;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.util.Log;
30 import android.util.Size;
31 import android.view.LayoutInflater;
32 import android.view.TextureView;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewOutlineProvider;
36 import android.view.accessibility.AccessibilityEvent;
37 import android.widget.TextView;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.StringRes;
41 
42 import com.android.settings.core.InstrumentedFragment;
43 import com.android.settingslib.R;
44 import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
45 import com.android.settingslib.bluetooth.BluetoothUtils;
46 import com.android.settingslib.qrcode.QrCamera;
47 
48 public class QrCodeScanModeFragment extends InstrumentedFragment implements
49         TextureView.SurfaceTextureListener,
50         QrCamera.ScannerCallback {
51     private static final boolean DEBUG = BluetoothUtils.D;
52     private static final String TAG = "QrCodeScanModeFragment";
53 
54     /** Message sent to hide error message */
55     private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
56     /** Message sent to show error message */
57     private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
58     /** Message sent to broadcast QR code */
59     private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
60 
61     private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
62     private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
63 
64     private boolean mIsGroupOp;
65     private int mCornerRadius;
66     private BluetoothDevice mSink;
67     private String mBroadcastMetadata;
68     private Context mContext;
69     private QrCamera mCamera;
70     private QrCodeScanModeController mController;
71     private TextureView mTextureView;
72     private TextView mSummary;
73     private TextView mErrorMessage;
74 
QrCodeScanModeFragment(boolean isGroupOp, BluetoothDevice sink)75     public QrCodeScanModeFragment(boolean isGroupOp, BluetoothDevice sink) {
76         mIsGroupOp = isGroupOp;
77         mSink = sink;
78     }
79 
80     @Override
onCreate(Bundle savedInstanceState)81     public void onCreate(Bundle savedInstanceState) {
82         super.onCreate(savedInstanceState);
83         mContext = getContext();
84         mController = new QrCodeScanModeController(mContext);
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                     mController.addSource(mSink, mBroadcastMetadata, mIsGroupOp);
213                     updateSummary();
214                     mSummary.sendAccessibilityEvent(
215                             AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
216                     break;
217                 default:
218             }
219         }
220     };
221 
showErrorMessage(@tringRes int messageResId)222     private void showErrorMessage(@StringRes int messageResId) {
223         final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
224                 getString(messageResId));
225         message.sendToTarget();
226     }
227 
handleBtLeAudioScanner()228     private void handleBtLeAudioScanner() {
229         Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
230         mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
231     }
232 
updateSummary()233     private void updateSummary() {
234         mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner));
235     }
236 
237     @Override
getMetricsCategory()238     public int getMetricsCategory() {
239         return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE;
240     }
241 }
242