in niap-cc/Permissions/Tester/app/src/main/java/com/android/certifications/niap/permissions/RuntimePermissionTester.java [116:448]
public RuntimePermissionTester(TestConfiguration configuration, Activity activity) {
super(configuration, activity);
mWallpaperManager = (WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE);
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
mPermissionTasks = new HashMap<>();
// android.permission.ACCEPT_HANDOVER - requires an active call which is then handed over
// to the app.
mPermissionTasks.put(BODY_SENSORS, new PermissionTest(false, () -> {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE)) {
throw new BypassTestException(
"A heard rate monitor is not available to run this test");
}
Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
if (sensor == null) {
throw new SecurityException(
"The heart rate sensor feature is available, but a null sensor was "
+ "returned");
}
}));
mPermissionTasks.put(CALL_PHONE, new PermissionTest(false, () -> {
mTelecomManager.endCall();
}));
// android.permission.GET_ACCOUNTS - is no longer used.
// android.permission.PROCESS_OUTGOING_CALLS requires a call to be placed to redirect /
// abort the call.
mPermissionTasks.put(READ_CALENDAR, new PermissionTest(false, () -> {
mContentResolver.query(CalendarAlerts.CONTENT_URI, null, null, null, null);
}));
mPermissionTasks.put(READ_CALL_LOG, new PermissionTest(false, () -> {
CallLog.Calls.getLastOutgoingCall(mContext);
}));
// android.permission.READ_CELL_BROADCASTS is a hidden runtime permission, but even with
// permission granted an app must also be on a whitelist to access the provider.
mPermissionTasks.put(READ_CONTACTS, new PermissionTest(false, () -> {
mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
}));
mPermissionTasks.put(READ_EXTERNAL_STORAGE, new PermissionTest(false, () -> {
mWallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM);
}));
mPermissionTasks.put(READ_PHONE_STATE, new PermissionTest(false, () -> {
mTelephonyManager.getCarrierConfig();
}));
mPermissionTasks.put(READ_SMS, new PermissionTest(false, () -> {
mContentResolver.query(Telephony.Sms.Inbox.CONTENT_URI, null, null, null, null);
}));
// android.permission.RECEIVE_MMS requires receipt of an MMS.
// android.permission.RECEIVE_SMS requires receipt of an SMS.
// android.permission.RECEIVE_WAP_PUSH requires receipt of WAP push messages.
mPermissionTasks.put(SEND_SMS, new PermissionTest(false, () -> {
String deviceNumber = null;
if (!isPermissionGranted(READ_PHONE_NUMBERS)) {
throw new BypassTestException(
"This test requires the READ_PHONE_NUMBERS permission to obtain the "
+ "device number");
}
try {
deviceNumber = mTelephonyManager.getLine1Number();
} catch (SecurityException e) {
// A SecurityException caught here indicates this app does not have the proper
// permission to obtain the device number; since the test requires the device
// number it should be skipped below.
}
if (deviceNumber == null) {
throw new BypassTestException(
"The device number could not be obtained to verify SEND_SMS");
}
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(deviceNumber, null, "Test message to verify SEND_SMS", null,
null);
}));
// android.permission.USE_SIP only guards methods that return null / false if the permission
// is not granted; no way to distinguish between permission being granted or not.
mPermissionTasks.put(WRITE_CALENDAR, new PermissionTest(false, () -> {
ContentValues values = new ContentValues();
values.put(Events.DTSTART, System.currentTimeMillis());
values.put(Events.DTEND, System.currentTimeMillis());
values.put(Events.TITLE, "Test Calendar Entry");
values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
values.put(Events.CALENDAR_ID, 1);
mContentResolver.insert(Events.CONTENT_URI, values);
}));
mPermissionTasks.put(WRITE_CALL_LOG, new PermissionTest(false, () -> {
ContentValues values = new ContentValues();
values.put(CallLog.Calls.NUMBER, "520-555-1234");
values.put(CallLog.Calls.DATE, System.currentTimeMillis());
values.put(CallLog.Calls.DURATION, 0);
values.put(CallLog.Calls.TYPE, Calls.OUTGOING_TYPE);
mContentResolver.insert(Calls.CONTENT_URI, values);
}));
mPermissionTasks.put(WRITE_CONTACTS, new PermissionTest(false, () -> {
Account[] accounts = mAccountManager.getAccounts();
if (accounts.length == 0) {
throw new BypassTestException(
"This permission requires an account with contacts");
} else {
Account account = accounts[0];
ContentValues values = new ContentValues();
values.put(RawContacts.ACCOUNT_TYPE, account.type);
values.put(RawContacts.ACCOUNT_NAME, account.name);
Uri rawContactUri = mContentResolver.insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
values.put(StructuredName.DISPLAY_NAME, "Test Contact");
mContentResolver.insert(Data.CONTENT_URI, values);
}
}));
mPermissionTasks.put(WRITE_EXTERNAL_STORAGE,
new PermissionTest(false, Build.VERSION_CODES.P, Build.VERSION_CODES.Q, () -> {
// Due to scoped storage apps targeting API level 30+ no longer get any
// additional access with the WRITE_EXTERNAL_STORAGE permission; for details see
// https://developer.android.com/preview/privacy/storage#permissions-target-11.
try {
File documentsDirectory = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS);
if (!documentsDirectory.exists() && !documentsDirectory.mkdir()) {
throw new SecurityException(
"Could not create the directory "
+ documentsDirectory.getAbsolutePath());
}
File file = new File(documentsDirectory, "test_file.out");
if (!file.createNewFile()) {
throw new SecurityException(
"Could not create the temporary file "
+ file.getAbsolutePath());
}
;
file.delete();
} catch (IOException e) {
// If the permission is not granted then this could fail with a
// 'Permission denied' IOException instead of a SecurityException.
String message = e.getMessage();
if (message != null && message.contains("Permission denied")) {
throw new SecurityException(e);
} else {
throw new UnexpectedPermissionTestFailureException(e);
}
}
}));
mPermissionTasks.put(ADD_VOICEMAIL, new PermissionTest(false, () -> {
ContentValues values = new ContentValues();
values.put(Voicemails.NUMBER, "520-555-1234");
values.put(Voicemails.DATE, System.currentTimeMillis());
values.put(Voicemails.DURATION, 10);
values.put(Voicemails.NEW, 0);
values.put(Voicemails.TRANSCRIPTION, "Testing ADD_VOICEMAIL");
values.put(Voicemails.IS_READ, 0);
values.put(Voicemails.HAS_CONTENT, 0);
values.put(Voicemails.SOURCE_DATA, "1234");
values.put(Voicemails.BACKED_UP, 0);
values.put(Voicemails.RESTORED, 0);
values.put(Voicemails.ARCHIVED, 0);
values.put(Voicemails.IS_OMTP_VOICEMAIL, 0);
values.put(Voicemails.SOURCE_PACKAGE, mPackageName);
Uri voicemailUri = Uri.parse(
Voicemails.CONTENT_URI + "?source_package=" + mPackageName);
mContentResolver.insert(voicemailUri, values);
}));
mPermissionTasks.put(ACCESS_COARSE_LOCATION, new PermissionTest(false, () -> {
mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}));
mPermissionTasks.put(ACCESS_FINE_LOCATION, new PermissionTest(false, () -> {
mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}));
mPermissionTasks.put(CAMERA, new PermissionTest(false, () -> {
try {
String[] cameras = mCameraManager.getCameraIdList();
if (cameras.length == 0) {
throw new BypassTestException(
"No cameras were found on this device to perform this test");
}
CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
@Override
public void onDisconnected(CameraDevice camera) {
mLogger.logDebug("onDisconnected: camera = " + camera);
}
@Override
public void onError(CameraDevice camera, int error) {
mLogger.logDebug("onError: camera = " + camera + ", error = " + error);
}
@Override
public void onOpened(CameraDevice camera) {
mLogger.logDebug("onOpened: camera = " + camera);
camera.close();
}
};
mCameraManager.openCamera(cameras[0], cameraCallback,
new Handler(Looper.getMainLooper()));
} catch (CameraAccessException e) {
throw new UnexpectedPermissionTestFailureException(e);
}
}));
mPermissionTasks.put(READ_PHONE_NUMBERS, new PermissionTest(false, () -> {
mTelephonyManager.getLine1Number();
}));
mPermissionTasks.put(RECORD_AUDIO, new PermissionTest(false, () -> {
try {
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
String fileName = mContext.getFilesDir() + "/test_record_audio.out";
recorder.setOutputFile(new File(fileName));
recorder.setOutputFormat(OutputFormat.THREE_GPP);
recorder.setAudioEncoder(AudioEncoder.AMR_NB);
recorder.prepare();
} catch (RuntimeException e) {
// The MediaRecorder framework throws a RuntimeException when the RECORD_AUDIO
// permission is not granted to the calling app.
throw new SecurityException(e);
} catch (IOException ioe) {
throw new UnexpectedPermissionTestFailureException(ioe);
}
}));
mPermissionTasks.put(ANSWER_PHONE_CALLS, new PermissionTest(false, () -> {
mTelecomManager.endCall();
}));
// New permissions for Q
mPermissionTasks.put(Manifest.permission.ACTIVITY_RECOGNITION,
new PermissionTest(false, Build.VERSION_CODES.Q, () -> {
Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
if (sensor == null) {
throw new BypassTestException(
"The step counter sensor is not available to execute this test");
}
SensorEventListener listener = new SensorEventListener() {
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
mLogger.logDebug(
"onAccuracyChanged: sensor = " + sensor + ", accuracy = "
+ accuracy);
}
@Override
public void onSensorChanged(SensorEvent event) {
mLogger.logDebug("onSensorChanged: event = " + event);
}
};
boolean listenerRegistered = mSensorManager.registerListener(listener,
sensor,
SensorManager.SENSOR_DELAY_NORMAL);
if (!listenerRegistered) {
throw new SecurityException(
"Failed to register a listener for the STEP_COUNTER sensor");
}
mSensorManager.unregisterListener(listener);
}));
mPermissionTasks.put(Manifest.permission.ACCESS_MEDIA_LOCATION,
new PermissionTest(false, Build.VERSION_CODES.Q, () -> {
String selection = MediaStore.Images.Media.MIME_TYPE + "='image/jpeg'" + " OR "
+ MediaStore.Images.Media.MIME_TYPE + "='image/jpg'";
Cursor cursor = mContentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media.DESCRIPTION,
MediaStore.Images.Media._ID}, selection, null, null);
if (cursor == null) {
throw new UnexpectedPermissionTestFailureException(
"Unable to obtain an image to test ACCESS_MEDIA_LOCATION");
}
if (cursor.getCount() == 0) {
if (isPermissionGranted(READ_EXTERNAL_STORAGE)) {
throw new UnexpectedPermissionTestFailureException(
"Unable to obtain an image with location data to verify "
+ "ACCESS_MEDIA_LOCATION");
} else {
throw new BypassTestException(
"READ_EXTERNAL_STORAGE not granted; unable to obtain "
+ "images");
}
}
while (cursor.moveToNext()) {
int columnIndex = cursor.getColumnIndex(MediaStore.Images.Media._ID);
String elementId = cursor.getString(columnIndex);
Uri elementUri = Uri.parse(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + elementId);
try (InputStream inputStream = mContentResolver.openInputStream(
elementUri)) {
ExifInterface exif = new ExifInterface(inputStream);
float[] latLong = new float[2];
exif.getLatLong(latLong);
// Not all images will have location data, ensure all images are
// tested before reporting an error.
if (latLong[0] == 0.0f && latLong[1] == 0.0f) {
continue;
} else {
return;
}
} catch (IOException e) {
mLogger.logError("Caught an IOException reading the image: ", e);
}
}
throw new SecurityException(
"Unable to obtain the location data from any image");
}));
}