How to disable Google Assist API (Now on Tap)
Asked Answered
E

3

9

Google have recently released Android Marshmallow with Now on Tap which can scan the app contents and provide an additional information to the user.

Unfortunately, for our app the info doesn't look very relevant and Google is ignoring the data we set inside onProvideContentAssist() and onProvideAssistData().

These specs look rather high level and also contain the words like "can suggest" and "additional information" so seems like Google officially allows itself to ignore the data app developers provide.

So we decided to disable Now on Tap, but seems it is not very trivial. According to the doc provided above, we should use FLAG_SECURE in this case. But then users couldn't capture screenshots and Google Now on Tap starts to blame our app with the following user facing message:

Results not available
This app has blocked Now on Tap

enter image description here

But it seems that Opera somehow gently blocks Now on Tap for their private tabs. It shows much more app friendly message for them:

Nothing on tap

enter image description here

How does Opera block Now on Tap?

Does anybody know how to block Google Assist API (Now on Tap) without getting a blame regarding our app from their side?

Enterprising answered 9/10, 2015 at 17:15 Comment(2)
"so seems like Google officially allows itself to ignore the data app developers provide." More than likely, you're just doing this part totally wrong.Aught
@Saeed Google has even a code example inside the docs I mentioned: developer.android.com/training/articles/… which data is totally ignored by onTap (aka Google Assist) inside our app.Enterprising
M
7

The View class has a method setAssistBlocked:

