├── ic_launcher-web.png
├── AndroidManifest.xml
├── .classpath
├── project.properties
├── proguard-project.txt
├── .project
├── README.md
└── src
└── com
└── google
└── android
└── hotword
├── service
└── IHotwordService.java
└── client
└── HotwordServiceClient.java
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Ok-Google-Hotword-Lib/HEAD/ic_launcher-web.png
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HotwordServiceLib
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ok-Google-Hotword-Lib
2 | =====================
3 |
4 | Reimplementation of library used by OEMs to include the "Ok Google" hotword in launchers
5 |
6 | Usage
7 | =====
8 |
9 | Import this library into your IDE and include it in your project. (Eclipse: right click, properties, Android, Library -> Add)
10 | In your launcher's main Activity:
11 |
12 | In onCreate, create a HotwordServiceClient instance.
13 |
14 | ````
15 | mHotwordServiceClient = new HotwordServiceClient(this);
16 | ````
17 |
18 | In onAttachedToWindow:
19 | ````
20 | mHotwordServiceClient.onAttachedToWindow();
21 | mHotwordServiceClient.requestHotwordDetection(true);
22 | ````
23 |
24 | In onDetachedFromWindow:
25 | ````
26 | mHotwordServiceClient.onDetachedFromWindow();
27 | mHotwordServiceClient.requestHotwordDetection(false);
28 | ````
29 |
30 | In onResume:
31 | ````
32 | mHotwordServiceClient.requestHotwordDetection(true);
33 | ````
34 |
35 | In onPause:
36 | ````
37 | mHotwordServiceClient.requestHotwordDetection(false);
38 | ````
39 |
40 | You're done, the mic in the Google Search widget should now be filled.
41 |
--------------------------------------------------------------------------------
/src/com/google/android/hotword/service/IHotwordService.java:
--------------------------------------------------------------------------------
1 | package com.google.android.hotword.service;
2 |
3 | import android.os.Binder;
4 | import android.os.IBinder;
5 | import android.os.IInterface;
6 | import android.os.Parcel;
7 | import android.os.RemoteException;
8 |
9 | public abstract interface IHotwordService extends IInterface {
10 | public abstract void requestHotwordDetection(String packageName, boolean detect) throws RemoteException;
11 |
12 | public static abstract class Stub extends Binder implements IHotwordService {
13 | private static final String DESCRIPTOR = "com.google.android.hotword.service.IHotwordService";
14 | static final int TRANSACTION_requestHotwordDetection = 1;
15 |
16 | public Stub() {
17 | attachInterface(this, DESCRIPTOR);
18 | }
19 |
20 | public static IHotwordService asInterface(IBinder binder) {
21 | if (binder == null)
22 | return null;
23 | IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
24 | if (iInterface != null && iInterface instanceof IHotwordService)
25 | return (IHotwordService)iInterface;
26 | return new Proxy(binder);
27 | }
28 |
29 | public IBinder asBinder() {
30 | return this;
31 | }
32 |
33 | public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
34 | switch (code) {
35 | default:
36 | return super.onTransact(code, data, reply, flags);
37 | case INTERFACE_TRANSACTION:
38 | reply.writeString(DESCRIPTOR);
39 | return true;
40 | case TRANSACTION_requestHotwordDetection:
41 | data.enforceInterface(DESCRIPTOR);
42 | String packageName = data.readString();
43 | boolean detect = (data.readInt() == 1) ? true : false;
44 | requestHotwordDetection(packageName, detect);
45 | return true;
46 | }
47 | }
48 |
49 | private static class Proxy implements IHotwordService {
50 | private IBinder mRemote;
51 |
52 | Proxy(IBinder iBinder) {
53 | mRemote = iBinder;
54 | }
55 |
56 | public IBinder asBinder() {
57 | return mRemote;
58 | }
59 |
60 | public String getInterfaceDescriptor() {
61 | return DESCRIPTOR;
62 | }
63 |
64 | public void requestHotwordDetection(String packageName, boolean detect) throws RemoteException {
65 | Parcel data = Parcel.obtain();
66 | try {
67 | data.writeInterfaceToken(getInterfaceDescriptor());
68 | data.writeString(packageName);
69 | data.writeInt(detect ? 1 : 0);
70 | mRemote.transact(TRANSACTION_requestHotwordDetection, data, null, FLAG_ONEWAY);
71 | } finally {
72 | data.recycle();
73 | }
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/com/google/android/hotword/client/HotwordServiceClient.java:
--------------------------------------------------------------------------------
1 | package com.google.android.hotword.client;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.annotation.TargetApi;
5 | import android.app.Activity;
6 | import android.content.ComponentName;
7 | import android.content.Intent;
8 | import android.content.ServiceConnection;
9 | import android.os.Build;
10 | import android.os.IBinder;
11 | import android.os.Looper;
12 | import android.os.RemoteException;
13 | import android.util.Log;
14 | import android.view.WindowId;
15 |
16 | import com.google.android.hotword.service.IHotwordService;
17 |
18 | @TargetApi(Build.VERSION_CODES.KITKAT)
19 | public class HotwordServiceClient {
20 | @SuppressWarnings("unused")
21 | private static final boolean DBG = false;
22 | private static final String HOTWORD_SERVICE = "com.google.android.googlequicksearchbox.HOTWORD_SERVICE";
23 | private static final String TAG = "HotwordServiceClient";
24 | private static final String VEL_PACKAGE = "com.google.android.googlequicksearchbox";
25 |
26 | private final Activity mActivity;
27 | private final ServiceConnection mConnection;
28 | private final WindowId.FocusObserver mFocusObserver;
29 |
30 | private IHotwordService mHotwordService;
31 |
32 | private boolean mHotwordStart;
33 | private boolean mIsAvailable = true;
34 | private boolean mIsBound;
35 | private boolean mIsFocused = false;
36 | private boolean mIsRequested = true;
37 |
38 | public HotwordServiceClient(Activity activity) {
39 | mActivity = activity;
40 | mConnection = new HotwordServiceConnection();
41 | mFocusObserver = new WindowFocusObserver();
42 | }
43 |
44 | private void assertMainThread() {
45 | if (Looper.getMainLooper().getThread() != Thread.currentThread())
46 | throw new IllegalStateException("Must be called on the main thread.");
47 | }
48 |
49 | private void internalBind() {
50 | if (!mIsAvailable || mIsBound) {
51 | if (!mIsAvailable)
52 | Log.w(TAG, "Hotword service is not available.");
53 | return;
54 | }
55 |
56 | Intent localIntent = new Intent(HOTWORD_SERVICE).setPackage(VEL_PACKAGE);
57 | mIsAvailable = mActivity.bindService(localIntent, mConnection, Context.BIND_AUTO_CREATE);
58 | mIsBound = mIsAvailable;
59 | }
60 |
61 | private void internalRequestHotword() {
62 | if (mIsFocused && mIsRequested) {
63 | if (!mHotwordStart) {
64 | mHotwordStart = true;
65 | if (!mIsBound) {
66 | internalBind();
67 | }
68 | }
69 | }
70 |
71 | try {
72 | if (mHotwordService != null)
73 | mHotwordService.requestHotwordDetection(mActivity.getPackageName(), mIsFocused && mIsRequested);
74 | } catch (RemoteException e) {
75 | Log.w(TAG, "requestHotwordDetection - remote call failed", e);
76 | return;
77 | }
78 | }
79 |
80 | private boolean isPreKitKatDevice() {
81 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
82 | Log.w(TAG, "Hotword service isn't usable on pre-Kitkat devices");
83 | return true;
84 | }
85 | return false;
86 | }
87 |
88 | public final void onAttachedToWindow() {
89 | if (isPreKitKatDevice())
90 | return;
91 |
92 | assertMainThread();
93 | mActivity.getWindow().getDecorView().getWindowId().registerFocusObserver(mFocusObserver);
94 | internalBind();
95 | }
96 |
97 | @SuppressLint("MissingSuperCall")
98 | public final void onDetachedFromWindow() {
99 | if (isPreKitKatDevice())
100 | return;
101 |
102 | if (!mIsBound) {
103 | return;
104 | }
105 |
106 | assertMainThread();
107 | mActivity.getWindow().getDecorView().getWindowId().unregisterFocusObserver(mFocusObserver);
108 | mActivity.unbindService(mConnection);
109 | mIsBound = false;
110 | }
111 |
112 | public final void requestHotwordDetection(boolean detect) {
113 | if (isPreKitKatDevice())
114 | return;
115 |
116 | assertMainThread();
117 | mIsRequested = detect;
118 | internalRequestHotword();
119 | }
120 |
121 | private class HotwordServiceConnection implements ServiceConnection {
122 | private HotwordServiceConnection() {}
123 |
124 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
125 | mHotwordService = IHotwordService.Stub.asInterface(iBinder);
126 | internalRequestHotword();
127 | }
128 |
129 | public void onServiceDisconnected(ComponentName componentName) {
130 | mIsBound = false;
131 | mHotwordService = null;
132 | }
133 | }
134 |
135 | private class WindowFocusObserver extends WindowId.FocusObserver {
136 | private WindowFocusObserver() {}
137 |
138 | public void onFocusGained(WindowId wid) {
139 | mIsFocused = true;
140 | internalRequestHotword();
141 | }
142 |
143 | public void onFocusLost(WindowId wid) {
144 | mIsFocused = false;
145 | internalRequestHotword();
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------