diff --git a/android/src/net/gcompris/GComprisActivity.java b/android/src/net/gcompris/GComprisActivity.java index e05fccf4a..0d813fbd8 100644 --- a/android/src/net/gcompris/GComprisActivity.java +++ b/android/src/net/gcompris/GComprisActivity.java @@ -1,223 +1,249 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtAndroidExtras module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ package net.gcompris; import org.qtproject.qt5.android.bindings.QtApplication; import org.qtproject.qt5.android.bindings.QtActivity; import com.android.vending.billing.*; import android.media.AudioManager; import android.util.Log; import android.os.Bundle; import android.os.IBinder; import android.content.ServiceConnection; import android.content.Intent; import android.content.ComponentName; import android.content.Context; import android.app.PendingIntent; import org.json.JSONObject; import java.util.ArrayList; +import android.view.WindowManager; public class GComprisActivity extends QtActivity { private static GComprisActivity m_instance; // public static final String SKU_NAME = "android.test.purchased"; // for testing public static final String SKU_NAME = "full"; private IInAppBillingService m_service; private ServiceConnection m_serviceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { m_service = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { if(service.isBinderAlive()) { m_service = IInAppBillingService.Stub.asInterface(service); } } }; public GComprisActivity() { m_instance = this; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"), m_serviceConnection, Context.BIND_AUTO_CREATE); } @Override public void onDestroy() { super.onDestroy(); unbindService(m_serviceConnection); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && data != null && requestCode == 1001) { int responseCode = data.getIntExtra("RESPONSE_CODE", -1); String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); try { JSONObject jo = new JSONObject(purchaseData); String sku = jo.getString("productId"); int purchaseState = jo.getInt("purchaseState"); String payload = jo.getString("developerPayload"); String purchaseToken = jo.getString("purchaseToken"); if (sku.equals(SKU_NAME) && purchaseState == 0) { bought(true); return; } } catch (Exception e) { e.printStackTrace(); } Log.e(QtApplication.QtTAG, "Buying full version failed: Result code == " + resultCode); } } private static native void bought(boolean b); public static void buyGCompris() { if (m_instance.m_service == null) { Log.e(QtApplication.QtTAG, "Buying full version failed: No billing service"); return; } try { Bundle buyIntentBundle = m_instance.m_service.getBuyIntent(3, m_instance.getPackageName(), SKU_NAME, "inapp", ""); int responseCode = buyIntentBundle.getInt("RESPONSE_CODE"); if (responseCode == 0 /* BILLING_RESPONSE_RESULT_OK */) { PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); m_instance.startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); return; } else if (responseCode == 7 /* BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED */) { bought(true); } else { Log.e(QtApplication.QtTAG, "Buying full version failed: Response code == " + responseCode); } } catch (Exception e) { Log.e(QtApplication.QtTAG, "Exception caught when buying full version!", e); } } public static void checkPayment() { if (m_instance.m_service == null) { Log.e(QtApplication.QtTAG, "Check full version is bought failed: No billing service"); return; } try { Bundle ownedItems = m_instance.m_service.getPurchases(3, m_instance.getPackageName(), "inapp", null); int responseCode = ownedItems.getInt("RESPONSE_CODE"); if (responseCode == 0) { ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); ArrayList purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); for(int i=0; i * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* ToDo: - make sensitivity configurable - add rectangular fixture for goal - editor: add 'clear' button - editor: allow going back: level 1 -> last level - add new item: unordered contact, that has to be collected but in an arbitrary order */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris .import Box2D 2.0 as Box2D .import "qrc:/gcompris/src/core/core.js" as Core .import QtQml 2.2 as Qml Qt.include("balancebox_common.js") var dataset = null; // Parameters that control the ball's dynamics var m = 0.2; // without ppm-correction: 10 var g = 9.81; // without ppm-correction: 50.8 var box2dPpm = 32; // pixelsPerMeter used in Box2D's world var boardSizeM = 0.9; // board's real edge length, fixed to 90 cm var boardSizePix = 500; // board's current size in pix (acquired dynamically) var dpiBase=139; var boardSizeBase = 760; var curDpi = null; var pixelsPerMeter = null; var vFactor = pixelsPerMeter / box2dPpm; // FIXME: calculate! var step = 20; // time step (in ms) var friction = 0.15; var restitution = 0.3; // rebounce factor // stuff for keyboard based tilting var keyboardTiltStep = 0.5; // degrees var keyboardTimeStep = 20; // ms var lastKey; var keyboardIsTilting = false; // tilting or resetting to horizontal var debugDraw = false; var currentLevel = 0; var numberOfLevel = 0; var items; var level; var map; // current map var goal = null; var holes = new Array(); var walls = new Array(); var contacts = new Array(); var ballContacts = new Array(); var goalUnlocked; var lastContact; var ballContacts; var wallComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/Wall.qml"); var contactComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/BalanceContact.qml"); var balanceItemComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/BalanceItem.qml"); var contactIndex = -1; var pendingObjects = 0; var pendingReconfigure = false; function start(items_) { items = items_; currentLevel = 0; if (items.mode === "play") { if (GCompris.ApplicationInfo.isMobile) { - // lock screen orientation + // we don't have many touch events, therefore disable screensaver on android: + GCompris.ApplicationInfo.setKeepScreenOn(true); + // lock screen orientation to landscape: GCompris.ApplicationInfo.setRequestedOrientation(0); if (GCompris.ApplicationInfo.getNativeOrientation() === Qt.PortraitOrientation) { /* * Adjust tilting if native orientation != landscape. * * Note: As of Qt 5.4.1 QTiltSensor as well as QRotationSensor * report on Android * isFeatureSupported(AxesOrientation) == false. * Therefore we honour rotation manually. */ items.tilt.swapAxes = true; items.tilt.invertX = true; } } var levelsFile = builtinFile; if (items.levelSet === "user") levelsFile = userFile; dataset = items.parser.parseFromUrl(levelsFile, validateLevels); if (dataset == null) { console.error("Balancebox: Error loading levels from " + levelsFile + ", can't continue!"); return; } } else { // testmode: dataset = [items.testLevel]; } numberOfLevel = dataset.length; reconfigureScene(); } function reconfigureScene() { if (items === undefined || items.mapWrapper === undefined) return; if (pendingObjects > 0) { pendingReconfigure = true; return; } // set up dynamic variables for movement: pixelsPerMeter = (items.mapWrapper.length / boardSizeBase) * boardSizePix / boardSizeM; vFactor = pixelsPerMeter / box2dPpm; // console.log("Starting: mode=" + items.mode // + " pixelsPerM=" + items.world.pixelsPerMeter // + " timeStep=" + items.world.timeStep // + " posIterations=" + items.world.positionIterations // + " velIterations=" + items.world.velocityIterations // + " boardSizePix" + boardSizePix + " (real " + items.mapWrapper.length + ")" // + " pixelsPerMeter=" + pixelsPerMeter // + " vFactor=" + vFactor // + " dpi=" + items.dpi // + " nativeOrientation=" + GCompris.ApplicationInfo.getNativeOrientation()); initLevel(); } function sinDeg(num) { return Math.sin(num/180*Math.PI); } function moveBall() { var dt = step / 1000; var dvx = ((m*g*dt) * sinDeg(items.tilt.yRotation)) / m; var dvy = ((m*g*dt) * sinDeg(items.tilt.xRotation)) / m; // console.log("moving ball: dv: " + items.ball.body.linearVelocity.x // + "/" + items.ball.body.linearVelocity.y // + " -> " + (items.ball.body.linearVelocity.x+dvx) // + "/" + (items.ball.body.linearVelocity.y+dvy)); items.ball.body.linearVelocity.x += dvx * vFactor; items.ball.body.linearVelocity.y += dvy * vFactor; checkBallContacts(); } function checkBallContacts() { for (var k = 0; k < ballContacts.length; k++) { if (items.ball.x > ballContacts[k].x - items.ballSize/2 && items.ball.x < ballContacts[k].x + items.ballSize/2 && items.ball.y > ballContacts[k].y - items.ballSize/2 && items.ball.y < ballContacts[k].y + items.ballSize/2) { // collision if (ballContacts[k].categories == items.holeType) finishBall(false, ballContacts[k].x, ballContacts[k].y); else if (ballContacts[k].categories == items.goalType && goalUnlocked) finishBall(true, ballContacts[k].x, ballContacts[k].y); else if (ballContacts[k].categories == items.buttonType) { if (!ballContacts[k].pressed && ballContacts[k].orderNum == lastContact + 1) { ballContacts[k].pressed = true; lastContact = ballContacts[k].orderNum; if (lastContact == contacts.length) { items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/win.wav"); goalUnlocked = true; goal.imageSource = baseUrl + "/door.svg"; } else items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/scroll.wav"); // bleep } } } } } function finishBall(won, x, y) { items.timer.stop(); items.keyboardTimer.stop(); items.ball.x = x; items.ball.y = y; items.ball.scale = 0.4; items.ball.body.linearVelocity = Qt.point(0, 0); if (won) items.bonus.good("flower"); else items.bonus.bad("flower"); } function stop() { // reset everything tearDown(); // unlock screen orientation - if (GCompris.ApplicationInfo.isMobile) + if (GCompris.ApplicationInfo.isMobile) { + GCompris.ApplicationInfo.setKeepScreenOn(false); GCompris.ApplicationInfo.setRequestedOrientation(-1); + } // make sure loading overlay is really stopped items.loading.stop(); } function createObject(component, properties) { var p = properties; p.world = items.world; var object = component.createObject(items.mapWrapper, p); return object; } var incubators; // need to reference all returned incubators in global scope // or things don't work function incubateObject(targetArr, component, properties) { var p = properties; p.world = items.world; var incubator = component.incubateObject(items.mapWrapper, p); if (incubator === null) { console.error("Error during object incubation!"); items.loading.stop(); return; } incubators.push(incubator); if (incubator.status === Qml.Component.Ready) targetAttr.push(incubator.object); else if (incubator.status === Qml.Component.Loading) { pendingObjects++; incubator.onStatusChanged = function(status) { if (status === Qml.Component.Ready) targetArr.push(incubator.object); else console.error("Error during object creation!"); if (--pendingObjects === 0) { // initMap completed if (pendingReconfigure) { pendingReconfigure = false; reconfigureScene(); } else { items.timer.start(); items.loading.stop(); } } } } else console.error("Error during object creation!"); } function initMap() { var modelMap = new Array(); incubators = new Array(); goalUnlocked = true; items.mapWrapper.rows = map.length; items.mapWrapper.columns = map[0].length; pendingObjects = 0; for (var row = 0; row < map.length; row++) { for (var col = 0; col < map[row].length; col++) { var x = col * items.cellSize; var y = row * items.cellSize; var orderNum = (map[row][col] & 0xFF00) >> 8; // debugging: if (debugDraw) { try { var rect = Qt.createQmlObject( "import QtQuick 2.0;Rectangle{" +"width:" + items.cellSize +";" +"height:" + items.cellSize+";" +"x:" + x + ";" +"y:" + y +";" +"color: \"transparent\";" +"border.color: \"blue\";" +"border.width: 1;" +"}", items.mapWrapper); } catch (e) { console.error("Error creating object: " + e); } } if (map[row][col] & NORTH) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y-items.wallSize/2, width: items.cellSize + items.wallSize, height: items.wallSize, shadow: false}); } if (map[row][col] & SOUTH) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y+items.cellSize-items.wallSize/2, width: items.cellSize+items.wallSize, height: items.wallSize, shadow: false}); } if (map[row][col] & EAST) { incubateObject(walls, wallComponent, { x: x+items.cellSize-items.wallSize/2, y: y-items.wallSize/2, width: items.wallSize, height: items.cellSize+items.wallSize, shadow: false}); } if (map[row][col] & WEST) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y-items.wallSize/2, width: items.wallSize, height: items.cellSize+items.wallSize, shadow: false}); } if (map[row][col] & START) { items.ball.x = col * items.cellSize + items.wallSize; items.ball.y = row * items.cellSize + items.wallSize; items.ball.visible = true; } if (map[row][col] & GOAL) { var goalX = col * items.cellSize + items.wallSize/2; var goalY = row * items.cellSize + items.wallSize/2; goal = createObject(balanceItemComponent, { x: goalX, y: goalY, width: items.cellSize - items.wallSize, height: items.cellSize - items.wallSize, imageSource: baseUrl + "/door_closed.svg", categories: items.goalType, sensor: true}); } if (map[row][col] & HOLE) { var holeX = col * items.cellSize + items.wallSize; var holeY = row * items.cellSize + items.wallSize; incubateObject(holes, balanceItemComponent, { x: holeX, y: holeY, width: items.ballSize, height: items.ballSize, imageSource: baseUrl + "/hole.svg", density: 0, friction: 0, restitution: 0, categories: items.holeType, sensor: true}); } if (orderNum > 0) { var contactX = col * items.cellSize + items.wallSize/2; var contactY = row * items.cellSize + items.wallSize/2; goalUnlocked = false; incubateObject(contacts, contactComponent, { x: contactX, y: contactY, width: items.cellSize - items.wallSize, height: items.cellSize - items.wallSize, pressed: false, density: 0, friction: 0, restitution: 0, categories: items.buttonType, sensor: true, orderNum: orderNum, text: level.targets[orderNum-1]}); } } } if (goalUnlocked && goal) // if we have no contacts at all goal.imageSource = baseUrl + "/door.svg"; if (pendingObjects === 0) { // don't have any pending objects (e.g. empty map!): stop overlay items.timer.start(); items.loading.stop(); } } function addBallContact(item) { if (ballContacts.indexOf(item) !== -1) return; ballContacts.push(item); } function removeBallContact(item) { var index = ballContacts.indexOf(item); if (index > -1) ballContacts.splice(index, 1); } function tearDown() { items.ball.body.linearVelocity = Qt.point(0, 0); items.ball.scale = 1; items.ball.visible = false; items.timer.stop(); items.keyboardTimer.stop(); if (holes.length > 0) { for (var i = 0; i< holes.length; i++) holes[i].destroy(); holes.length = 0; } if (walls.length > 0) { for (var i = 0; i< walls.length; i++) walls[i].destroy(); walls.length = 0; } if (contacts.length > 0) { for (var i = 0; i< contacts.length; i++) contacts[i].destroy(); contacts.length = 0; } lastContact = 0; if (goal) goal.destroy(); goal = null; items.tilt.xRotation = 0; items.tilt.yRotation = 0; ballContacts = new Array(); } function initLevel(testLevel) { items.loading.start(); items.bar.level = currentLevel + 1; // reset everything tearDown(); level = dataset[currentLevel]; map = level.map initMap(); } // keyboard tilting stuff: function keyboardHandler() { var MAX_TILT = 5 if (keyboardIsTilting) { if (lastKey == Qt.Key_Left && items.tilt.yRotation > -MAX_TILT) items.tilt.yRotation -= keyboardTiltStep; else if (lastKey == Qt.Key_Right && items.tilt.yRotation < MAX_TILT) items.tilt.yRotation += keyboardTiltStep; else if (lastKey == Qt.Key_Up && items.tilt.xRotation > -MAX_TILT) items.tilt.xRotation -= keyboardTiltStep; else if (lastKey == Qt.Key_Down && items.tilt.xRotation < MAX_TILT) items.tilt.xRotation += keyboardTiltStep; items.keyboardTimer.start(); } else {// is resetting // yRotation: if (items.tilt.yRotation < 0) items.tilt.yRotation = Math.min(items.tilt.yRotation + keyboardTiltStep, 0); else if (items.tilt.yRotation > 0) items.tilt.yRotation = Math.max(items.tilt.yRotation - keyboardTiltStep, 0); // xRotation: if (items.tilt.xRotation < 0) items.tilt.xRotation = Math.min(items.tilt.xRotation + keyboardTiltStep, 0); else if (items.tilt.xRotation > 0) items.tilt.xRotation = Math.max(items.tilt.xRotation - keyboardTiltStep, 0); // resetting done? if (items.tilt.yRotation != 0 || items.tilt.xRotation != 0) items.keyboardTimer.start(); } } function processKeyPress(key) { if (key == Qt.Key_Left || key == Qt.Key_Right || key == Qt.Key_Up || key == Qt.Key_Down) { lastKey = key; keyboardIsTilting = true; items.keyboardTimer.stop(); keyboardHandler(); } } function processKeyRelease(key) { if (key == Qt.Key_Left || key == Qt.Key_Right || key == Qt.Key_Up || key == Qt.Key_Down) { lastKey = key; keyboardIsTilting = false; items.keyboardTimer.stop(); keyboardHandler(); } } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } diff --git a/src/core/ApplicationAndroid.cpp b/src/core/ApplicationAndroid.cpp index 4a5cb1f05..9158f444a 100644 --- a/src/core/ApplicationAndroid.cpp +++ b/src/core/ApplicationAndroid.cpp @@ -1,94 +1,99 @@ /* GCompris - ApplicationSettingsAndroid.cpp * * Copyright (C) 2014-2015 Bruno Coudoin * * Authors: * Bruno Coudoin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "ApplicationSettings.h" #include "ApplicationInfo.h" #include #include #include void ApplicationSettings::setDemoMode(const bool newDemoMode) { if(!newDemoMode) { // Call Google play store QAndroidJniObject::callStaticMethod("net/gcompris/GComprisActivity", "buyGCompris"); } else { // Going back to demo mode, should never happens except for testing ApplicationSettings::getInstance()->bought(false); } } void ApplicationSettings::checkPayment() { #if defined(WITH_ACTIVATION_CODE) QAndroidJniObject::callStaticMethod("net/gcompris/GComprisActivity", "checkPayment"); #endif } static void bought(JNIEnv *, jclass /*clazz*/, jboolean b) { ApplicationSettings::getInstance()->bought(b); } static JNINativeMethod methods[] = { {"bought", "(Z)V", (void *)bought} }; bool ApplicationInfo::requestAudioFocus() const { qDebug() << "requestAudioFocus"; return QAndroidJniObject::callStaticMethod("net/gcompris/GComprisActivity", "requestAudioFocus"); } void ApplicationInfo::abandonAudioFocus() const { QAndroidJniObject::callStaticMethod("net/gcompris/GComprisActivity", "abandonAudioFocus"); } jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_4) != JNI_OK) return JNI_FALSE; jclass clazz = env->FindClass("net/gcompris/GComprisActivity"); if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) return JNI_FALSE; return JNI_VERSION_1_4; } void ApplicationInfo::setRequestedOrientation(int orientation) { QAndroidJniObject activity = QtAndroid::androidActivity(); activity.callMethod("setRequestedOrientation", "(I)V", orientation); } int ApplicationInfo::getRequestedOrientation() { QAndroidJniObject activity = QtAndroid::androidActivity(); jint orientation = activity.callMethod("getRequestedOrientation"); return orientation; } +void ApplicationInfo::setKeepScreenOn(bool value) +{ + QAndroidJniObject activity = QtAndroid::androidActivity(); + activity.callMethod("setKeepScreenOn", "(Z)V", value); +} diff --git a/src/core/ApplicationInfo.h b/src/core/ApplicationInfo.h index 72fa057bb..327d06cbd 100644 --- a/src/core/ApplicationInfo.h +++ b/src/core/ApplicationInfo.h @@ -1,392 +1,400 @@ /* GCompris - ApplicationSettingsDefault.cpp * * Copyright (C) 2014-2015 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef APPLICATIONINFO_H #define APPLICATIONINFO_H #include #include "ApplicationSettings.h" #include #include #include #include #include class QQuickWindow; /** * @class ApplicationInfo * @short A general purpose singleton that exposes miscellaneous native * functions to the QML layer. * @ingroup infrastructure */ class ApplicationInfo : public QObject { Q_OBJECT Q_ENUMS(Platform) /** * Width of the application viewport. */ Q_PROPERTY(int applicationWidth READ applicationWidth WRITE setApplicationWidth NOTIFY applicationWidthChanged) /** * Platform the application is currently running on. */ Q_PROPERTY(Platform platform READ platform CONSTANT) /** * Whether the application is currently running on a mobile platform. * * Mobile platforms are Android, Ios (not supported yet), * Blackberry (not supported) */ Q_PROPERTY(bool isMobile READ isMobile CONSTANT) /** * Whether the current platform supports fragment shaders. * * This flag is used in some core modules to selectively deactivate * particle effects which cause crashes on some Android devices. * * cf. https://bugreports.qt.io/browse/QTBUG-44194 * * For now always set to false, to prevent crashes. */ Q_PROPERTY(bool hasShader READ hasShader CONSTANT) /** * Whether currently in portrait mode, on mobile platforms. * * Based on viewport geometry. */ Q_PROPERTY(bool isPortraitMode READ isPortraitMode WRITE setIsPortraitMode NOTIFY portraitModeChanged) /** * Ratio factor used for scaling of sizes on high-dpi devices. * * Must be used by activities as a scaling factor to all pixel values. */ Q_PROPERTY(qreal ratio READ ratio NOTIFY ratioChanged) /** * Ratio factor used for font scaling. * * On some low-dpi Android devices with high res (e.g. Galaxy Tab 4) the * fonts in Text-like elements appear too small with respect to the other * graphics -- also if we are using font.pointSize. * * For these cases we calculate a fontRatio in ApplicationInfo that takes * dpi information into account, as proposed on * http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio * * GCText applies this factor automatically on its new fontSize property. */ Q_PROPERTY(qreal fontRatio READ fontRatio NOTIFY fontRatioChanged) /** * Short (2-letter) locale string of the currently active language. */ Q_PROPERTY(QString localeShort READ localeShort) /** * GCompris version string (compile time). */ Q_PROPERTY(QString GCVersion READ GCVersion CONSTANT) /** * Qt version string (runtime). */ Q_PROPERTY(QString QTVersion READ QTVersion CONSTANT) /** * Audio codec used for voices resources. * * This is determined at compile time (ogg for free platforms, aac on * MacOSX and IOS). */ Q_PROPERTY(QString CompressedAudio READ CompressedAudio CONSTANT) /** * Download allowed * * This is determined at compile time. If set to NO GCompris will * never download anything. */ Q_PROPERTY(bool isDownloadAllowed READ isDownloadAllowed CONSTANT) public: /** * Known host platforms. */ enum Platform { Linux, /**< Linux (except Android) */ Windows, /**< Windows */ MacOSX, /**< MacOSX */ Android, /**< Android */ Ios, /**< IOS (not supported) */ Blackberry, /**< Blackberry (not supported) */ SailfishOS /**< SailfishOS */ }; /** * Registers singleton in the QML engine. * * It is not recommended to create a singleton of Qml Singleton registered * object but we could not found a better way to let us access ApplicationInfo * on the C++ side. All our test shows that it works. */ static void init(); /** * Returns an absolute and platform independent path to the passed @p file * * @param file A relative filename. * @returns Absolute path to the file. */ static QString getFilePath(const QString &file); /** * Returns the short locale name for the passed @p locale. * * Handles also 'system' (GC_DEFAULT_LOCALE) correctly which resolves to * QLocale::system().name(). * * @param locale A locale string of the form \_\ * @returns A short locale string of the form \ */ static QString localeShort(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); } // Can't use left(2) because of Asturian where there are 3 chars return _locale.left(_locale.indexOf('_')); } /** * Returns a locale string that can be used in voices filenames. * * @param locale A locale string of the form \_\ * @returns A locale string as used in voices filenames. */ Q_INVOKABLE QString getVoicesLocale(const QString &locale); /** * Request GCompris to take the Audio Focus at the system level. * * On systems that support it, it will mute a running audio player. */ Q_INVOKABLE bool requestAudioFocus() const; /** * Abandon the Audio Focus. * * On systems that support it, it will let an audio player start again. */ Q_INVOKABLE void abandonAudioFocus() const; /** * Return the platform specific path for storing data shared between apps * * On Android: /storage/emulated/0/GCompris (>= Android 4.2), * /storage/sdcard0/GCompris (< Android 4.2) * On Linux: $HOME/local/share/GCompris */ Q_INVOKABLE QString getSharedWritablePath() const; /// @cond INTERNAL_DOCS static ApplicationInfo *getInstance() { if(!m_instance) { m_instance = new ApplicationInfo(); } return m_instance; } static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static void setWindow(QQuickWindow *window); explicit ApplicationInfo(QObject *parent = 0); ~ApplicationInfo(); int applicationWidth() const { return m_applicationWidth; } void setApplicationWidth(const int newWidth); Platform platform() const { return m_platform; } bool isPortraitMode() const { return m_isPortraitMode; } void setIsPortraitMode(const bool newMode); bool isMobile() const { return m_isMobile; } bool hasShader() const { return false; } qreal ratio() const { return m_ratio; } qreal fontRatio() const { return m_fontRatio; } QString localeShort() const { return localeShort( ApplicationSettings::getInstance()->locale() ); } static QString GCVersion() { return VERSION; } static QString QTVersion() { return qVersion(); } static QString CompressedAudio() { return COMPRESSED_AUDIO; } static bool isDownloadAllowed() { return QString(DOWNLOAD_ALLOWED) == "ON"; } /** * Returns the native screen orientation. * * Wraps QScreen::nativeOrientation: The native orientation of the screen * is the orientation where the logo sticker of the device appears the * right way up, or Qt::PrimaryOrientation if the platform does not support * this functionality. * * The native orientation is a property of the hardware, and does not change */ Q_INVOKABLE Qt::ScreenOrientation getNativeOrientation(); /** * Change the desired orientation of the application. * * Android specific function, cf. http://developer.android.com/reference/android/app/Activity.html#setRequestedOrientation(int) * * @param orientation Desired orientation of the application. For possible * values cf. http://developer.android.com/reference/android/content/pm/ActivityInfo.html#screenOrientation . * Some useful values: * - -1: SCREEN_ORIENTATION_UNSPECIFIED * - 0: SCREEN_ORIENTATION_LANDSCAPE: forces landscape * - 1: SCREEN_ORIENTATION_PORTRAIT: forces portrait * - 5: SCREEN_ORIENTATION_NOSENSOR: forces native * orientation mode on each device (portrait on * smartphones, landscape on tablet) * - 14: SCREEN_ORIENTATION_LOCKED: lock current orientation */ Q_INVOKABLE void setRequestedOrientation(int orientation); /** * Query the desired orientation of the application. * * @sa setRequestedOrientation */ Q_INVOKABLE int getRequestedOrientation(); /** * Checks whether a sensor type from the QtSensor module is supported on * the current platform. * * @param sensorType Classname of a sensor from the QtSensor module * to be checked (e.g. "QTiltSensor"). */ Q_INVOKABLE bool sensorIsSupported(const QString& sensorType); + /** + * Toggles activation of screensaver on android + * + * @param value Whether screensaver should be disabled (true) or + * enabled (false). + */ + Q_INVOKABLE void setKeepScreenOn(bool value); + /// @endcond protected slots: /** * Returns the resource root-path used for GCompris resources. */ QString getResourceDataPath(); /** * Returns an absolute path to a language specific sound/voices file. If * the file is already absolute only the token replacement is applied. * * @param file A templated relative path to a language specific file. Any * occurrence of the '$LOCALE' placeholder will be replaced by * the currently active locale string. * Any occurrence of '$CA' placeholder will be replaced by * the current compressed audio format ('ogg' or 'aac). * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' * @returns An absolute path to the corresponding resource file. */ Q_INVOKABLE QString getAudioFilePath(const QString &file); /** * Returns an absolute path to a language specific sound/voices file. If * the file is already absolute only the token replacement is applied. * * @param file A templated relative path to a language specific file. Any * occurrence of the '$LOCALE' placeholder will be replaced by * the currently active locale string. * Any occurrence of '$CA' placeholder will be replaced by * the current compressed audio format ('ogg' or 'aac). * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' * @param locale the locale for which to get this audio file * @returns An absolute path to the corresponding resource file. */ Q_INVOKABLE QString getAudioFilePathForLocale(const QString &file, const QString &localeName); /** * Returns an absolute path to a language specific resource file. * * Generalization of getAudioFilePath(). * @sa getAudioFilePath */ Q_INVOKABLE QString getLocaleFilePath(const QString &file); /** * @returns A list of systems-fonts that should be excluded from font * selection. */ Q_INVOKABLE QStringList getSystemExcludedFonts(); /** * @returns A list of fonts contained in the fonts resources. */ Q_INVOKABLE QStringList getFontsFromRcc(); /** * Stores a screenshot in the passed @p file. * * @param file Absolute destination filename. */ Q_INVOKABLE void screenshot(const QString &file); void notifyPortraitMode(); Q_INVOKABLE void notifyFullscreenChanged(); protected: qreal getSizeWithRatio(const qreal height) { return ratio() * height; } signals: void applicationWidthChanged(); void portraitModeChanged(); void ratioChanged(); void fontRatioChanged(); void applicationSettingsChanged(); void fullscreenChanged(); private: static ApplicationInfo *m_instance; int m_applicationWidth; Platform m_platform; QQmlPropertyMap *m_constants; bool m_isPortraitMode; bool m_isMobile; qreal m_ratio; qreal m_fontRatio; // Symbols fonts that user can't see QStringList m_excludedFonts; QStringList m_fontsFromRcc; static QQuickWindow *m_window; }; #endif // APPLICATIONINFO_H diff --git a/src/core/ApplicationInfoDefault.cpp b/src/core/ApplicationInfoDefault.cpp index c91d51e6b..72724a204 100644 --- a/src/core/ApplicationInfoDefault.cpp +++ b/src/core/ApplicationInfoDefault.cpp @@ -1,41 +1,46 @@ /* GCompris - ApplicationInfoDefault.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "ApplicationInfo.h" bool ApplicationInfo::requestAudioFocus() const { return true; } void ApplicationInfo::abandonAudioFocus() const { } void ApplicationInfo::setRequestedOrientation(int orientation) { Q_UNUSED(orientation); } int ApplicationInfo::getRequestedOrientation() { return -1; } + +void ApplicationInfo::setKeepScreenOn(bool value) +{ + Q_UNUSED(value); +}