I've seen strange crash reports from my app.
android.view.accessibility.CaptioningManager$1.onChange (CaptioningManager.java:226)
android.database.ContentObserver.onChange (ContentObserver.java:145)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:703)
http://crashes.to/s/db9e325f0f5
It looks like that there is a problem when accessibility functions are enabled. But how I can detect on what UI element or screen that error appears?
I tried to enable accessibility on my own device and navigate through all application screens, but don't receive an exeption.
EDIT
Can this error be caused by using Span in TextView?
// welcome text
TextView welcome = (TextView) view.findViewById(R.id.home_user_name);
welcome.setText(Html.fromHtml(getString(R.string.home_welcome_text, accountManager.getActiveUser())));
// change...
welcome.append(" ");
SpannableString str = SpannableString.valueOf(getString(R.string.home_user_change));
str.setSpan(new URLSpan(getString(R.string.home_user_change)) {
#Override
public void onClick(View view) {
mGuiHandler.sendEmptyMessage(MESSAGE_CHANGE_USER);
}
}, 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
welcome.append(str);
welcome.setMovementMethod(LinkMovementMethod.getInstance());
First, this isn't part of the accessibility service APIs. It is part of the View's implementation of accessibility. See the google code project. CaptioningManager is in the core/java/android/view/accessibility package. So, this crash is happening regardless of whether accessibility is on or not, or the very least, independent of what accessibility service may be on.
In Captioning Manager on line 235 (the version on Google Code is out of date, but pretty close.). The onChange function is like this:
#Override
public void onChange(boolean selfChange, Uri uri) {
final String uriPath = uri.getPath();
final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
notifyEnabledChanged();
} else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
notifyLocaleChanged();
} else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
notifyFontScaleChanged();
} else {
// We only need a single callback when multiple style properties
// change in rapid succession.
mHandler.removeCallbacks(mStyleChangedRunnable);
mHandler.post(mStyleChangedRunnable);
}
}
This is being called by the ContentObserver class, from this point:
/**
* Dispatches a change notification to the observer. Includes the changed
* content Uri when available and also the user whose content changed.
*
* #param selfChange True if this is a self-change notification.
* #param uri The Uri of the changed content, or null if unknown.
* #param userId The user whose content changed. Can be either a specific
* user or {#link UserHandle#USER_ALL}.
*
* #hide
*/
public void onChange(boolean selfChange, Uri uri, int userId) {
onChange(selfChange, uri);
}
Notice in the documentation for the ContentObserver class explicitly states that the uri can be null, but the CaptioningManager immediately calls getPath without checking fi the value is null. This is why it is crashing. The uri passed to onChange is null.
Now, this is where it gets a little fuzzy right. The rest is private, not available on Google Code. SO, we can only guess as to what zygote is doing. Although, it likely wouldn't be helpful, even if we could see it.
Now, what can we glean from this. In the documentation for the CaptioningManager we see the following explanation for its purpose:
Contains methods for accessing and monitoring preferred video
captioning state and visual properties.
So, based on all of this, check any URIs or other properties of any video and perhaps other media elements in your application...
Related
I noticed in the branch.io documentations that branch SDK can only be configured to send deep linking data to an Activity. These methods must be called to setup branch SDK:
#Override
public void onStart()
{
super.onStart();
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener()
{
#Override
public void onInitFinished(JSONObject referringParams, BranchError error)
{
if (error == null)
{
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
}
else
{
Log.i("MyApp", error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
#Override
public void onNewIntent(Intent intent)
{
this.setIntent(intent);
}
As you see the method initSession() only accepts Activity for the third input. but I want the deeplinking data be sent to an IntentService. Am I missing something and branch provides a way to do that? Or if it doesn't, how can I provide the feature? I know that I can start an invisible activity and pass data through that to the IntentService but I've read that it makes the startup slow. Any suggestions?
We don't have anything baked into the SDK for sending parameters directly to an intent service. Capturing these parameters yourself in an activity and passing them elsewhere won't take more time than any other approach, as all approaches will require an init call, and that's where the negligible delay lives.
I read the source code of Branch and found out that there are some overloaded methods which doesn't get Activity as an input. Actually they are calling initSession with Activity set as null.
/**
* <p>Initialises a session with the Branch API.</p>
*
* #param callback A {#link BranchReferralInitListener} instance that will be called
* following successful (or unsuccessful) initialisation of the session
* with the Branch API.
* #param data A {#link Uri} variable containing the details of the source link that
* led to this initialisation action.
* #return A {#link Boolean} value that will return <i>false</i> if the supplied
* <i>data</i> parameter cannot be handled successfully - i.e. is not of a
* valid URI format.
*/
public boolean initSession(BranchReferralInitListener callback, #NonNull Uri data) {
return initSession(callback, data, null);
}
I used the method declared above and defined all the needed intent filters inside my IntentService declaration in manifest, instead of an activity. I tested it and it worked. It would be nice if they had documented that.
UPDATE
It's just a misuse. Not a reliable and supported approach. It would be better to create an invisible Activity and send data through that to the IntentService.
Context:
I'm trying to integrate the google voice actions in my app. I have seen and understood (or at least that what I think) the google codelabs-io2015 example and in this example if you don't modify the code everything works as expected. The problem starts when you attempt to adapt this example to your real use case.
The problem:
So, my problem is that I'm trying to implement the search voice action but Activity#isVoiceInteraction is always false. I don't finally understand when and why the activity is (and when it is not) linked to a voice interactor.
Research:
Looking into the source code of the Activity ,Activity#isVoiceInteraction and Activity#getVoiceInteractor API level 23, I have found the following:
/**
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
* {#link VoiceInteractor} returned by {#link #getVoiceInteractor}.
*/
public boolean isVoiceInteraction() {
return mVoiceInteractor != null;
}
,
/**
* Retrieve the active {#link VoiceInteractor} that the user is going through to
* interact with this activity.
*/
public VoiceInteractor getVoiceInteractor() {
return mVoiceInteractor;
}
and the mVoiceInteractor is only initialize on attach function as shown below:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
...
}
I have an app that, when notified by a ContentObserver of a change to a ContentProvider, attempts to query the provider on a background thread. This causes an SecurityException to be thrown:
8-10 15:54:29.577 3057-3200/com.xxxx.mobile.android.xxx W/Binderīš Caught a RuntimeException from the binder stub implementation.
java.lang.SecurityException: Permission Denial: reading com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452)
at android.content.ContentProvider$Transport.query(ContentProvider.java:205)
at android.content.ContentResolver.query(ContentResolver.java:478)
at android.content.ContentResolver.query(ContentResolver.java:422)
How would a thread created by an app end up with a different UID from the app's ContentProvider?
By placing an exception breakpoint in android.content.ContentProvider I see that UserHandle.isSameApp(uid, mMyUid) is false and UserHandle.isSameUser(uid, mMyUid) is true. I also see that the providers UID is 10087.
The uid value of 1000 belongs to the Android system. Many features of Android involve proxying requests to the system thread for processing. If an exception is thrown during this, the error will include the uid of the system, rather than the original requestor.
For the other points:
UserHandle.isSameApp(uid, mMyUid) is false
UserHandle.isSameUser(uid, mMyUid) is true
These are easiest to explain by looking at the source. On an Android device with multi-user support, each user is defined by a range of UIDs. isSameApp is false because the modulus of the ids do not match:
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;
}
Similarly, the two ids belong to the same user because they live in the same 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;
}
}
Note that this logic is flawed because it means that all Android system uids (< 10000) will be assumed to "belong" to the first user.
Also note that if a second user installs more than 1000 apps(!), there's the possibility that an App will be mistaken for a system app (both uid % PER_USER_RANGE will return 1000). It won't really matter though, because the strong sandboxing would prevent anything too bad from happening.
I got the same problem while trying to interact with my ContentProvider in a system callback (LeScanCallback). The problem is that the callback thread is owned by the Android system, and not by my app, even if the code is in my app.
Passing the work from the callback to one of my app threads before trying to interact with my ContentProvider solved the problem successfully.
To reduce boilerplate for thread creation and recycling (needed for frequent callbacks to reduce overhead), I used AndroidAnnotation's #Background annotation on my delegate method (but would use Kotlin Coroutines today).
If a Thread is started by any component of the application that has the provider, then you can access the ContentProvider without any SecurityException.
I am using a ContentProvider in my application just as an extra abstraction layer and I haven't exposed the content to other applications. I am accessing the ContentProvider in background thread (Not AsyncTask, but a simple java.lang.Thread). I am not getting any SecurityException. The following is the code from my application.
AndroidManifest.xml
<provider
android:authorities="com.sample.provider"
android:name="com.sample.MyProvider"
android:exported="false" />
MainActivity
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();
}
I don't seem to get any SecurityException. Ideally we need to be using AsyncQueryHandler for accessing the ContentProvider, because that allows you do all the fetching process in the background thread and will use the UI Thread to post the results in the UI. But after seeing this post, I just wanted to see if I could just use Thread and check if I could still access it without any Exception. It works fine.
I have turned accessability on, and my device speaks as I navigate around.
I have a custom seekbar and have implemented the folllowing:
onTouchEvent excerpt:
...
case MotionEvent.ACTION_MOVE:
getParent().requestDisallowInterceptTouchEvent(true);
setTouchAngle(pointToAngle(touchX, touchY));
score = getScoreFromAngle(angleStart,touchAngle);
if (onScoreSetListener != null) {
onScoreSetListener.onScorePoll(this, score);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
}
break;
...
onPopulateAccessibilityEvent method:
#Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
LogUtils.i(TAG,"onPopulateAccessibilityEvent()",null);
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
LogUtils.d(TAG,"dispatchPopulateAccessibilityEvent() TYPE_VIEW_TEXT_CHANGED",null);
event.getText().add(String.valueOf(getScore()));
break;
}
}
I can see onPopulateAccessibilityEvent being called in LogCat successfully, but the device is not giving any feedback. I expect the current score to be read back, but nothing.
Does anyone have any insight?
If you're extending ProgressBar, you can set the text for outgoing TYPE_VIEW_SELECTED events. These are sent automatically as the user adjusts the seek bar.
#Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.getText().add(...);
}
However, it looks like you may have extended View. In this case, you will need to use a slight workaround and trigger an announcement by sending a VIEW_FOCUSED event on ICS, or use the announceForAccessibility API on JellyBean and above. Which would require the support-v4 library and would look like this:
/** The parent context. Used to obtain string resources. */
private final Context mContext;
/**
* The accessibility manager for this context. This is used to check the
* accessibility enabled state, as well as to send raw accessibility events.
*/
private final AccessibilityManager mA11yManager;
/**
* Generates and dispatches an SDK-specific spoken announcement.
* <p>
* For backwards compatibility, we're constructing an event from scratch
* using the appropriate event type. If your application only targets SDK
* 16+, you can just call View.announceForAccessibility(CharSequence).
* </p>
*
* #param text The text to announce.
*/
private void announceForAccessibilityCompat(CharSequence text) {
if (!mA11yManager.isEnabled()) {
return;
}
// Prior to SDK 16, announcements could only be made through FOCUSED
// events. Jelly Bean (SDK 16) added support for speaking text verbatim
// using the ANNOUNCEMENT event type.
final int eventType;
if (Build.VERSION.SDK_INT < 16) {
eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED;
} else {
eventType = AccessibilityEventCompat.TYPE_ANNOUNCEMENT;
}
// Construct an accessibility event with the minimum recommended
// attributes. An event without a class name or package may be dropped.
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.getText().add(text);
event.setEnabled(isEnabled());
event.setClassName(getClass().getName());
event.setPackageName(mContext.getPackageName());
// JellyBean MR1 requires a source view to set the window ID.
final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event);
record.setSource(this);
// Sends the event directly through the accessibility manager. If your
// application only targets SDK 14+, you should just call
// getParent().requestSendAccessibilityEvent(this, event);
mA11yManager.sendAccessibilityEvent(event);
}
I'm considering possibility to replace Android's default SMS ContentProvider with my own one.
I'm talking about those which is called after:
context.getContentResolver().query(Uri.parse("content://sms/"),....);
I would dare to ask: is it possible?
No, That is internally used by the SMS messaging application AND the telephony layer of Android.
Replacing any in-built content providers is guaranteed to break Android - that is a given!
But what you can do is create your own content provider and craft your application to use your own instead.
If you're talking about monitoring the sms content provider, what you can do is use a ContentObserver to watch on the sms content provider and forward the changes made to the sms content provider to your own.
Here's an example of such scenario, everytime a change is made, the onChange gets fired, it is within there, that relaying to your own custom content provider will suffice.
private class MySMSContentObserver extends ContentObserver{
public MySMSContentObserver() {
super();
}
#Override
public boolean deliverSelfNotifications() {
return true;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// This is where the change notifications gets received!
}
}
// For example
MySMSContentObserver contentSMSObserver = new MySMSContentObserver();
//
context.getContentResolver().registerContentObserver (
"content://sms",
true,
contentSMSObserver);
Also, do not forget to unregister the content observer when the application is finished, i.e.:
context.getContentResolver().unregisterContentObserver(contentSMSObserver);