Commit ff7ba373 authored by Drahomír Karchňák's avatar Drahomír Karchňák

#641 WIP - IOS QR Code Reader now handles permission requests much better....

#641 WIP - IOS QR Code Reader now handles permission requests much better. Added custom dialog, which prompts user to allow Tablexia to use camera on the device.
parent 734c0582
......@@ -30,6 +30,7 @@ system_proceed=Pokračovat
system_back=Zpět
system_understand=Rozumím
system_close=Zavřít
system_settings=Nastavení
zipassetloader_error=Chyba: Nemohu stáhnout dodatečná data! Zkontrolujte prosím připojení k internetu.
zipassetloader_download_request: Právě využíváte mobilní připojení k internetu. Tablexia potřebuje stáhnout ~150 MB herních dat. Přejete si je stáhnout nyní ?
......@@ -53,9 +54,11 @@ mainmenu_screendebug=Debug Screen
mainmenu_panorama=Panorama
usermenu_newuser=+ Nový detektiv
usermenu_deleteuser= Opravdu chceš smazat uživatele
usermenu_dialog_yes = Ano
usermenu_dialog_no = Ne
usermenu_deleteuser=Opravdu chceš smazat uživatele
usermenu_dialog_yes=Ano
usermenu_dialog_no=Ne
qr_scanner_permission_message_ios=Pro použití čtečky QR kódů je potřeba povolit Tablexii přístup k fotoaparátu v nastavení svého zařízení.
user_logout_message=Opravdu chcete odhlásit stávajícího uživatele?
......
......@@ -30,6 +30,7 @@ system_back=Zurück
system_understand=Ich verstehe
system_proceed=Fortsetzen
system_close=Schließen
system_settings=[DE]Nastavení
zipassetloader_error=Fehler: Nicht, um zusätzliche Daten Download! Bitte überprüfen Sie Ihre Internetverbindung.
zipassetloader_download_request=Sie verwenden im Moment das mobile Internet. Tablexia lädt ~ 150 MB an Spieldaten herunter. Möchten Sie die Daten wirklich jetzt runterladen?
......@@ -58,6 +59,9 @@ usermenu_newuser=+ Neues Profil
usermenu_deleteuser=Möchtest du dieses Profil wirklich löschen
usermenu_dialog_yes=Ja
usermenu_dialog_no=Nein
qr_scanner_permission_message_ios=[DE]Pro použití čtečky QR kódů je potřeba povolit Tablexii přístup k fotoaparátu v nastavení svého zařízení.[DE]
user_logout_message=Möchtest du abmelden?
preloader_title=Wie spielt man?
confirm_button=Ich verstehe
......
......@@ -30,6 +30,7 @@ system_back=Späť
system_understand=Rozumiem
system_proceed=Pokračovať
system_close=Zavrieť
system_settings=Nastavenie
zipassetloader_error=Chyba: Nemôžem stiahnuť dodatočná dáta! Skontrolujte prosím pripojenie k internetu.
zipassetloader_download_request:Práve využívate mobilné pripojenie k internetu. Tablexia potrebuje stiahnuť ~150 M herných dat. Prajete si ich stiahnuť teraz?
......@@ -58,6 +59,7 @@ usermenu_deleteuser=Naozaj chceš zmazať užívateľa
usermenu_dialog_yes=Áno
usermenu_dialog_no=Nie
qr_scanner_permission_message_ios=Pre použitie čítačky QR kódu je potrebné povoliť Tablexii prístup k fotoaparátu v nastavení svojho zariadenia.
user_logout_message=Naozaj chceš odhlásiť súčasného užívateľa?
......
......@@ -142,11 +142,6 @@ public class AndroidCamera2QRCodeScanner extends QRCodeScanner {
return cameraDevice;
}
@Override
public boolean isCameraAccessible() {
return cameraAccessible;
}
@Override
public void onCameraPreviewStarted() {
textureView = new TextureView(androidLauncher);
......@@ -203,7 +198,19 @@ public class AndroidCamera2QRCodeScanner extends QRCodeScanner {
}
});
}
@Override
protected CameraPermissionStatus refreshCameraPermission() {
//TODO implement this properly
if(cameraAccessible) return CameraPermissionStatus.Allowed;
else return CameraPermissionStatus.Denied;
}
@Override
protected void requestPermissionSettingChange() {
//TODO - implement this!
}
private int getOrientation(){
return androidLauncher.getWindowManager().getDefaultDisplay().getRotation();
}
......
......@@ -104,11 +104,6 @@ public class AndroidQRCodeScanner extends QRCodeScanner implements Camera.Previe
if (previewCalled) startPreviewAsync();
}
@Override
public boolean isCameraAccessible() {
return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
@Override
public void onCameraPreviewStarted() {
if (isCameraPreviewActive()) stopPreview();
......@@ -126,6 +121,17 @@ public class AndroidQRCodeScanner extends QRCodeScanner implements Camera.Previe
stopPreviewAsync();
}
@Override
protected CameraPermissionStatus refreshCameraPermission() {
//TODO
return CameraPermissionStatus.Allowed;
}
@Override
protected void requestPermissionSettingChange() {
//TODO
}
private synchronized void startPreviewAsync() {
Runnable r = new Runnable() {
public void run() {
......
......@@ -58,6 +58,7 @@ public class ApplicationTextManager extends TablexiaDataManager<I18NBundle> impl
public static final String SYSTEM_BACK = "system_back";
public static final String SYSTEM_UNDERSTAND = "system_understand";
public static final String SYSTEM_CLOSE = "system_close";
public static final String SYSTEM_SETTINGS = "system_settings";
public static final String ZIPASSETLOADER_ERROR = "zipassetloader_error";
public static final String ZIPASSETLOADER_DOWNLOAD_REQUEST = "zipassetloader_download_request";
......@@ -147,9 +148,7 @@ public class ApplicationTextManager extends TablexiaDataManager<I18NBundle> impl
public static final String SYNC_QR_REQUEST_BUTTON = "sync_qr_request_button";
public static final String SYNC_QR_REQUEST_INFO_DIALOG_TEXT = "sync_qr_request_info_dialog_text";
public static final String SYNC_QR_REQUEST_ON_DECODED_DIALOG_TEXT = "sync_qr_request_on_decoded_dialog_text";
}
}
private static final String APPLICATION_TEXT_RESOURCE_FILE = "text/application/application";
......
......@@ -25,14 +25,37 @@ import cz.nic.tablexia.util.Log;
* Created by drahomir on 10/5/16.
*/
public abstract class QRCodeScanner {
public enum CameraPermissionStatus {
Allowed(true, false),
Denied(false, true),
NotDetermined(false, true);
private boolean scanningAllowed;
private boolean requestsPermissionChange;
CameraPermissionStatus(boolean scanningAllowed, boolean requestsPermissionChange) {
this.scanningAllowed = scanningAllowed;
this.requestsPermissionChange = requestsPermissionChange;
}
public boolean isScanningAllowed() {
return scanningAllowed;
}
public boolean requestsPermissionChange() {
return requestsPermissionChange;
}
}
public interface QRCodeListener {
void onCodeScanned(String data);
}
private QRCodeListener listener;
private boolean scanningEnabled = false;
protected boolean cameraPreviewActive = false;
private QRCodeListener listener;
private boolean scanningEnabled = false;
protected boolean cameraPreviewActive = false;
protected CameraPermissionStatus cameraPermissionStatus = CameraPermissionStatus.NotDetermined;
public void enableScanning() {
scanningEnabled = true;
}
......@@ -73,7 +96,18 @@ public abstract class QRCodeScanner {
Log.info(getClass(), "Camera preview is already active. Cannot start it again!");
return;
}
cameraPermissionStatus = refreshCameraPermission();
if(!cameraPermissionStatus.isScanningAllowed()) {
if(cameraPermissionStatus.requestsPermissionChange()) {
Log.info(getClass(), "Requesting permission setting change!");
requestPermissionSettingChange();
} else {
Log.info(getClass(), "Scanning is not allowed, but permission change is not requested!");
}
}
Log.info(getClass(), "Starting Camera Preview!");
cameraPreviewActive = true;
......@@ -91,10 +125,14 @@ public abstract class QRCodeScanner {
onCameraPreviewStopped();
cameraPreviewActive = false;
}
/* ABSTRACT METHODS */
public abstract boolean isCameraAccessible();
public CameraPermissionStatus getCameraPermissionStatus() {
return cameraPermissionStatus;
}
public abstract void onCameraPreviewStarted();
public abstract void onCameraPreviewStopped();
}
protected abstract CameraPermissionStatus refreshCameraPermission();
protected abstract void requestPermissionSettingChange();
}
\ No newline at end of file
......@@ -17,8 +17,10 @@
package cz.nic.tablexia.util.ui.dialog;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Scaling;
......@@ -43,9 +45,6 @@ import cz.nic.tablexia.util.ui.dialog.components.TextContentDialogComponent;
import cz.nic.tablexia.util.ui.dialog.components.TouchCloseDialogComponent;
import cz.nic.tablexia.util.ui.dialog.components.ViewportMaximumSizeComponent;
/**
* Created by Matyáš Latner.
*/
public class TablexiaComponentDialogFactory implements Disposable {
public static final int WARNING_DIALOG_WIDTH = 400;
......@@ -163,4 +162,30 @@ public class TablexiaComponentDialogFactory implements Disposable {
public TablexiaComponentDialog createDialog(Stage stage, TablexiaComponentDialog.TablexiaDialogType dialogType, TablexiaDialogComponentAdapter ... componentDefinitions) {
return new TablexiaComponentDialog(stage, dialogType, componentDefinitions);
}
public TablexiaComponentDialog createPermissionDialog(final String message, final Runnable onConfirmRunnable) {
TablexiaDialogComponentAdapter[] componentAdapters = new TablexiaDialogComponentAdapter[] {
new CenterPositionDialogComponent(),
new FixedSpaceContentDialogComponent(),
new ResizableSpaceContentDialogComponent(),
new TextContentDialogComponent(message),
new ResizableSpaceContentDialogComponent(),
new PositiveNegativeButtonContentDialogComponent(
new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if(onConfirmRunnable != null) onConfirmRunnable.run();
}
},
PositiveNegativeButtonContentDialogComponent.PositiveNegativeButtonType.SETTINGS_DECLINE),
new FixedSpaceContentDialogComponent()
};
return new TablexiaComponentDialog(stage, TablexiaComponentDialog.TablexiaDialogType.DIALOG_SQUARE_BORDERS, componentAdapters) {
@Override
public void show() {
show(WARNING_DIALOG_WIDTH, WARNING_DIALOG_HEIGHT);
}
};
}
}
......@@ -35,7 +35,8 @@ public class PositiveNegativeButtonContentDialogComponent extends TwoButtonConte
YES_NO (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_YES, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_NO),
CONFIRM_DECLINE (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_CONFIRM, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_DECLINE),
AGAIN_EXIT (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_RETRY, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_EXIT),
AGAIN_BACK (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_RETRY, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_BACK);
AGAIN_BACK (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_RETRY, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_BACK),
SETTINGS_DECLINE (ApplicationTextManager.ApplicationTextsAssets.SYSTEM_SETTINGS, ApplicationTextManager.ApplicationTextsAssets.SYSTEM_DECLINE);
private final String positiveText;
private final String negativeText;
......
......@@ -20,7 +20,6 @@ package cz.nic.tablexia;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.iosrobovm.IOSApplication;
import org.robovm.apple.avfoundation.AVAuthorizationStatus;
import org.robovm.apple.avfoundation.AVCaptureConnection;
import org.robovm.apple.avfoundation.AVCaptureDevice;
import org.robovm.apple.avfoundation.AVCaptureDeviceInput;
......@@ -39,7 +38,7 @@ import org.robovm.apple.dispatch.DispatchQueue;
import org.robovm.apple.dispatch.DispatchQueueAttr;
import org.robovm.apple.foundation.NSArray;
import org.robovm.apple.foundation.NSErrorException;
import org.robovm.apple.foundation.NSString;
import org.robovm.apple.foundation.NSURL;
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIInterfaceOrientation;
import org.robovm.apple.uikit.UIView;
......@@ -47,15 +46,13 @@ import org.robovm.apple.uikit.UIViewController;
import org.robovm.objc.block.VoidBooleanBlock;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import cz.nic.tablexia.loader.application.ApplicationTextManager;
import cz.nic.tablexia.util.Log;
import cz.nic.tablexia.util.ui.QRCodeScanner;
import cz.nic.tablexia.util.ui.dialog.TablexiaComponentDialogFactory;
public class IOSQRCodeScanner extends QRCodeScanner {
private static final String DISPATCH_QUEUE_NAME = "qrCodeQueue";
private class CaptureMetadataOutputDelegate extends AVCaptureMetadataOutputObjectsDelegateAdapter{
private IOSQRCodeScanner qrCodeScanner;
......@@ -76,6 +73,9 @@ public class IOSQRCodeScanner extends QRCodeScanner {
}
}
}
private static final String DISPATCH_QUEUE_NAME = "qrCodeQueue";
private static final String PERMISSION_MESSAGE_TEXT_KEY = "qr_scanner_permission_message_ios";
private AVCaptureDevice cameraDevice;
......@@ -91,11 +91,8 @@ public class IOSQRCodeScanner extends QRCodeScanner {
private DispatchQueue dispatchQueue;
private AtomicBoolean hasPermissions = new AtomicBoolean(false);
private AtomicBoolean startRequested = new AtomicBoolean(false);
private void initializeCameraDevice() {
if(cameraDevice != null) return;
private boolean initializeCameraDevice() {
if(cameraDevice != null) return true;
NSArray<AVCaptureDevice> captureDevices = AVCaptureDevice.getDevicesForMediaType(AVMediaType.Video);
......@@ -103,52 +100,65 @@ public class IOSQRCodeScanner extends QRCodeScanner {
if(captureDevice.getPosition() == AVCaptureDevicePosition.Back) {
//We found back camera!
cameraDevice = captureDevice;
break;
return true;
}
}
return false;
}
private void setupPermissionsToUseCamera() {
if(cameraDevice == null) {
hasPermissions.set(false);
return;
}
AVAuthorizationStatus status = cameraDevice.getAuthorizationStatusForMediaType(AVMediaType.Video);
Log.info(getClass(), "Authorization status for video media type: " + status.name());
switch (status) {
@Override
protected CameraPermissionStatus refreshCameraPermission() {
if(!initializeCameraDevice()) return CameraPermissionStatus.Denied;
switch (cameraDevice.getAuthorizationStatusForMediaType(AVMediaType.Video)) {
case Authorized:
hasPermissions.set(true);
break;
return CameraPermissionStatus.Allowed;
case NotDetermined:
cameraDevice.requestAccessForMediaType(AVMediaType.Video, new VoidBooleanBlock() {
@Override
public void invoke(boolean b) {
hasPermissions.set(b);
if(hasPermissions.get() && startRequested.get() && !isCameraPreviewActive()) {
Log.info(getClass(), "Starting camera preview again!");
startCameraPreview();
}
}
});
break;
return CameraPermissionStatus.NotDetermined;
case Restricted:
case Denied:
hasPermissions.set(false);
return;
return CameraPermissionStatus.Denied;
}
//Something went wrong...
return CameraPermissionStatus.Denied;
}
@Override
public boolean isCameraAccessible() {
initializeCameraDevice();
return cameraDevice != null;
protected void requestPermissionSettingChange() {
Log.info(getClass(), "Requesting permission change!");
//First request...
if(getCameraPermissionStatus() == CameraPermissionStatus.NotDetermined) {
cameraDevice.requestAccessForMediaType(AVMediaType.Video, new VoidBooleanBlock() {
@Override
public void invoke(boolean result) {
if (result) {
cameraPermissionStatus = CameraPermissionStatus.Allowed;
Log.info(getClass(), "Starting camera preview again!");
startCameraPreview();
} else cameraPermissionStatus = CameraPermissionStatus.Denied;
}
});
}
else { //First permission request has been denied. Open our own dialog...
TablexiaComponentDialogFactory.getInstance().createPermissionDialog(
ApplicationTextManager.getInstance().getText(PERMISSION_MESSAGE_TEXT_KEY),
new Runnable() {
@Override
public void run() {
//On success open settings url for this app
UIApplication.getSharedApplication().openURL(new NSURL(UIApplication.getOpenSettingsURLString()));
}
}
).show();
}
}
private AVCaptureVideoOrientation getOrientationForCameraPreviewLayer() {
switch (UIApplication.getSharedApplication().getStatusBarOrientation()) {
case LandscapeLeft: return AVCaptureVideoOrientation.LandscapeLeft;
......@@ -167,17 +177,6 @@ public class IOSQRCodeScanner extends QRCodeScanner {
public void onCameraPreviewStarted() {
captureSession = new AVCaptureSession();
initializeCameraDevice();
setupPermissionsToUseCamera();
if(!hasPermissions.get()) {
cameraPreviewActive = false;
startRequested.set(true);
captureSession.dispose();
captureSession = null;
return;
}
//INPUT DEVICE//
try {
captureDeviceInput = new AVCaptureDeviceInput(cameraDevice);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment