Skip to content

Commit 9cb362e

Browse files
committed
CATROID-1607 Fix bluetooth for Android API 31 onward
Bluetooth now works again across all devices. Depending on the device running the app either the new or the legacy bluetooth permissions are used. This is achieved by streamlining/simplifying the permission handling process for all features that require bluetooth permissions. And a little bit of boyscouting through the code.
1 parent 6dc83b6 commit 9cb362e

File tree

9 files changed

+170
-71
lines changed

9 files changed

+170
-71
lines changed

catroid/src/androidTest/java/org/catrobat/catroid/test/content/bricks/BrickPermissionTest.java

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import org.catrobat.catroid.formulaeditor.Formula;
4545
import org.catrobat.catroid.formulaeditor.FormulaElement;
4646
import org.catrobat.catroid.formulaeditor.UserVariable;
47-
import org.catrobat.catroid.stage.StageResourceHolder;
47+
import org.catrobat.catroid.ui.runtimepermissions.BrickResourcesToRuntimePermissions;
4848
import org.junit.Before;
4949
import org.junit.Test;
5050
import org.junit.runner.RunWith;
@@ -53,11 +53,16 @@
5353
import java.util.Arrays;
5454
import java.util.List;
5555

56+
import android.os.Build;
57+
5658
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
5759
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
5860
import static android.Manifest.permission.ACCESS_WIFI_STATE;
5961
import static android.Manifest.permission.BLUETOOTH;
6062
import static android.Manifest.permission.BLUETOOTH_ADMIN;
63+
import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
64+
import static android.Manifest.permission.BLUETOOTH_CONNECT;
65+
import static android.Manifest.permission.BLUETOOTH_SCAN;
6166
import static android.Manifest.permission.CAMERA;
6267
import static android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE;
6368
import static android.Manifest.permission.CHANGE_WIFI_STATE;
@@ -70,29 +75,87 @@
7075
public class BrickPermissionTest {
7176

7277
private static Brick brickWithGPS =
73-
new SetVariableBrick(new Formula(new FormulaElement(FormulaElement.ElementType.SENSOR, "LONGITUDE", null)), new UserVariable("x"));
78+
new SetVariableBrick(
79+
new Formula(new FormulaElement(
80+
FormulaElement.ElementType.SENSOR, "LONGITUDE", null)),
81+
new UserVariable("x"));
7482

7583
@Parameterized.Parameters(name = "{0}")
7684
public static Iterable<Object[]> data() {
7785
return Arrays.asList(new Object[][] {
78-
{"CameraBrick", new Brick[]{new CameraBrick()}, new String[]{CAMERA}},
79-
{"LegoNxtMotorMoveBrick", new Brick[]{new LegoNxtMotorMoveBrick()}, new String[]{BLUETOOTH_ADMIN, BLUETOOTH}},
80-
{"CameraBrick + LegoNxtMotorTurnAngleBrick", new Brick[]{new CameraBrick(), new LegoNxtMotorTurnAngleBrick()}, new String[]{CAMERA, BLUETOOTH_ADMIN, BLUETOOTH}},
81-
{"AskSpeechBrick", new Brick[]{new AskSpeechBrick()}, new String[]{RECORD_AUDIO}},
82-
{"WhenGamepadButtonBrick", new Brick[]{new WhenGamepadButtonBrick(new WhenGamepadButtonScript())}, new String[]{CHANGE_WIFI_MULTICAST_STATE, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE}},
83-
{"WhenNfcBrick", new Brick[]{new WhenNfcBrick()}, new String[]{NFC}},
84-
{"WhenNfcBrick + GPS", new Brick[]{new WhenNfcBrick(), brickWithGPS}, new String[]{NFC, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}},
85-
{"Brick With GPS Formula", new Brick[]{brickWithGPS}, new String[]{ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}}
86+
{
87+
"CameraBrick",
88+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
89+
new Brick[]{new CameraBrick()},
90+
new String[]{CAMERA}
91+
},
92+
{
93+
"LegoNxtMotorMoveBrick",
94+
Build.VERSION_CODES.R,
95+
new Brick[]{new LegoNxtMotorMoveBrick()},
96+
new String[]{BLUETOOTH_ADMIN, BLUETOOTH}
97+
},
98+
{
99+
"LegoNxtMotorMoveBrick",
100+
Build.VERSION_CODES.S,
101+
new Brick[]{new LegoNxtMotorMoveBrick()},
102+
new String[]{BLUETOOTH_ADVERTISE, BLUETOOTH_SCAN, BLUETOOTH_CONNECT}
103+
},
104+
{
105+
"CameraBrick + LegoNxtMotorTurnAngleBrick",
106+
Build.VERSION_CODES.R,
107+
new Brick[]{new CameraBrick(), new LegoNxtMotorTurnAngleBrick()},
108+
new String[]{CAMERA, BLUETOOTH_ADMIN, BLUETOOTH}
109+
},
110+
{
111+
"CameraBrick + LegoNxtMotorTurnAngleBrick",
112+
Build.VERSION_CODES.S,
113+
new Brick[]{new CameraBrick(), new LegoNxtMotorTurnAngleBrick()},
114+
new String[]{CAMERA, BLUETOOTH_ADVERTISE, BLUETOOTH_SCAN, BLUETOOTH_CONNECT}
115+
},
116+
{
117+
"AskSpeechBrick",
118+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
119+
new Brick[]{new AskSpeechBrick()},
120+
new String[]{RECORD_AUDIO}
121+
},
122+
{
123+
"WhenGamepadButtonBrick",
124+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
125+
new Brick[]{new WhenGamepadButtonBrick(new WhenGamepadButtonScript())},
126+
new String[]{CHANGE_WIFI_MULTICAST_STATE, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE}
127+
},
128+
{
129+
"WhenNfcBrick",
130+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
131+
new Brick[]{new WhenNfcBrick()},
132+
new String[]{NFC}
133+
},
134+
{
135+
"WhenNfcBrick + GPS",
136+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
137+
new Brick[]{new WhenNfcBrick(), brickWithGPS},
138+
new String[]{NFC, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}
139+
},
140+
{
141+
"Brick With GPS Formula",
142+
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
143+
new Brick[]{brickWithGPS},
144+
new String[]{ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}
145+
}
86146
});
87147
}
88148

89149
@Parameterized.Parameter
90150
public String name;
91151

92152
@Parameterized.Parameter(1)
93-
public Brick[] bricks;
153+
public int apiLevel;
94154

95155
@Parameterized.Parameter(2)
156+
public Brick[] bricks;
157+
158+
@Parameterized.Parameter(3)
96159
public String[] expectedPermission;
97160

98161
Script script;
@@ -168,7 +231,9 @@ public void testDoubleNestedPermission() {
168231
}
169232

170233
private void checkProjectRuntimePermissions() {
171-
List<String> requestedString = StageResourceHolder.getProjectsRuntimePermissionList();
234+
List<String> requestedString = BrickResourcesToRuntimePermissions.translate(
235+
ProjectManager.getInstance().getCurrentProject().getRequiredResources(),
236+
apiLevel);
172237
assertTrue(requestedString.containsAll(Arrays.asList(expectedPermission)));
173238
assertTrue(Arrays.asList(expectedPermission).containsAll(requestedString));
174239
}

catroid/src/main/AndroidManifest.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@
5555
<uses-feature
5656
android:name="android.hardware.bluetooth"
5757
android:required="false" />
58-
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
59-
<uses-permission android:name="android.permission.BLUETOOTH" />
60-
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
58+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
59+
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
60+
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
6161
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
6262
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
63+
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
6364
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
6465
tools:ignore="ProtectedPermissions" />
6566

catroid/src/main/java/org/catrobat/catroid/content/Project.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public void removeScene(Scene scene) {
165165
}
166166

167167
public boolean hasScene() {
168-
return (sceneList.size() > 0);
168+
return !sceneList.isEmpty();
169169
}
170170

171171
public Scene getDefaultScene() {
@@ -337,7 +337,7 @@ public UserVariable getMultiplayerVariable(String name) {
337337
}
338338

339339
public boolean hasMultiplayerVariables() {
340-
return multiplayerVariables.size() > 0;
340+
return !multiplayerVariables.isEmpty();
341341
}
342342

343343
public boolean addMultiplayerVariable(UserVariable multiplayerVariable) {
@@ -422,6 +422,11 @@ public Brick.ResourcesSet getRequiredResources() {
422422
if (isCastProject()) {
423423
resourcesSet.add(Brick.CAST_REQUIRED);
424424
}
425+
426+
if (hasMultiplayerVariables()) {
427+
resourcesSet.add(Brick.BLUETOOTH_MULTIPLAYER);
428+
}
429+
425430
ActionFactory physicsActionFactory = new ActionPhysicsFactory();
426431
ActionFactory actionFactory = new ActionFactory();
427432

@@ -436,6 +441,7 @@ public Brick.ResourcesSet getRequiredResources() {
436441
}
437442
}
438443
}
444+
439445
return resourcesSet;
440446
}
441447

catroid/src/main/java/org/catrobat/catroid/content/bricks/Brick.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ interface FormulaField extends Serializable {
4545
}
4646

4747
enum BrickField implements FormulaField {
48-
COLOR, COLOR_CHANGE, BRIGHTNESS, BRIGHTNESS_CHANGE, X_POSITION, Y_POSITION, X_POSITION_CHANGE, Y_POSITION_CHANGE,
49-
TRANSPARENCY, TRANSPARENCY_CHANGE, SIZE, SIZE_CHANGE, VOLUME, VOLUME_CHANGE, X_DESTINATION, Y_DESTINATION, STEPS,
50-
DURATION_IN_SECONDS, DEGREES, TURN_RIGHT_DEGREES, TURN_LEFT_DEGREES, TIME_TO_WAIT_IN_SECONDS, VARIABLE,
48+
COLOR, COLOR_CHANGE, BRIGHTNESS, BRIGHTNESS_CHANGE, X_POSITION, Y_POSITION,
49+
X_POSITION_CHANGE, Y_POSITION_CHANGE, TRANSPARENCY, TRANSPARENCY_CHANGE, SIZE, SIZE_CHANGE,
50+
VOLUME, VOLUME_CHANGE, X_DESTINATION, Y_DESTINATION, STEPS, DURATION_IN_SECONDS, DEGREES,
51+
TURN_RIGHT_DEGREES, TURN_LEFT_DEGREES, TIME_TO_WAIT_IN_SECONDS, VARIABLE,
5152

52-
VARIABLE_CHANGE, WEB_REQUEST, LOOK_REQUEST, LOOK_NEW, LOOK_COPY, BACKGROUND_REQUEST, WRITE_FILENAME,
53-
READ_FILENAME, TEMPO, HORIZONTAL_FLEXIBILITY, VERTICAL_FLEXIBILITY,
53+
VARIABLE_CHANGE, WEB_REQUEST, LOOK_REQUEST, LOOK_NEW, LOOK_COPY, BACKGROUND_REQUEST,
54+
WRITE_FILENAME, READ_FILENAME, TEMPO, HORIZONTAL_FLEXIBILITY, VERTICAL_FLEXIBILITY,
5455
TEMPO_CHANGE, BEATS_TO_PAUSE, NOTE_TO_PLAY, BEATS_TO_PLAY_NOTE, OPEN_URL, PLAY_DRUM,
5556
PLAY_SOUND_AT,
5657

@@ -66,19 +67,22 @@ enum BrickField implements FormulaField {
6667
LEGO_EV3_FREQUENCY, LEGO_EV3_DURATION_IN_SECONDS, LEGO_EV3_VOLUME,
6768
LEGO_EV3_SPEED, LEGO_EV3_POWER, LEGO_EV3_PERIOD_IN_SECONDS, LEGO_EV3_DEGREES,
6869

69-
DRONE_TIME_TO_FLY_IN_SECONDS, LIST_ADD_ITEM, LIST_DELETE_ITEM, INSERT_ITEM_INTO_USERLIST_VALUE,
70-
INSERT_ITEM_INTO_USERLIST_INDEX, REPLACE_ITEM_IN_USERLIST_VALUE, REPLACE_ITEM_IN_USERLIST_INDEX, DRONE_POWER_IN_PERCENT,
70+
DRONE_TIME_TO_FLY_IN_SECONDS, LIST_ADD_ITEM, LIST_DELETE_ITEM,
71+
INSERT_ITEM_INTO_USERLIST_VALUE, INSERT_ITEM_INTO_USERLIST_INDEX,
72+
REPLACE_ITEM_IN_USERLIST_VALUE, REPLACE_ITEM_IN_USERLIST_INDEX, DRONE_POWER_IN_PERCENT,
7173

7274
DRONE_ALTITUDE_LIMIT, DRONE_VERTICAL_SPEED_MAX, DRONE_ROTATION_MAX, DRONE_TILT_ANGLE,
7375

74-
JUMPING_SUMO_SPEED, JUMPING_SUMO_TIME_TO_DRIVE_IN_SECONDS, JUMPING_SUMO_VOLUME, JUMPING_SUMO_ROTATE,
76+
JUMPING_SUMO_SPEED, JUMPING_SUMO_TIME_TO_DRIVE_IN_SECONDS, JUMPING_SUMO_VOLUME,
77+
JUMPING_SUMO_ROTATE,
7578

7679
PHIRO_SPEED, PHIRO_DURATION_IN_SECONDS, PHIRO_LIGHT_RED, PHIRO_LIGHT_GREEN, PHIRO_LIGHT_BLUE,
7780

7881
PHYSICS_BOUNCE_FACTOR, PHYSICS_FRICTION, PHYSICS_GRAVITY_X, PHYSICS_GRAVITY_Y, PHYSICS_MASS,
7982
PHYSICS_VELOCITY_X, PHYSICS_VELOCITY_Y, PHYSICS_TURN_LEFT_SPEED, PHYSICS_TURN_RIGHT_SPEED,
8083

81-
ARDUINO_ANALOG_PIN_VALUE, ARDUINO_ANALOG_PIN_NUMBER, ARDUINO_DIGITAL_PIN_VALUE, ARDUINO_DIGITAL_PIN_NUMBER,
84+
ARDUINO_ANALOG_PIN_VALUE, ARDUINO_ANALOG_PIN_NUMBER, ARDUINO_DIGITAL_PIN_VALUE,
85+
ARDUINO_DIGITAL_PIN_NUMBER,
8286

8387
RASPI_DIGITAL_PIN_VALUE, RASPI_DIGITAL_PIN_NUMBER, RASPI_PWM_PERCENTAGE, RASPI_PWM_FREQUENCY,
8488

@@ -90,7 +94,8 @@ enum BrickField implements FormulaField {
9094

9195
ASSERT_LOOP_ACTUAL;
9296

93-
public static final BrickField[] EXPECTS_STRING_VALUE = {VARIABLE, NOTE, SPEAK, STRING, ASK_QUESTION,
97+
private static final BrickField[] EXPECTS_STRING_VALUE = {
98+
VARIABLE, NOTE, SPEAK, STRING, ASK_QUESTION,
9499
NFC_NDEF_MESSAGE, ASK_SPEECH_QUESTION, LIST_ADD_ITEM, INSERT_ITEM_INTO_USERLIST_VALUE,
95100
REPLACE_ITEM_IN_USERLIST_VALUE};
96101

@@ -123,10 +128,11 @@ public static boolean isUserList(BrickData field) {
123128

124129
@Retention(RetentionPolicy.SOURCE)
125130
@IntDef({TEXT_TO_SPEECH, BLUETOOTH_LEGO_NXT, PHYSICS, FACE_DETECTION,
126-
BLUETOOTH_SENSORS_ARDUINO, SOCKET_RASPI, CAMERA_FLASH, VIBRATION, BLUETOOTH_PHIRO, CAMERA_BACK, CAMERA_FRONT,
127-
SENSOR_ACCELERATION, SENSOR_INCLINATION, SENSOR_COMPASS, NFC_ADAPTER, VIDEO, SENSOR_GPS, COLLISION,
128-
BLUETOOTH_LEGO_EV3, NETWORK_CONNECTION, CAST_REQUIRED, MICROPHONE, STORAGE_WRITE, STORAGE_READ,
129-
SPEECH_RECOGNITION, TEXT_DETECTION, POSE_DETECTION, OBJECT_DETECTION})
131+
BLUETOOTH_SENSORS_ARDUINO, SOCKET_RASPI, CAMERA_FLASH, VIBRATION, BLUETOOTH_PHIRO,
132+
CAMERA_BACK, CAMERA_FRONT, SENSOR_ACCELERATION, SENSOR_INCLINATION, SENSOR_COMPASS,
133+
NFC_ADAPTER, VIDEO, SENSOR_GPS, COLLISION, BLUETOOTH_LEGO_EV3, NETWORK_CONNECTION,
134+
CAST_REQUIRED, MICROPHONE, STORAGE_WRITE, STORAGE_READ, SPEECH_RECOGNITION,
135+
TEXT_DETECTION, POSE_DETECTION, OBJECT_DETECTION, BLUETOOTH_MULTIPLAYER})
130136
@interface Resources {
131137
}
132138

@@ -158,6 +164,7 @@ public static boolean isUserList(BrickData field) {
158164
int STORAGE_WRITE = 26;
159165
int POSE_DETECTION = 27;
160166
int OBJECT_DETECTION = 28;
167+
int BLUETOOTH_MULTIPLAYER = 29;
161168

162169
class ResourcesSet extends HashSet<Integer> {
163170
@Override

catroid/src/main/java/org/catrobat/catroid/stage/StageActivity.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import android.nfc.NdefMessage;
3333
import android.nfc.NfcAdapter;
3434
import android.nfc.Tag;
35+
import android.os.Build;
3536
import android.os.Bundle;
3637
import android.os.Handler;
3738
import android.os.Looper;
@@ -415,7 +416,8 @@ public void adaptToDeniedPermissions(List<String> deniedPermissions) {
415416
for (Sprite sprite : scene.getSpriteList()) {
416417
for (Brick brick : sprite.getAllBricks()) {
417418
brick.addRequiredResources(requiredResources);
418-
List<String> requiredPermissions = BrickResourcesToRuntimePermissions.translate(requiredResources);
419+
List<String> requiredPermissions = BrickResourcesToRuntimePermissions.translate(
420+
requiredResources, Build.VERSION.SDK_INT);
419421
requiredPermissions.retainAll(deniedPermissions);
420422

421423
if (!requiredPermissions.isEmpty()) {

catroid/src/main/java/org/catrobat/catroid/stage/StageLifeCycleController.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import android.content.pm.ActivityInfo;
2727
import android.graphics.PixelFormat;
28+
import android.os.Build;
2829
import android.os.SystemClock;
2930
import android.util.Log;
3031
import android.view.SurfaceView;
@@ -116,7 +117,7 @@ static void stageCreate(final StageActivity stageActivity) {
116117
stageActivity.stageResourceHolder = new StageResourceHolder(stageActivity);
117118
MidiSoundManager.getInstance().reset();
118119

119-
List<String> requiredPermissions = getProjectsRuntimePermissionList();
120+
List<String> requiredPermissions = getProjectsRuntimePermissionList(Build.VERSION.SDK_INT);
120121
if (requiredPermissions.isEmpty()) {
121122
stageActivity.stageResourceHolder.initResources();
122123
} else {
@@ -129,7 +130,7 @@ public void task() {
129130
}
130131

131132
static void stagePause(final StageActivity stageActivity) {
132-
if (checkPermission(stageActivity, getProjectsRuntimePermissionList())) {
133+
if (checkPermission(stageActivity, getProjectsRuntimePermissionList(Build.VERSION.SDK_INT))) {
133134
if (stageActivity.nfcAdapter != null) {
134135
try {
135136
stageActivity.nfcAdapter.disableForegroundDispatch(stageActivity);
@@ -177,14 +178,15 @@ public static void stageResume(final StageActivity stageActivity) {
177178
return;
178179
}
179180

180-
if (checkPermission(stageActivity, getProjectsRuntimePermissionList())) {
181+
if (checkPermission(stageActivity,
182+
getProjectsRuntimePermissionList(Build.VERSION.SDK_INT))) {
181183
Brick.ResourcesSet resourcesSet = ProjectManager.getInstance().getCurrentProject().getRequiredResources();
182184
List<Sprite> spriteList = ProjectManager.getInstance().getCurrentlyPlayingScene().getSpriteList();
183185

184186
SensorHandler.startSensorListener(stageActivity);
185187

186188
for (Sprite sprite : spriteList) {
187-
if (sprite.getPlaySoundBricks().size() > 0) {
189+
if (!sprite.getPlaySoundBricks().isEmpty()) {
188190
stageActivity.stageAudioFocus.requestAudioFocus();
189191
break;
190192
}
@@ -203,9 +205,10 @@ public static void stageResume(final StageActivity stageActivity) {
203205
}
204206

205207
if (resourcesSet.contains(Brick.BLUETOOTH_LEGO_NXT)
208+
|| resourcesSet.contains(Brick.BLUETOOTH_LEGO_EV3)
206209
|| resourcesSet.contains(Brick.BLUETOOTH_PHIRO)
207210
|| resourcesSet.contains(Brick.BLUETOOTH_SENSORS_ARDUINO)
208-
|| ProjectManager.getInstance().getCurrentProject().hasMultiplayerVariables()) {
211+
|| resourcesSet.contains(Brick.BLUETOOTH_MULTIPLAYER)) {
209212
try {
210213
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).start();
211214
} catch (MindstormsException e) {
@@ -239,7 +242,7 @@ public static void stageResume(final StageActivity stageActivity) {
239242
}
240243

241244
static void stageDestroy(StageActivity stageActivity) {
242-
if (checkPermission(stageActivity, getProjectsRuntimePermissionList())) {
245+
if (checkPermission(stageActivity, getProjectsRuntimePermissionList(Build.VERSION.SDK_INT))) {
243246
if (stageActivity.brickDialogManager != null) {
244247
stageActivity.brickDialogManager.dismissAllDialogs();
245248
}

0 commit comments

Comments
 (0)