Как поток, созданный приложением, будет считаться отличным от приложения ContentProvider?

У меня есть приложение, которое при получении уведомления от ContentObserver изменения в ContentProvider, пытается запросить провайдера в фоновом потоке. Это вызывает SecurityException быть брошенным

8-10 15:54:29.577    3057-3200/com.xxxx.mobile.android.xxx W/Binder﹕ Обнаружено исключение RuntimeException из реализации заглушки Binder.
  java.lang.SecurityException: отказ в разрешении: чтение com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 требует, чтобы поставщик был экспортирован, или grantUriPermission()
на android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539)
           на android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452)
           на android.content.ContentProvider$Transport.query(ContentProvider.java:205)
           на android.content.ContentResolver.query(ContentResolver.java:478)
           на android.content.ContentResolver.query(ContentResolver.java:422)

Как поток, созданный приложением, может получить другой UID, отличный от ContentProvider?

Размещая точку останова исключения в android.content.ContentProvider я вижу это UserHandle.isSameApp(uid, mMyUid) является false а также UserHandle.isSameUser(uid, mMyUid) является true, Я также вижу, что UID провайдера составляет 10087.

3 ответа

Решение

У меня возникла та же проблема при попытке взаимодействия с моим ContentProvider в системном обратном вызове (LeScanCallback). Проблема в том, что поток обратных вызовов принадлежит системе Android, а не моему приложению, даже если код находится в моем приложении.

Передача работы от обратного вызова до одного из потоков моего приложения перед попыткой взаимодействия с моим ContentProvider успешно разрешила проблему.

Чтобы уменьшить шаблон для создания и переработки потоков (необходим для частых обратных вызовов для уменьшения накладных расходов), я использовал AndroidAnnotation's @Background аннотации к моему методу делегата (но я бы использовал Kotlin Coroutines сегодня).

Значение uid 1000 принадлежит системе Android. Многие функции Android включают передачу запросов в системный поток для обработки. Если во время этого события возникнет исключение, ошибка будет включать uid системы, а не исходный запросчик.

Для других пунктов:

UserHandle.isSameApp(uid, mMyUid) is false

UserHandle.isSameUser(uid, mMyUid) is true

Это проще всего объяснить, посмотрев на источник. На устройстве Android с многопользовательской поддержкой каждый пользователь определяется диапазоном UID. isSameApp ложно, потому что модуль идентификаторов не совпадают:

 public static final boolean isSameApp(int uid1, int uid2) {
        return getAppId(uid1) == getAppId(uid2);
}

 public static final int getAppId(int uid) {
        return uid % PER_USER_RANGE;
}

Аналогично, два идентификатора принадлежат одному и тому же пользователю, поскольку они находятся в одном диапазоне:

 public static final boolean isSameUser(int uid1, int uid2) {
        return getUserId(uid1) == getUserId(uid2);
 }

public static final int getUserId(int uid) {
        if (MU_ENABLED) {
            return uid / PER_USER_RANGE;
        } else {
            return 0;
        }
}

Обратите внимание, что эта логика имеет недостатки, поскольку означает, что все системные Android-идентификаторы (< 10000) будут считаться "принадлежащими" первому пользователю.

Также обратите внимание, что если второй пользователь установит более 1000 приложений (!), Существует вероятность того, что приложение будет ошибочно принято за системное приложение (оба uid % PER_USER_RANGE вернет 1000). Это не будет иметь большого значения, потому что сильная песочница предотвратит что-то слишком плохое.

Если Thread запускается любым компонентом приложения, у которого есть поставщик, затем вы можете получить доступ к ContentProvider без всяких SecurityException,

Я использую ContentProvider в моем приложении просто как дополнительный уровень абстракции, и я не предоставляю контент другим приложениям. Я получаю доступ к ContentProvider в фоновом режиме (не AsyncTask, но простой java.lang.Thread). Я не получаю SecurityException, Ниже приведен код из моего приложения.

AndroidManifest.xml

 <provider
    android:authorities="com.sample.provider"
    android:name="com.sample.MyProvider"
    android:exported="false" />

Основная деятельность

public void performContinue(Bundle extras){
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            String AUTHORITY = "com.sample.provider";
            Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
            Uri currentUri = BASE_URI.buildUpon().appendPath("SAMPLE_COUNT").build();
            final Cursor query = InputActivity.this.getContentResolver().query(currentUri, null, null, null, null);
            if (query != null) {
                final int count = query.getCount();
                Log.d("DEBUG","CONTENT = " + count);
            }else{
                Log.d("DEBUG","CONTENT = CURSOR NULL");
            }
        }
    });
    thread.setName("THREAD_1");
    thread.start();
}


Я, кажется, не понимаю SecurityException, В идеале мы должны использовать AsyncQueryHandler для доступа к ContentProviderпотому что это позволяет вам выполнять весь процесс выборки в фоновом потоке и будет использовать поток пользовательского интерфейса для публикации результатов в пользовательском интерфейсе. Но, увидев этот пост, я просто хотел посмотреть, смогу ли я просто использовать Thread и проверьте, смогу ли я получить к нему доступ без каких-либо исключений. Работает нормально.

Другие вопросы по тегам