Skip to content

Commit 4db8512

Browse files
committed
feat: fallback to zxing-based lib to scan QrCodes
Uses a zxing-based library as a fallback when scanning a Qr-Code and GMS is not available. Closes #825
1 parent 72df722 commit 4db8512

8 files changed

Lines changed: 161 additions & 21 deletions

File tree

mastodon/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ dependencies {
9696
implementation 'com.squareup:otto:1.3.8'
9797
implementation 'de.psdev:async-otto:1.0.3'
9898
implementation 'com.google.zxing:core:3.5.3'
99+
implementation 'com.github.markusfisch:BarcodeScannerView:1.6.0'
99100
implementation 'org.microg:safe-parcel:1.5.0'
100101
implementation 'org.parceler:parceler-api:1.1.12'
101102
annotationProcessor 'org.parceler:parceler:1.1.12'

mastodon/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
1010
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
1111
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
12+
<uses-permission android:name="android.permission.CAMERA" />
13+
<uses-feature android:name="android.hardware.camera" />
14+
<uses-feature android:name="android.hardware.camera.autofocus" />
1215

1316
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
1417

@@ -79,6 +82,7 @@
7982
<data android:mimeType="*/*"/>
8083
</intent-filter>
8184
</activity>
85+
<activity android:name=".QrCodeScanActivity"/>
8286

8387
<service android:name=".AudioPlayerService" android:foregroundServiceType="mediaPlayback"/>
8488
<service android:name=".NotificationActionHandlerService" android:exported="false"/>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.joinmastodon.android;
2+
3+
import android.Manifest;
4+
import android.app.Activity;
5+
import android.content.Intent;
6+
import android.content.pm.PackageManager;
7+
import android.net.Uri;
8+
import android.os.Build;
9+
import android.os.Bundle;
10+
import android.os.VibrationEffect;
11+
import android.os.Vibrator;
12+
import android.provider.Settings;
13+
import android.view.HapticFeedbackConstants;
14+
import android.view.View;
15+
16+
import androidx.annotation.NonNull;
17+
18+
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
19+
import org.joinmastodon.android.ui.utils.UiUtils;
20+
21+
import de.markusfisch.android.barcodescannerview.widget.BarcodeScannerView;
22+
23+
public class QrCodeScanActivity extends Activity{
24+
private static final int PERMISSION_RESULT=65537;
25+
private BarcodeScannerView scannerView;
26+
27+
@Override
28+
public void onCreate(Bundle savedInstanceState){
29+
super.onCreate(savedInstanceState);
30+
UiUtils.setUserPreferredTheme(this);
31+
setContentView(R.layout.activity_qr_scan);
32+
33+
if(this.checkSelfPermission(Manifest.permission.CAMERA)!=PackageManager.PERMISSION_GRANTED){
34+
requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_RESULT);
35+
}
36+
37+
findViewById(R.id.dismiss).setOnClickListener(view -> finish());
38+
scannerView=findViewById(R.id.scanner);
39+
scannerView.setCropRatio(.75f);
40+
scannerView.setOnBarcodeListener(barcode -> {
41+
vibrate(scannerView);
42+
Intent result=new Intent();
43+
result.putExtra("barcode", barcode.getText());
44+
setResult(RESULT_OK, result);
45+
finish();
46+
return false;
47+
});
48+
}
49+
50+
@Override
51+
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
52+
if(requestCode==PERMISSION_RESULT){
53+
if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
54+
scannerView.openAsync();
55+
}else if(!this.shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
56+
new M3AlertDialogBuilder(this)
57+
.setTitle(R.string.permission_required)
58+
.setMessage(R.string.storage_permission_to_download)
59+
.setPositiveButton(R.string.open_settings, (dialog, which)->this.startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", this.getPackageName(), null))))
60+
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> finish())
61+
.setOnCancelListener(dialogInterface -> finish())
62+
.show();
63+
}
64+
}
65+
}
66+
67+
68+
private static void vibrate(View v) {
69+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
70+
v.performHapticFeedback(HapticFeedbackConstants.CONFIRM);
71+
return;
72+
}
73+
74+
Vibrator vibrator=v.getContext().getSystemService(Vibrator.class);
75+
76+
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
77+
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
78+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
79+
VibrationEffect effect=VibrationEffect.createOneShot(75L, 128);
80+
vibrator.vibrate(effect);
81+
} else {
82+
vibrator.vibrate(75L);
83+
}
84+
}
85+
86+
87+
@Override
88+
public void onResume() {
89+
super.onResume();
90+
scannerView.openAsync();
91+
}
92+
93+
@Override
94+
public void onPause() {
95+
super.onPause();
96+
scannerView.close();
97+
}
98+
}

mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileQrCodeFragment.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import android.widget.LinearLayout;
5454
import android.widget.TextView;
5555
import android.widget.Toast;
56-
import android.window.OnBackInvokedCallback;
5756
import android.window.OnBackInvokedDispatcher;
5857

5958
import com.google.zxing.BarcodeFormat;
@@ -64,6 +63,7 @@
6463
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
6564

