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());
}
}
...
}
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.
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...
On Activity start my app is checking for ANY possible network providers, and if there is no network available it shuts down and stops executing code, so user can do nothing till user reopen app.
I want to make it so that the connection detector runs all the time. If user got to app without internet connection, then my app stops running; I want my app to periodically scan code so my app will resume after user connects to any network WITHOUT LEAVING MY APP. Thanks in advance.
This is simple.
1) read the docs
http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html
2) implement Broadcast Receiver like
public class UnifiedReceiver extends BroadcastReceiver {
private static final String TAG = UnifiedReceiver.class.getSimpleName();
private static final String ACTION_CONNECTIVITY_CHANGE = "android.net.conn.CONNECTIVITY_CHANGE";
/**
* the constructor
*/
public UnifiedReceiver ( ) {
super();
}
/* (non-Javadoc)
* #see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
*/
#Override
public void onReceive ( Context context, Intent intent ) {
final String TAG2 = "onReceive";
Log.d(TAG, "entering "+TAG2+"()");
if (intent.getAction().equals(ACTION_CONNECTIVITY_CHANGE)) {
Log.d(TAG, TAG2+": '"+ACTION_CONNECTIVITY_CHANGE+"' received.");
// do something useful
}
}
}
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 using the In App Billing sample app to add this feature to my application.
After I finished adding it to my app, and tested all working, I noticed the comment in this Security class:
Security-related methods. For a secure implementation, all of
this code should be implemented on a server that communicates with
the application on the device. For the sake of simplicity and
clarity of this example, this code is included here and is executed
on the device. If you must verify the purchases on the phone, you
should obfuscate this code to make it harder for an attacker to
replace the code with stubs that treat all purchases as verified.
As Google suggests, I do the purchase verification on the server side so I really don't need the Security class in my project.
The problem is, I can't figure out how to remove the BillingService class dependency in the Security class.
I started by deleting the Security class and following the errors in the BillingService and most places it's being used I can remove easily, except in one place:
private void purchaseStateChanged(int startId, String signedData, String signature) {
ArrayList<Security.VerifiedPurchase> purchases;
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
vp.orderId, vp.purchaseTime, vp.developerPayload);
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(startId, notifyIds);
}
}
Would love if someone can share his/hers purchaseStateChanged method (based on the in app billing sample app) without the use of the Security class.
So here's what I did. First the calls to BillingService occur on the applications main thread, so you need to issue your server calls in a background thread. I chose to finish up processing on the main thread, since I wasn't sure what impact calling methods like 'confirmNotifications' on a background thread might have.
I created a callback interface VerifyTransactionCompletion which could be dispatched back to the main thread after the remote call completed.
I keep around the Security class and have it manage the call to the server now, instead of what it originally performed in the sample. So when you see the call to Security, that's where I call out to my server and perform signature validation.
/**
* Callback interface to <em>finish</em> processing a transaction once the remote
* servers have processed it.
*/
public interface VerifyTransactionCompletion {
public void transactionVerified(List<Security.VerifiedPurchase> purchases);
}
private void purchaseStateChanged(final int startId, String signedData, String signature) {
// verifyPurchase issues remote call to server (in a background thread), then
// calls transactionVerified on the main thread to continue processing.
Security.verifyPurchase(signedData, signature, new VerifyTransactionCompletion() {
#Override
public void transactionVerified(List<VerifiedPurchase> purchases) {
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(BillingService.this, vp.purchaseState, vp.productId,
vp.orderId, vp.purchaseTime, vp.developerPayload);
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(startId, notifyIds);
}
}
});
}