I have previously been testing an app on devices running Android 8, 10, 11 and it runs fine.
Compiled as TargetVersion 34
I have just tried to compile the app to a device that is running Android 12 and it will not install. I am seeing the following message ;
Installation failed due to: 'Failed to commit install session 558053483 with command cmd package install-commit 558053483. Error: -127: Package com.xxxxx.xxxxx.free.debug attempting to declare permission com.xxxxx.xxxxx.free.debug.permission.DELETE_MESSAGES in non-existing group android.permission-group.MESSAGES'
I tried building it as Release as well however I am getting the same message.
Manifest code is as follows ;
<permission
android:name="${applicationId}.permission.DELETE_MESSAGES"
android:description="@string/delete_messages_desc"
android:label="@string/delete_messages_label"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="dangerous"/>
<provider
android:name="com.xxxxx.xxxxx.external.MessageProvider"
android:authorities="${applicationId}.messageprovider"
android:exported="false"
android:grantUriPermissions="true"
android:multiprocess="true"
android:readPermission="${applicationId}.permission.READ_MESSAGES"
android:writePermission="${applicationId}.permission.DELETE_MESSAGES"/>
Did some searching but I cannot get a grasp on how to tackle this.
Thanks
UPDATE
Code is below. Not sure how much to include so this might be overkill sorry if it is.
public class MessageProvider extends ContentProvider {
public static String AUTHORITY = BuildConfig.APPLICATION_ID + ".messageprovider";
public static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
private static final String[] DEFAULT_MESSAGE_PROJECTION = new String[] {
MessageColumns._ID,
MessageColumns.SEND_DATE,
MessageColumns.SENDER,
MessageColumns.SUBJECT,
MessageColumns.PREVIEW,
MessageColumns.ACCOUNT,
MessageColumns.URI,
MessageColumns.DELETE_URI,
MessageColumns.SENDER_ADDRESS
};
private static final String[] DEFAULT_ACCOUNT_PROJECTION = new String[] {
AccountColumns.ACCOUNT_NUMBER,
AccountColumns.ACCOUNT_NAME,
};
private static final String[] UNREAD_PROJECTION = new String[] {
UnreadColumns.ACCOUNT_NAME,
UnreadColumns.UNREAD
};
private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private List<QueryHandler> queryHandlers = new ArrayList<>();
/**
* How many simultaneous cursors we can afford to expose at once
*/
Semaphore semaphore = new Semaphore(1);
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1);
@Override
public boolean onCreate() {
registerQueryHandler(new ThrottlingQueryHandler(new AccountsQueryHandler()));
registerQueryHandler(new ThrottlingQueryHandler(new MessagesQueryHandler()));
registerQueryHandler(new ThrottlingQueryHandler(new UnreadQueryHandler()));
return true;
}
public static void init() {
Timber.v("Registering content resolver notifier");
final Context context = DI.get(Context.class);
MessagingController messagingController = DI.get(MessagingController.class);
messagingController.addListener(new SimpleMessagingListener() {
@Override
public void folderStatusChanged(Account account, String folderServerId, int unreadMessageCount) {
context.getContentResolver().notifyChange(CONTENT_URI, null);
}
});
}
@Override
public String getType(Uri uri) {
Timber.v("MessageProvider/getType: %s", uri);
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Timber.v("MessageProvider/query: %s", uri);
int code = uriMatcher.match(uri);
if (code == -1) {
throw new IllegalStateException("Unrecognized URI: " + uri);
}
Cursor cursor;
try {
QueryHandler handler = queryHandlers.get(code);
cursor = handler.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (Exception e) {
Timber.e(e, "Unable to execute query for URI: %s", uri);
return null;
}
return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Timber.v("MessageProvider/delete: %s", uri);
// Note: can only delete a message
List<String> segments = uri.getPathSegments();
int accountId = Integer.parseInt(segments.get(1));
String folderServerId = segments.get(2);
String msgUid = segments.get(3);
// get account
Account myAccount = null;
for (Account account : Preferences.getPreferences(getContext()).getAccounts()) {
if (account.getAccountNumber() == accountId) {
myAccount = account;
if (!account.isAvailable(getContext())) {
Timber.w("not deleting messages because account is unavailable at the moment");
return 0;
}
}
}
if (myAccount == null) {
Timber.e("Could not find account with id %d", accountId);
}
if (myAccount != null) {
MessageReference messageReference = new MessageReference(myAccount.getUuid(), folderServerId, msgUid, null);
MessagingController controller = MessagingController.getInstance(getContext());
controller.deleteMessage(messageReference, null);
}
// FIXME return the actual number of deleted messages
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Timber.v("MessageProvider/insert: %s", uri);
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Timber.v("MessageProvider/update: %s", uri);
// TBD
return 0;
}
/**
* Register a {@link QueryHandler} to handle a certain {@link Uri} for
* {@link #query(Uri, String[], String, String[], String)}
*/
protected void registerQueryHandler(QueryHandler handler) {
if (queryHandlers.contains(handler)) {
return;
}
queryHandlers.add(handler);
int code = queryHandlers.indexOf(handler);
uriMatcher.addURI(AUTHORITY, handler.getPath(), code);
}
public static class ReverseDateComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object2, MessageInfoHolder object1) {
if (object1.compareDate == null) {
return (object2.compareDate == null ? 0 : 1);
} else if (object2.compareDate == null) {
return -1;
} else {
return object1.compareDate.compareTo(object2.compareDate);
}
}
}
protected interface QueryHandler {
/**
* The path this instance is able to respond to.
*/
String getPath();
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
throws Exception;
}
/**
* Extracts a value from an object.
*/
public interface FieldExtractor<T, K> {
K getField(T source);
}
public static class DeleteUriExtractor implements FieldExtractor<MessageInfoHolder, String> {
@Override
public String getField(MessageInfoHolder source) {
LocalMessage message = source.message;
int accountNumber = message.getAccount().getAccountNumber();
return CONTENT_URI.buildUpon()
.appendPath("delete_message")
.appendPath(Integer.toString(accountNumber))
.appendPath(message.getFolder().getServerId())
.appendPath(message.getUid())
.build()
.toString();
}
}
2