Controls whether assist data collection from this view and its children is enabled (that is, whether {@link #onProvideStructure} and {@link #onProvideVirtualStructure} will be called). The default value is false, allowing normal assist collection. Setting this to false will disable assist collection.

@param enabled Set to true to disable assist data collection, or false (the default) to allow it.

Unfortunately this method is annotated with @hide, so we can't see it or directly use it.

But we can use it with a small reflection call:

private void setAssistBlocked(View view, boolean blocked) {
        try {
            Method setAssistBlockedMethod = View.class.getMethod("setAssistBlocked", boolean.class);
            setAssistBlockedMethod.invoke(view, blocked);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

To block all contents in the activity you can use:

final View rootContent = findViewById(android.R.id.content);
setAssistBlocked(rootContent, true);
Mathildamathilde answered 10/10, 2015 at 8:15 Comment(4)
Hello! We deeply frown on recommendations of using private APIs of the platform. They are hidden for a reason. Hidden APIs are not supported, meaning you don't know what can ever happen with them -- maybe in the future they disappear (fortunately you'll just silently choke on that), maybe the implementation changes so that the call you are making now unleashes a zombie apocalypse on the world. You don't know the future, so please don't do this.Should
Further, in this case there is no reason to do this, because you can use this: developer.android.com/reference/android/view/…Should
So if there is no reason to do this, why chrome do exactly the same thing? Please explain it, because i am really curious...Mathildamathilde
BTW, turning on Google Assist by default for all the Android M smartphones and hiding the good way to disable it doesn't look friendly to app developers, isn't it?Enterprising
H
5

Another possible solution, that I'll eventually need to try out, is hinted at by hackbod's one comment:

Further, in this case there is no reason to do this, because you can use this: https://developer.android.com/reference/android/view/View.html#dispatchProvideStructure(android.view.ViewStructure)

The code comment for dispatchProvideStructure() states: "Dispatch creation of ViewStructure down the hierarchy." (emphasis mine).

So, create a NoAssistFrameLayout subclass of FrameLayout, override dispatchProvideStructure() to be a no-op, and use that as your root container for your activity layout. This will block the automatic population of the ViewStructure for anything in the body of the activity.

It will not, however, block anything that could be inferred from stuff outside your main content area, such as your action bar, as that would be outside the NoAssistFrameLayout's view hierarchy. Probably there's not much in there that would have privacy implications, but it's a limitation of this technique.


OK, I have tried it, and it does seem to work:

/***
 Copyright (c) 2015 CommonsWare, LLC
 Licensed under the Apache License, Version 2.0 (the "License"); you may not
 use this file except in compliance with the License. You may obtain a copy
 of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
 by applicable law or agreed to in writing, software distributed under the
 License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
 OF ANY KIND, either express or implied. See the License for the specific
 language governing permissions and limitations under the License.

 From _The Busy Coder's Guide to Android Development_
 https://commonsware.com/Android
 */

package com.commonsware.android.assist.no;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewStructure;
import android.widget.FrameLayout;

public class NoAssistFrameLayout extends FrameLayout {
  public NoAssistFrameLayout(Context context) {
    super(context);
  }

  public NoAssistFrameLayout(Context context,
                             AttributeSet attrs) {
    super(context, attrs);
  }

  public NoAssistFrameLayout(Context context,
                             AttributeSet attrs,
                             int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public NoAssistFrameLayout(Context context,
                             AttributeSet attrs,
                             int defStyleAttr,
                             int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  @Override
  public void dispatchProvideStructure(ViewStructure structure) {
    // no, thanks
  }
}
Hardfeatured answered 15/10, 2015 at 23:17 Comment(10)
I am curious what the "On Tap" pop up looks like after using this as a root view. Can you describe what showed up or post a screenshot by chance?Pollock
@JeffAlexander: I disable Google Now as a matter of course. I have been testing NoAssistFrameLayout with my own assistant implementation that just logs what is exposed by the activity. I tested NoAssistFrameLayout for a single tab of a ViewPager in this sample project and things worked as expected.Hardfeatured
In case someone wanted to know what it will look like, I tested an Activity with a root view that was a FrameLayout within that there was a TextView with text was of a movie title. On Tap showed info on that movie. Then I switched to the NoAssistFrameLayout I got "Nothing on tap".Pollock
Seems like this approach is rather hard to use in a real app. The main problem is that it blocks only 1 level of view hierarchy from Google Assist access. E.g. it works for the immediate children of NoAssistFrameLayout, but if I use NoAssistFrameLayout as a parent to Fragment, then Google Assist can successfully access Fragment's data.Enterprising
@goRGon: Sorry, I am not seeing those results. I just tested a revised version of this sample project, where I wrapped a NoAssistFrameLayout around the contents of res/layout/main.xml (LinearLayout holding a MaterialTabs+ViewPager). The ViewPager holds 3 tabs, each populated by a fragment. The only stuff that shows up from an assistant, using this logging assistant instead of Now On Tap, is the action bar. Got a reproducible test case?Hardfeatured
@Hardfeatured Maybe, it depends on the data you have inside this fragment? Or maybe Google Assist is smart enough to recognize some images in our app. But the thing is: when I put the whole page into NoAssistFrameLayout, Google Assist somehow still steals some data from the embedded Fragments. And using setAssistBlocked works perfect across the whole app by just calling it inside our general base activity class. Very sad that Google decided to hide that method from app developers.Enterprising
@goRGon: "Or maybe Google Assist is smart enough to recognize some images in our app" -- that's a possibility, as NoAssistFrameLayout is not blocking the screenshot. Beyond that, without a reproducible test case, I cannot really help you, though.Hardfeatured
Thank you, I am happy with NoAssistFrameLayout. Seems like this is the best solution, especially after @Mattia Maestrini comment that even Google Chrome uses it...Enterprising
No, Chrome doesn't use the NoAssistFrameLayout technique, it uses setAssistBlocked as you can see hereMathildamathilde
Ok, finally I found that NoAssistFrameLayout solution works also. But to make it work across the whole app we need to put NoAssist*Layout into all the layouts of our app.Enterprising
S
-2

Opera does this probably because they haven't updated their browser to report assist data (this requires special cooperation with apps that don't use the normal view hierarchy, like browsers).

However if your issue is that Now on Tap isn't returning relevant data, the solution is not to block the data it is getting -- all that does is ensure the data it returns for your app never improves, because it never sees data it could give better suggestions for. Blocking it certainly isn't going to cause it to use other data, it is just going to make it stupid for your app.

You should not be trying to make Now on Tap stupid for your app.

Should answered 15/10, 2015 at 21:44 Comment(1)
As I've mentioned in my question, Opera does this only for their private tabs, but Google Assist still works for their public tabs. So it doesn't seem that they haven't updated their browser to report assist data. I understand your point of view and I think there should be many apps that like Google Assist and the data that it things is relevant, but my question was about disabling it.Enterprising

© 2022 - 2024 — McMap. All rights reserved.