6665
import org.joinmastodon.android.MainActivity;
66+
import org.joinmastodon.android.QrCodeScanActivity;
6767
import org.joinmastodon.android.R;
6868
import org.joinmastodon.android.api.MastodonAPIController;
6969
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -127,7 +127,11 @@ public void onCreate(Bundle savedInstanceState){
127127
accountID=getArguments().getString("account");
128128
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
129129
setCancelable(false);
130-
scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true);
130+
131+
scannerIntent=GmsClient.isGooglePlayServicesAvailable(getActivity())
132+
? BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true)
133+
: new Intent(getActivity(), QrCodeScanActivity.class);
134+
131135
}
132136

133137
@Override
@@ -265,11 +269,9 @@ public void dismiss(){
265269

266270
@Override
267271
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
268-
if(GmsClient.isGooglePlayServicesAvailable(getActivity())){
269-
MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code);
270-
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
271-
item.setIcon(R.drawable.ic_qr_code_scanner_24px);
272-
}
272+
MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code);
273+
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
274+
item.setIcon(R.drawable.ic_qr_code_scanner_24px);
273275
}
274276

275277
@Override
@@ -330,11 +332,15 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
330332

331333
@Override
332334
public void onActivityResult(int requestCode, int resultCode, Intent data){
333-
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
334-
Barcode code=BarcodeScanner.getResult(data);
335+
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){
336+
String code=data.getStringExtra("barcode");
337+
if(BarcodeScanner.isValidResult(data)){
338+
Barcode barcode=BarcodeScanner.getResult(data);
339+
code=barcode.rawValue;
340+
}
335341
if(code!=null){
336-
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
337-
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
342+
if(code.startsWith("https:") || code.startsWith("http:")){
343+
((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID);
338344
dismiss();
339345
}else{
340346
Toast.makeText(themeWrapper, R.string.link_not_supported, Toast.LENGTH_SHORT).show();

mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import android.widget.Toast;
1818

1919
import org.joinmastodon.android.MainActivity;
20+
import org.joinmastodon.android.QrCodeScanActivity;
2021
import org.joinmastodon.android.R;
2122
import org.joinmastodon.android.fragments.ScrollableToTop;
2223
import org.joinmastodon.android.googleservices.GmsClient;
@@ -70,7 +71,9 @@ public void onCreate(Bundle savedInstanceState){
7071
setRetainInstance(true);
7172

7273
accountID=getArguments().getString("account");
73-
scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true);
74+
scannerIntent=GmsClient.isGooglePlayServicesAvailable(getActivity())
75+
? BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true)
76+
: new Intent(getActivity(), QrCodeScanActivity.class);
7477
}
7578

7679
@Nullable
@@ -186,11 +189,7 @@ public void onTabReselected(TabLayout.Tab tab){
186189
searchView.setVisibility(View.VISIBLE);
187190
}
188191
searchScanQR=view.findViewById(R.id.search_scan_qr);
189-
if(!GmsClient.isGooglePlayServicesAvailable(getActivity())){
190-
searchScanQR.setVisibility(View.GONE);
191-
}else{
192-
searchScanQR.setOnClickListener(v->openQrScanner());
193-
}
192+
searchScanQR.setOnClickListener(v->openQrScanner());
194193

195194
View searchWrap=view.findViewById(R.id.search_wrap);
196195
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
@@ -280,11 +279,15 @@ public void onFragmentResult(int reqCode, boolean success, Bundle result){
280279

281280
@Override
282281
public void onActivityResult(int requestCode, int resultCode, Intent data){
283-
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
284-
Barcode code=BarcodeScanner.getResult(data);
282+
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){
283+
String code=data.getStringExtra("barcode");
284+
if(BarcodeScanner.isValidResult(data)){
285+
Barcode barcode=BarcodeScanner.getResult(data);
286+
code=barcode.rawValue;
287+
}
285288
if(code!=null){
286-
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
287-
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
289+
if(code.startsWith("https:") || code.startsWith("http:")){
290+
((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID);
288291
}else{
289292
Toast.makeText(getActivity(), R.string.link_not_supported, Toast.LENGTH_SHORT).show();
290293
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
android:clipChildren="false"
8+
android:clipToPadding="false">
9+
10+
<de.markusfisch.android.barcodescannerview.widget.BarcodeScannerView
11+
android:id="@+id/scanner"
12+
android:layout_width="match_parent"
13+
android:layout_height="match_parent"/>
14+
15+
<ImageButton
16+
android:id="@+id/dismiss"
17+
android:layout_width="wrap_content"
18+
android:layout_height="wrap_content"
19+
android:layout_gravity="start|top"
20+
android:background="?android:selectableItemBackground"
21+
android:contentDescription="@string/back"
22+
android:padding="16dp"
23+
android:src="@drawable/ic_arrow_back"
24+
android:tint="@android:color/white"
25+
android:layout_margin="16dp"/>
26+
</FrameLayout>

mastodon/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@
233233
<string name="download">Download</string>
234234
<string name="permission_required">Permission required</string>
235235
<string name="storage_permission_to_download">The app needs access to your storage to save this file.</string>
236+
<string name="camera_permission_to_scan">The app needs access to your camera to scan.</string>
236237
<string name="open_settings">Open settings</string>
237238
<string name="error_saving_file">Error saving file</string>
238239
<string name="file_saved">File saved</string>

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ dependencyResolutionManagement {
44
google()
55
mavenCentral()
66
mavenLocal()
7+
maven { url 'https://jitpack.io' }
78
}
89
}
910
rootProject.name = "Mastodon"

0 commit comments

Comments
 (0)