diff options
author | Thecrazyskull <anaskarbila@gmail.com> | 2018-01-13 17:09:25 +0100 |
---|---|---|
committer | Arne Coucheron <arco68@gmail.com> | 2018-01-19 00:27:14 +0100 |
commit | 8dee56deeed7b09a9e5e9f4d21f27aa9d495930c (patch) | |
tree | 46367d80413452eb6e68e960953690b56215658c | |
parent | 523c12ffac7fb3a79458e56f9439a070b8930d6a (diff) | |
download | android_packages_apps_Trebuchet-8dee56deeed7b09a9e5e9f4d21f27aa9d495930c.tar.gz android_packages_apps_Trebuchet-8dee56deeed7b09a9e5e9f4d21f27aa9d495930c.tar.bz2 android_packages_apps_Trebuchet-8dee56deeed7b09a9e5e9f4d21f27aa9d495930c.zip |
Trebuchet: feed integration support
jrizzoli: adapted for trebuchet
Includes the following commits:
* Launcher3: support google now tab
* LauncherClient: make sure service is connected before trying to unbind
* Launcher3: animate workspace when animating the Google now page
* Launcher3: make Google now left page optional
* Launcher3: cleanup launcher tab preference
Change-Id: I84582ae812b8ecb0d694ae2396843effdcf1219c
Signed-off-by: Joey <joey@lineageos.org>
12 files changed, 1049 insertions, 0 deletions
diff --git a/res/drawable/ic_settings_feed.xml b/res/drawable/ic_settings_feed.xml new file mode 100644 index 000000000..875890adc --- /dev/null +++ b/res/drawable/ic_settings_feed.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The LineageOS Project + + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="@color/settings_icons" + android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z" /> +</vector> diff --git a/res/values/lineage_strings.xml b/res/values/lineage_strings.xml index afe0ad2df..1b4564c57 100644 --- a/res/values/lineage_strings.xml +++ b/res/values/lineage_strings.xml @@ -67,4 +67,5 @@ <string name="settings_icon_shape">Icons shape</string> <string name="settings_icon_badging_desc_on">A bubble will be displayed above the icon when there\'s a notification</string> <string name="settings_icon_badging_desc_off">No bubble will be displayed above the icon when there\'s a notification</string> + <string name="settings_feed">Enable feed integration</string> </resources> diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml index f4d7831f1..03fc0ed09 100644 --- a/res/xml/launcher_preferences.xml +++ b/res/xml/launcher_preferences.xml @@ -33,6 +33,13 @@ android:title="@string/statusbar_expand" /> <SwitchPreference + android:defaultValue="false" + android:icon="@drawable/ic_settings_feed" + android:key="pref_feed_integration" + android:persistent="true" + android:title="@string/settings_feed" /> + + <SwitchPreference android:defaultValue="true" android:icon="@drawable/ic_settings_add_shortcut" android:key="pref_add_icon_to_home" diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e7cfc6955..95b1f0f93 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -368,6 +368,10 @@ public class Launcher extends BaseActivity private EditText mIconEditTitle; private IconsHandler mIconsHandler; + // Feed integration + private LauncherTab mLauncherTab; + private boolean mFeedIntegrationEnabled; + @Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG_STRICT_MODE) { @@ -496,6 +500,9 @@ public class Launcher extends BaseActivity // we want the screen to auto-rotate based on the current orientation setOrientation(); + mFeedIntegrationEnabled = isFeedIntegrationEnabled(); + mLauncherTab = new LauncherTab(this, mFeedIntegrationEnabled); + setContentView(mLauncherView); // Listen for broadcasts @@ -1077,6 +1084,9 @@ public class Launcher extends BaseActivity if (shouldShowDiscoveryBounce()) { mAllAppsController.showDiscoveryBounce(); } + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onResume(); + } if (mLauncherCallbacks != null) { mLauncherCallbacks.onResume(); } @@ -1099,6 +1109,10 @@ public class Launcher extends BaseActivity mWorkspace.getCustomContentCallbacks().onHide(); } + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onPause(); + } + if (mLauncherCallbacks != null) { mLauncherCallbacks.onPause(); } @@ -1607,6 +1621,11 @@ public class Launcher extends BaseActivity super.onAttachedToWindow(); FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); + + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onAttachedToWindow(); + } + if (mLauncherCallbacks != null) { mLauncherCallbacks.onAttachedToWindow(); } @@ -1616,6 +1635,11 @@ public class Launcher extends BaseActivity public void onDetachedFromWindow() { super.onDetachedFromWindow(); + + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onDetachedFromWindow(); + } + if (mLauncherCallbacks != null) { mLauncherCallbacks.onDetachedFromWindow(); } @@ -1781,6 +1805,10 @@ public class Launcher extends BaseActivity mIconEditDialog = null; } + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().hideOverlay(true); + } + if (mLauncherCallbacks != null) { mLauncherCallbacks.onHomeIntent(); } @@ -1892,6 +1920,10 @@ public class Launcher extends BaseActivity clearPendingBinds(); + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onDestroy(); + } + if (mLauncherCallbacks != null) { mLauncherCallbacks.onDestroy(); } @@ -4134,6 +4166,10 @@ public class Launcher extends BaseActivity return super.onKeyShortcut(keyCode, event); } + private boolean isFeedIntegrationEnabled() { + return Utilities.hasFeedIntegration(this); + } + public static CustomAppWidget getCustomAppWidget(String name) { return sCustomAppWidgets.get(name); } @@ -4158,6 +4194,19 @@ public class Launcher extends BaseActivity // Recreate the activity so that it initializes the rotation preference again. recreate(); } + if (SettingsActivity.KEY_FEED_INTEGRATION.equals(key)) { + if (mLauncherTab == null) { + return; + } + + mFeedIntegrationEnabled = isFeedIntegrationEnabled(); + mLauncherTab.updateLauncherTab(mFeedIntegrationEnabled); + if (mFeedIntegrationEnabled) { + mLauncherTab.getClient().onAttachedToWindow(); + } else { + mLauncherTab.getClient().onDestroy(); + } + } } } } diff --git a/src/com/android/launcher3/LauncherTab.java b/src/com/android/launcher3/LauncherTab.java new file mode 100644 index 000000000..2bb450070 --- /dev/null +++ b/src/com/android/launcher3/LauncherTab.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 Paranoid Android + * Copyright (C) 2018 The LineageOS Project + * + * 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. + */ + +package com.android.launcher3; + +import com.android.launcher3.Launcher.LauncherOverlay; +import com.android.launcher3.Launcher.LauncherOverlayCallbacks; + +import com.google.android.libraries.launcherclient.LauncherClient; +import com.google.android.libraries.launcherclient.LauncherClientCallbacksAdapter; + +public class LauncherTab { + + public static final String SEARCH_PACKAGE = "com.google.android.googlequicksearchbox"; + + private Launcher mLauncher; + private LauncherClient mLauncherClient; + private Workspace mWorkspace; + + public LauncherTab(Launcher launcher, boolean enabled) { + mLauncher = launcher; + mWorkspace = launcher.getWorkspace(); + + updateLauncherTab(enabled); + if (enabled && mLauncherClient.isConnected()) { + launcher.setLauncherOverlay(new LauncherOverlays()); + } + } + + protected void updateLauncherTab(boolean enabled) { + if (enabled) { + mLauncherClient = new LauncherClient(mLauncher, + new LauncherClientCallbacks(), SEARCH_PACKAGE, true); + mLauncher.setLauncherOverlay(new LauncherOverlays()); + } else { + mLauncher.setLauncherOverlay(null); + } + } + + protected LauncherClient getClient() { + return mLauncherClient; + } + + private class LauncherOverlays implements LauncherOverlay { + @Override + public void onScrollInteractionBegin() { + mLauncherClient.startMove(); + } + + @Override + public void onScrollInteractionEnd() { + mLauncherClient.endMove(); + } + + @Override + public void onScrollChange(float progress, boolean rtl) { + mLauncherClient.updateMove(progress); + } + + @Override + public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks) { + } + } + + private class LauncherClientCallbacks extends LauncherClientCallbacksAdapter { + @Override + public void onOverlayScrollChanged(float progress) { + mWorkspace.onOverlayScrollChanged(progress); + } + } +} diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java index 765879946..8ae76a81a 100644 --- a/src/com/android/launcher3/SettingsActivity.java +++ b/src/com/android/launcher3/SettingsActivity.java @@ -30,11 +30,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; +import android.preference.SwitchPreference; import android.provider.Settings; import com.android.launcher3.graphics.IconShapeOverride; @@ -57,6 +61,8 @@ public class SettingsActivity extends Activity { private static final String KEY_SHOW_DESKTOP_LABELS = "pref_desktop_show_labels"; private static final String KEY_SHOW_DRAWER_LABELS = "pref_drawer_show_labels"; + static final String KEY_FEED_INTEGRATION = "pref_feed_integration"; + static final String EXTRA_SCHEDULE_RESTART = "extraScheduleRestart"; @Override @@ -130,6 +136,12 @@ public class SettingsActivity extends Activity { mIconBadgingObserver.register(NOTIFICATION_BADGING, NOTIFICATION_ENABLED_LISTENERS); } + SwitchPreference feedIntegration = (SwitchPreference) + findPreference(KEY_FEED_INTEGRATION); + if (!hasPackageInstalled(LauncherTab.SEARCH_PACKAGE)) { + homeGroup.removePreference(feedIntegration); + } + Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE); if (iconShapeOverride != null) { if (IconShapeOverride.isSupported(getActivity())) { @@ -179,6 +191,16 @@ public class SettingsActivity extends Activity { manager.set(AlarmManager.RTC, java.lang.System.currentTimeMillis() + 1, pi); java.lang.System.exit(0); } + + private boolean hasPackageInstalled(String pkgName) { + try { + ApplicationInfo ai = getContext().getPackageManager() + .getApplicationInfo(pkgName, 0); + return ai.enabled; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } } /** diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 52e1e48dd..300df43c6 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -699,4 +699,8 @@ public final class Utilities { return !currentPack.equals(defaultPack) && !currentPack.equals(defaultLocalziedPack); } + static boolean hasFeedIntegration(Context context) { + SharedPreferences prefs = getPrefs(context.getApplicationContext()); + return prefs.getBoolean(SettingsActivity.KEY_FEED_INTEGRATION, false); + } } diff --git a/src/com/google/android/libraries/launcherclient/ILauncherOverlay.java b/src/com/google/android/libraries/launcherclient/ILauncherOverlay.java new file mode 100644 index 000000000..1b54b26ea --- /dev/null +++ b/src/com/google/android/libraries/launcherclient/ILauncherOverlay.java @@ -0,0 +1,319 @@ +package com.google.android.libraries.launcherclient; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; +import android.view.WindowManager; + +public interface ILauncherOverlay extends IInterface { + void closeOverlay(int options) throws RemoteException; + + void endScroll() throws RemoteException; + + String getVoiceSearchLanguage() throws RemoteException; + + boolean isVoiceDetectionRunning() throws RemoteException; + + void onPause() throws RemoteException; + + void onResume() throws RemoteException; + + void onScroll(float progress) throws RemoteException; + + void openOverlay(int options) throws RemoteException; + + void requestVoiceDetection(boolean start) throws RemoteException; + + void startScroll() throws RemoteException; + + void windowAttached(WindowManager.LayoutParams attrs, ILauncherOverlayCallback callbacks, + int options) throws RemoteException; + + void windowDetached(boolean isChangingConfigurations) throws RemoteException; + + abstract class Stub extends Binder implements ILauncherOverlay { + static final int START_SCROLL_TRANSACTION = 1; + static final int ON_SCROLL_TRANSACTION = 2; + static final int END_SCROLL_TRANSACTION = 3; + static final int WINDOW_ATTACHED_TRANSACTION = 4; + static final int WINDOW_DETACHED_TRANSACTION = 5; + static final int CLOSE_OVERLAY_TRANSACTION = 6; + static final int ON_PAUSE_TRANSACTION = 7; + static final int ON_RESUME_TRANSACTION = 8; + static final int OPEN_OVERLAY_TRANSACTION = 9; + static final int REQUEST_VOICE_DETECTION_TRANSACTION = 10; + static final int GET_VOICE_SEARCH_LANGUAGE_TRANSACTION = 11; + static final int IS_VOICE_DETECTION_RUNNING_TRANSACTION = 12; + + + public static ILauncherOverlay asInterface(IBinder obj) { + if (obj == null) { + return null; + } + + IInterface iin = obj.queryLocalInterface(ILauncherOverlay.class.getName()); + if (iin != null && iin instanceof ILauncherOverlay) { + return (ILauncherOverlay) iin; + } + else { + return new Proxy(obj); + } + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: + reply.writeString(ILauncherOverlay.class.getName()); + return true; + case START_SCROLL_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + startScroll(); + return true; + case ON_SCROLL_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + float _arg0 = data.readFloat(); + onScroll(_arg0); + return true; + case END_SCROLL_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + endScroll(); + return true; + case WINDOW_ATTACHED_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + WindowManager.LayoutParams layoutParams = null; + if (data.readInt() != 0) { + layoutParams = WindowManager.LayoutParams.CREATOR.createFromParcel(data); + } + + windowAttached( + layoutParams, + ILauncherOverlayCallback.Stub.asInterface(data.readStrongBinder()), + data.readInt() + ); + + return true; + case WINDOW_DETACHED_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + windowDetached(data.readInt() != 0); + return true; + case CLOSE_OVERLAY_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + closeOverlay(data.readInt()); + return true; + case ON_PAUSE_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + onPause(); + return true; + case ON_RESUME_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + onResume(); + return true; + case OPEN_OVERLAY_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + openOverlay(data.readInt()); + return true; + case REQUEST_VOICE_DETECTION_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + requestVoiceDetection(data.readInt() != 0); + return true; + case GET_VOICE_SEARCH_LANGUAGE_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + String language = getVoiceSearchLanguage(); + reply.writeNoException(); + reply.writeString(language); + return true; + case IS_VOICE_DETECTION_RUNNING_TRANSACTION: + data.enforceInterface(ILauncherOverlay.class.getName()); + boolean running = isVoiceDetectionRunning(); + reply.writeNoException(); + reply.writeInt(running ? 1 : 0); + return true; + default: + return super.onTransact(code, data, reply, flags); + } + } + + private static class Proxy implements ILauncherOverlay { + private IBinder mRemote; + + public Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public void closeOverlay(int options) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + _data.writeInt(options); + + mRemote.transact(CLOSE_OVERLAY_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + public void endScroll() throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(END_SCROLL_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + public String getVoiceSearchLanguage() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(GET_VOICE_SEARCH_LANGUAGE_TRANSACTION, _data, _reply, 0); + _reply.readException(); + return _reply.readString(); + } finally { + _data.recycle(); + _reply.recycle(); + } + } + + @Override + public boolean isVoiceDetectionRunning() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(IS_VOICE_DETECTION_RUNNING_TRANSACTION, _data, _reply, 0); + _reply.readException(); + return _reply.readInt() != 0; + } finally { + _data.recycle(); + _reply.recycle(); + } + } + + @Override + public void onPause() throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(ON_PAUSE_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void onResume() throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(ON_RESUME_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void onScroll(float progress) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + _data.writeFloat(progress); + + mRemote.transact(ON_SCROLL_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void openOverlay(int options) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + _data.writeInt(options); + + mRemote.transact(OPEN_OVERLAY_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void requestVoiceDetection(boolean start) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + _data.writeInt(start ? 1 : 0); + + mRemote.transact(REQUEST_VOICE_DETECTION_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void startScroll() throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + + mRemote.transact(START_SCROLL_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void windowAttached(WindowManager.LayoutParams attrs, + ILauncherOverlayCallback callbacks, int options) + throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + if (attrs != null) { + _data.writeInt(1); + attrs.writeToParcel(_data, 0); + } else { + _data.writeInt(0); + } + _data.writeStrongBinder(callbacks.asBinder()); + _data.writeInt(options); + + _data.writeInt(1); + + mRemote.transact(WINDOW_ATTACHED_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + @Override + public void windowDetached(boolean isChangingConfigurations) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlay.class.getName()); + _data.writeInt(isChangingConfigurations ? 1 : 0); + + mRemote.transact(WINDOW_DETACHED_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + } + + + } +}
\ No newline at end of file diff --git a/src/com/google/android/libraries/launcherclient/ILauncherOverlayCallback.java b/src/com/google/android/libraries/launcherclient/ILauncherOverlayCallback.java new file mode 100644 index 000000000..2f6d029e4 --- /dev/null +++ b/src/com/google/android/libraries/launcherclient/ILauncherOverlayCallback.java @@ -0,0 +1,94 @@ +package com.google.android.libraries.launcherclient; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +public interface ILauncherOverlayCallback extends IInterface { + void overlayScrollChanged(float progress) throws RemoteException; + + void overlayStatusChanged(int status) throws RemoteException; + + abstract class Stub extends Binder implements ILauncherOverlayCallback { + static final int OVERLAY_SCROLL_CHANGED_TRANSACTION = 1; + static final int OVERLAY_STATUS_CHANGED_TRANSACTION = 2; + + public Stub() { + attachInterface(this, ILauncherOverlayCallback.class.getName()); + } + + public static ILauncherOverlayCallback asInterface(IBinder obj) { + if (obj == null) { + return null; + } + + IInterface iin = obj.queryLocalInterface(ILauncherOverlayCallback.class.getName()); + if (iin != null && iin instanceof ILauncherOverlayCallback) { + return (ILauncherOverlayCallback) iin; + } else { + return new Proxy(obj); + } + } + + public IBinder asBinder() { + return this; + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: + reply.writeString(ILauncherOverlay.class.getName()); + return true; + case OVERLAY_SCROLL_CHANGED_TRANSACTION: + data.enforceInterface(ILauncherOverlayCallback.class.getName()); + overlayScrollChanged(data.readFloat()); + return true; + case OVERLAY_STATUS_CHANGED_TRANSACTION: + data.enforceInterface(ILauncherOverlayCallback.class.getName()); + overlayStatusChanged(data.readInt()); + default: + return super.onTransact(code, data, reply, flags); + } + } + + private static class Proxy implements ILauncherOverlayCallback { + private IBinder mRemote; + + public Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public void overlayScrollChanged(float progress) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlayCallback.class.getName()); + _data.writeFloat(progress); + + mRemote.transact(OVERLAY_SCROLL_CHANGED_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + + public void overlayStatusChanged(int status) throws RemoteException { + Parcel _data = Parcel.obtain(); + try { + _data.writeInterfaceToken(ILauncherOverlayCallback.class.getName()); + _data.writeInt(status); + + mRemote.transact(OVERLAY_STATUS_CHANGED_TRANSACTION, _data, null, FLAG_ONEWAY); + } finally { + _data.recycle(); + } + } + } + + } +}
\ No newline at end of file diff --git a/src/com/google/android/libraries/launcherclient/LauncherClient.java b/src/com/google/android/libraries/launcherclient/LauncherClient.java new file mode 100644 index 000000000..5a1985600 --- /dev/null +++ b/src/com/google/android/libraries/launcherclient/LauncherClient.java @@ -0,0 +1,423 @@ +package com.google.android.libraries.launcherclient; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.graphics.Point; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PatternMatcher; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Window; +import android.view.WindowManager; + +import com.android.launcher3.Utilities; + +public class LauncherClient { + + private static AppServiceConnection sApplicationConnection; + + private final Activity mActivity; + private OverlayCallbacks mCurrentCallbacks; + private boolean mDestroyed; + private boolean mIsResumed; + private boolean mIsServiceConnected; + private LauncherClientCallbacks mLauncherClientCallbacks; + private ILauncherOverlay mOverlay; + private OverlayServiceConnection mServiceConnection; + private int mServiceConnectionOptions; + private final Intent mServiceIntent; + private int mServiceStatus; + private int mState; + private final BroadcastReceiver mUpdateReceiver; + private WindowManager.LayoutParams mWindowAttrs; + + public LauncherClient(Activity activity, LauncherClientCallbacks callbacks, + String targetPackage, boolean overlayEnabled) { + mUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reconnect(); + } + }; + mIsResumed = false; + mDestroyed = false; + mIsServiceConnected = false; + mServiceStatus = -1; + mActivity = activity; + mServiceIntent = LauncherClient.getServiceIntent(activity, targetPackage); + mLauncherClientCallbacks = callbacks; + mState = 0; + mServiceConnection = new OverlayServiceConnection(); + mServiceConnectionOptions = overlayEnabled ? 3 : 2; + + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addDataScheme("package"); + filter.addDataSchemeSpecificPart(targetPackage, PatternMatcher.PATTERN_LITERAL); + mActivity.registerReceiver(mUpdateReceiver, filter); + + reconnect(); + } + + private void applyWindowToken() { + if (mOverlay == null) { + return; + } + + try { + if (mCurrentCallbacks == null) { + mCurrentCallbacks = new OverlayCallbacks(); + } + + mCurrentCallbacks.setClient(this); + mOverlay.windowAttached(mWindowAttrs, mCurrentCallbacks, mServiceConnectionOptions); + + if (mIsResumed) { + mOverlay.onResume(); + } + else { + mOverlay.onPause(); + } + } + catch (RemoteException ignored) { + + } + } + + private boolean connectSafely(Context context, ServiceConnection conn, int flags) { + try { + return context.bindService(mServiceIntent, conn, flags | Context.BIND_AUTO_CREATE); + } + catch (SecurityException e) { + Log.e("DrawerOverlayClient", "Unable to connect to overlay service"); + return false; + } + } + + static Intent getServiceIntent(Context context, String targetPackage) { + Uri uri = Uri.parse("app://" + context.getPackageName() + ":" + Process.myUid()).buildUpon() + .appendQueryParameter("v", Integer.toString(0)) + .build(); + + return new Intent("com.android.launcher3.WINDOW_OVERLAY") + .setPackage(targetPackage) + .setData(uri); + } + + public boolean isConnected() { + return mOverlay != null; + } + + private void notifyStatusChanged(int status) { + if (mServiceStatus == status) { + return; + } + + mServiceStatus = status; + mLauncherClientCallbacks.onServiceStateChanged((status & 1) != 0, true); + } + + private void removeClient(boolean removeAppConnection) { + mDestroyed = true; + if (mIsServiceConnected) { + mActivity.unbindService(mServiceConnection); + mIsServiceConnected = false; + } + mActivity.unregisterReceiver(mUpdateReceiver); + + if (mCurrentCallbacks != null) { + mCurrentCallbacks.clear(); + mCurrentCallbacks = null; + } + + if (removeAppConnection && sApplicationConnection != null) { + mActivity.getApplicationContext().unbindService(sApplicationConnection); + sApplicationConnection = null; + } + } + + private void setWindowAttrs(WindowManager.LayoutParams windowAttrs) { + mWindowAttrs = windowAttrs; + if (mWindowAttrs != null) { + applyWindowToken(); + } + else if (mOverlay != null) { + try { + mOverlay.windowDetached(mActivity.isChangingConfigurations()); + } + catch (RemoteException ignored) { + + } + mOverlay = null; + } + } + + public void endMove() { + if (!isConnected()) { + return; + } + + try { + mOverlay.endScroll(); + } + catch (RemoteException ignored) { + + } + } + + public void hideOverlay(boolean animate) { + if (mOverlay == null) { + return; + } + + try { + mOverlay.closeOverlay(animate ? 1 : 0); + } + catch (RemoteException ignored) { + + } + } + + public final void onAttachedToWindow() { + if (mDestroyed) { + return; + } + + setWindowAttrs(mActivity.getWindow().getAttributes()); + } + + public void onDestroy() { + removeClient(!mActivity.isChangingConfigurations()); + } + + public final void onDetachedFromWindow() { + if (mDestroyed) { + return; + } + + setWindowAttrs(null); + } + + public void onPause() { + if (mDestroyed) { + return; + } + + mIsResumed = false; + if (mOverlay != null && mWindowAttrs != null) { + try { + mOverlay.onPause(); + } + catch (RemoteException ignored) { + + } + } + } + + public void onResume() { + if (mDestroyed) { + return; + } + + reconnect(); + mIsResumed = true; + if (mOverlay != null && mWindowAttrs != null) { + try { + mOverlay.onResume(); + } + catch (RemoteException ignored) { + + } + } + } + + public void reconnect() { + if (mDestroyed || mState != 0) { + return; + } + + if (sApplicationConnection != null && + !sApplicationConnection.packageName.equals(mServiceIntent.getPackage())) { + mActivity.getApplicationContext().unbindService(sApplicationConnection); + } + + if (sApplicationConnection == null) { + sApplicationConnection = new AppServiceConnection(mServiceIntent.getPackage()); + + if (!connectSafely(mActivity.getApplicationContext(), sApplicationConnection, + Context.BIND_WAIVE_PRIORITY)) { + sApplicationConnection = null; + } + } + + if (sApplicationConnection != null) { + mState = 2; + + if (!connectSafely(mActivity, mServiceConnection, Context.BIND_ADJUST_WITH_ACTIVITY)) { + mState = 0; + } else { + mIsServiceConnected = true; + } + } + + if (mState == 0) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + notifyStatusChanged(0); + } + }); + } + } + + public void startMove() { + if (!isConnected()) { + return; + } + + try { + mOverlay.startScroll(); + } + catch (RemoteException ignored) { + + } + } + + public void updateMove(float progressX) { + if (!isConnected()) { + return; + } + + try { + mOverlay.onScroll(progressX); + } + catch (RemoteException ignored) { + + } + } + + + final class AppServiceConnection implements ServiceConnection { + public final String packageName; + + public AppServiceConnection(String pkg) { + packageName = pkg; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + + } + + public void onServiceDisconnected(ComponentName name) { + if (name.getPackageName().equals(packageName)) { + sApplicationConnection = null; + } + } + } + + private static class OverlayCallbacks extends ILauncherOverlayCallback.Stub + implements Handler.Callback { + private LauncherClient mClient; + private final Handler mUIHandler; + private Window mWindow; + private boolean mWindowHidden; + private WindowManager mWindowManager; + private int mWindowShift; + + public OverlayCallbacks() { + mWindowHidden = false; + mUIHandler = new Handler(Looper.getMainLooper(), this); + } + + private void hideActivityNonUI(boolean isHidden) { + if (mWindowHidden != isHidden) { + mWindowHidden = isHidden; + } + } + + public void clear() { + mClient = null; + mWindowManager = null; + mWindow = null; + } + + public boolean handleMessage(Message msg) { + if (mClient == null) { + return true; + } + + switch (msg.what) { + case 2: + if ((mClient.mServiceStatus & 1) != 0) { + mClient.mLauncherClientCallbacks.onOverlayScrollChanged((float) msg.obj); + } + return true; + case 3: + WindowManager.LayoutParams attrs = mWindow.getAttributes(); + if ((boolean) msg.obj) { + attrs.x = mWindowShift; + attrs.flags = attrs.flags | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + } + mWindowManager.updateViewLayout(mWindow.getDecorView(), attrs); + return true; + case 4: + mClient.notifyStatusChanged(msg.arg1); + return true; + default: + return false; + } + } + + public void overlayScrollChanged(float progress) throws RemoteException { + mUIHandler.removeMessages(2); + Message.obtain(mUIHandler, 2, progress).sendToTarget(); + + if (progress > 0) { + hideActivityNonUI(false); + } + } + + public void overlayStatusChanged(int status) { + Message.obtain(mUIHandler, 4, status, 0).sendToTarget(); + } + + public void setClient(LauncherClient client) { + mClient = client; + mWindowManager = client.mActivity.getWindowManager(); + + Point p = new Point(); + mWindowManager.getDefaultDisplay().getRealSize(p); + mWindowShift = Math.max(p.x, p.y); + + mWindow = client.mActivity.getWindow(); + } + } + + private class OverlayServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName name, IBinder service) { + mState = 1; + mOverlay = ILauncherOverlay.Stub.asInterface(service); + if (mWindowAttrs != null) { + applyWindowToken(); + } + } + + public void onServiceDisconnected(ComponentName name) { + mState = 0; + mOverlay = null; + notifyStatusChanged(0); + } + } + + +}
\ No newline at end of file diff --git a/src/com/google/android/libraries/launcherclient/LauncherClientCallbacks.java b/src/com/google/android/libraries/launcherclient/LauncherClientCallbacks.java new file mode 100644 index 000000000..e21440bfa --- /dev/null +++ b/src/com/google/android/libraries/launcherclient/LauncherClientCallbacks.java @@ -0,0 +1,7 @@ +package com.google.android.libraries.launcherclient; + +public interface LauncherClientCallbacks { + void onOverlayScrollChanged(float progress); + + void onServiceStateChanged(boolean overlayAttached, boolean hotwordActive); +} diff --git a/src/com/google/android/libraries/launcherclient/LauncherClientCallbacksAdapter.java b/src/com/google/android/libraries/launcherclient/LauncherClientCallbacksAdapter.java new file mode 100644 index 000000000..6fbcb64be --- /dev/null +++ b/src/com/google/android/libraries/launcherclient/LauncherClientCallbacksAdapter.java @@ -0,0 +1,13 @@ +package com.google.android.libraries.launcherclient; + +public class LauncherClientCallbacksAdapter implements LauncherClientCallbacks { + @Override + public void onOverlayScrollChanged(float progress) { + + } + + @Override + public void onServiceStateChanged(boolean overlayAttached, boolean hotwordActive) { + + } +} |