├── 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 | --------------------------------------------------------------------------------