More QS Tile modes in the API

Allow tiles to request when they are bound instead of doing it
automatically for them when in the listening state.  Only one
of these modes is allowed for a given tile, meaning it can either
push updates when it thinks they matter, or it can be told when
to update.

Change-Id: I165b39dddb836df90d253aeb5ebea48e62ea0dae
diff --git a/api/current.txt b/api/current.txt
index 30d3020..1a8185a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -33631,10 +33631,13 @@
     method public void onClick();
     method public void onStartListening();
     method public void onStopListening();
-    method public void onTileAdded();
+    method public int onTileAdded();
     method public void onTileRemoved();
+    method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
     method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+    field public static final int TILE_MODE_ACTIVE = 2; // 0x2
+    field public static final int TILE_MODE_PASSIVE = 1; // 0x1
   }
 
 }
diff --git a/api/system-current.txt b/api/system-current.txt
index d7393b7..7e30350 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -35805,10 +35805,13 @@
     method public void onClick();
     method public void onStartListening();
     method public void onStopListening();
-    method public void onTileAdded();
+    method public int onTileAdded();
     method public void onTileRemoved();
+    method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
     method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+    field public static final int TILE_MODE_ACTIVE = 2; // 0x2
+    field public static final int TILE_MODE_PASSIVE = 1; // 0x1
   }
 
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index 9058fe7..db9cc98 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -33634,10 +33634,13 @@
     method public void onClick();
     method public void onStartListening();
     method public void onStopListening();
-    method public void onTileAdded();
+    method public int onTileAdded();
     method public void onTileRemoved();
+    method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
     method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+    field public static final int TILE_MODE_ACTIVE = 2; // 0x2
+    field public static final int TILE_MODE_PASSIVE = 1; // 0x1
   }
 
 }
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
index 7e70501..75da82f 100644
--- a/core/java/android/service/quicksettings/IQSService.aidl
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -24,4 +24,5 @@
 interface IQSService {
     void updateQsTile(in Tile tile);
     void onShowDialog(in Tile tile);
+    void setTileMode(in ComponentName component, int mode);
 }
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
index 63a4c5e..4997f75 100644
--- a/core/java/android/service/quicksettings/IQSTileService.aidl
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -22,6 +22,7 @@
  * @hide
  */
 oneway interface IQSTileService {
+    void setQSService(in IQSService service);
     void setQSTile(in Tile tile);
     void onTileAdded();
     void onTileRemoved();
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index a53fc59..6104913 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -37,11 +37,12 @@
     private static final String TAG = "Tile";
 
     private ComponentName mComponentName;
-    private IQSService mService;
     private Icon mIcon;
     private CharSequence mLabel;
     private CharSequence mContentDescription;
 
+    private IQSService mService;
+
     /**
      * @hide
      */
@@ -52,8 +53,14 @@
     /**
      * @hide
      */
-    public Tile(ComponentName componentName, IQSService service) {
+    public Tile(ComponentName componentName) {
         mComponentName = componentName;
+    }
+
+    /**
+     * @hide
+     */
+    public void setService(IQSService service) {
         mService = service;
     }
 
@@ -65,6 +72,13 @@
     }
 
     /**
+     * @hide
+     */
+    public IQSService getQsService() {
+        return mService;
+    }
+
+    /**
      * Gets the current icon for the tile.
      */
     public Icon getIcon() {
@@ -137,21 +151,8 @@
         }
     }
 
-    /**
-     * @hide
-     * Notifies the IQSService that this tile is showing a dialog.
-     */
-    void onShowDialog() {
-        try {
-            mService.onShowDialog(this);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't onShowDialog");
-        }
-    }
-
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongInterface(mService);
         if (mComponentName != null) {
             dest.writeByte((byte) 1);
             mComponentName.writeToParcel(dest, flags);
@@ -169,7 +170,6 @@
     }
 
     private void readFromParcel(Parcel source) {
-        mService = IQSService.Stub.asInterface(source.readStrongBinder());
         if (source.readByte() != 0) {
             mComponentName = ComponentName.CREATOR.createFromParcel(source);
         } else {
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 55fe4cd..1e134c7 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -15,8 +15,11 @@
  */
 package android.service.quicksettings;
 
+import android.Manifest;
 import android.app.Dialog;
 import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,7 +45,7 @@
  * <li>When a tile should be up to date and listing will be indicated by
  * {@link #onStartListening()} and {@link #onStopListening()}.</li>
  *
- * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
+ * <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
  * will be called.</li>
  * </ul>
  * <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
@@ -71,11 +74,48 @@
      */
     public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
 
+    /**
+     * The tile mode hasn't been set yet.
+     * @hide
+     */
+    public static final int TILE_MODE_UNSET = 0;
+
+    /**
+     * Constant to be returned by {@link #onTileAdded}.
+     * <p>
+     * Passive mode is the default mode for tiles.  The System will tell the tile
+     * when it is most important to update by putting it in the listening state.
+     */
+    public static final int TILE_MODE_PASSIVE = 1;
+
+    /**
+     * Constant to be returned by {@link #onTileAdded}.
+     * <p>
+     * Active mode is for tiles which already listen and keep track of their state in their
+     * own process.  These tiles may request to send an update to the System while their process
+     * is alive using {@link #requestListeningState}.  The System will only bind these tiles
+     * on its own when a click needs to occur.
+     */
+    public static final int TILE_MODE_ACTIVE = 2;
+
+    /**
+     * Used to notify SysUI that Listening has be requested.
+     * @hide
+     */
+    public static final String ACTION_REQUEST_LISTENING
+            = "android.service.quicksettings.action.REQUEST_LISTENING";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
+
     private final H mHandler = new H(Looper.getMainLooper());
 
     private boolean mListening = false;
     private Tile mTile;
     private IBinder mToken;
+    private IQSService mService;
 
     @Override
     public void onDestroy() {
@@ -92,8 +132,12 @@
      * Note that this is not guaranteed to be called between {@link #onCreate()}
      * and {@link #onStartListening()}, it will only be called when the tile is added
      * and not on subsequent binds.
+     *
+     * @see #TILE_MODE_PASSIVE
+     * @see #TILE_MODE_ACTIVE
      */
-    public void onTileAdded() {
+    public int onTileAdded() {
+        return TILE_MODE_PASSIVE;
     }
 
     /**
@@ -138,7 +182,10 @@
         dialog.getWindow().getAttributes().token = mToken;
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
         dialog.show();
-        getQsTile().onShowDialog();
+        try {
+            mService.onShowDialog(mTile);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -156,6 +203,11 @@
     public IBinder onBind(Intent intent) {
         return new IQSTileService.Stub() {
             @Override
+            public void setQSService(IQSService service) throws RemoteException {
+                mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
+            }
+
+            @Override
             public void setQSTile(Tile tile) throws RemoteException {
                 mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
             }
@@ -194,6 +246,7 @@
         private static final int MSG_TILE_ADDED = 4;
         private static final int MSG_TILE_REMOVED = 5;
         private static final int MSG_TILE_CLICKED = 6;
+        private static final int MSG_SET_SERVICE = 7;
 
         public H(Looper looper) {
             super(looper);
@@ -202,11 +255,28 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_SET_SERVICE:
+                    mService = (IQSService) msg.obj;
+                    if (mTile != null) {
+                        mTile.setService(mService);
+                    }
+                    break;
                 case MSG_SET_TILE:
                     mTile = (Tile) msg.obj;
+                    if (mService != null && mTile != null) {
+                        mTile.setService(mService);
+                    }
                     break;
                 case MSG_TILE_ADDED:
-                    TileService.this.onTileAdded();
+                    int mode = TileService.this.onTileAdded();
+                    if (mService == null) {
+                        return;
+                    }
+                    try {
+                        mService.setTileMode(new ComponentName(TileService.this,
+                                TileService.this.getClass()), mode);
+                    } catch (RemoteException e) {
+                    }
                     break;
                 case MSG_TILE_REMOVED:
                     TileService.this.onTileRemoved();
@@ -230,4 +300,16 @@
             }
         }
     }
+
+    /**
+     * Requests that a tile be put in the listening state so it can send an update.
+     *
+     * This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from
+     * {@link #onTileAdded()}, and will do nothing otherwise.
+     */
+    public static final void requestListeningState(Context context, ComponentName component) {
+        Intent intent = new Intent(ACTION_REQUEST_LISTENING);
+        intent.putExtra(EXTRA_COMPONENT, component);
+        context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index e622e11..2e5a0b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManager;
@@ -59,7 +60,7 @@
         mComponent = ComponentName.unflattenFromString(action);
         mServiceManager = host.getTileServices().getTileWrapper(this);
         mService = mServiceManager.getTileService();
-        mTile = new Tile(mComponent, host.getTileServices());
+        mTile = new Tile(mComponent);
         try {
             PackageManager pm = mContext.getPackageManager();
             ServiceInfo info = pm.getServiceInfo(mComponent, 0);
@@ -68,6 +69,11 @@
             mTile.setLabel(info.loadLabel(pm));
         } catch (Exception e) {
         }
+        try {
+            mService.setQSTile(mTile);
+        } catch (RemoteException e) {
+            // Called through wrapper, won't happen here.
+        }
     }
 
     public ComponentName getComponent() {
@@ -94,9 +100,10 @@
         mListening = listening;
         try {
             if (listening) {
-                mServiceManager.setBindRequested(true);
-                mService.setQSTile(mTile);
-                mService.onStartListening();
+                if (mServiceManager.getType() == TileService.TILE_MODE_PASSIVE) {
+                    mServiceManager.setBindRequested(true);
+                    mService.onStartListening();
+                }
             } else {
                 mService.onStopListening();
                 if (mIsTokenGranted && !mIsShowingDialog) {
@@ -139,20 +146,20 @@
 
     @Override
     protected void handleClick() {
-        if (mService != null) {
-            try {
-                if (DEBUG) Log.d(TAG, "Adding token");
-                mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
-                mIsTokenGranted = true;
-            } catch (RemoteException e) {
+        try {
+            if (DEBUG) Log.d(TAG, "Adding token");
+            mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
+            mIsTokenGranted = true;
+        } catch (RemoteException e) {
+        }
+        try {
+            if (mServiceManager.getType() == TileService.TILE_MODE_ACTIVE) {
+                mServiceManager.setBindRequested(true);
+                mService.onStartListening();
             }
-            try {
-                mService.onClick(mToken);
-            } catch (RemoteException e) {
-                // Called through wrapper, won't happen here.
-            }
-        } else {
-            Log.e(TAG, "Click with no service " + getTileSpec());
+            mService.onClick(mToken);
+        } catch (RemoteException e) {
+            // Called through wrapper, won't happen here.
         }
         MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index d656686..d41cdde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -16,6 +16,7 @@
 package com.android.systemui.qs.external;
 
 import android.os.IBinder;
+import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.util.Log;
@@ -93,4 +94,14 @@
             return false;
         }
     }
+
+    public boolean setQSService(IQSService service) {
+        try {
+            mService.setQSService(service);
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 500ee19..41fce9f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.support.annotation.VisibleForTesting;
@@ -74,6 +75,7 @@
     private boolean mBound;
     @VisibleForTesting
     boolean mReceiverRegistered;
+    private IQSService mService;
 
     public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) {
         mContext = context;
@@ -82,6 +84,10 @@
         mUser = user;
     }
 
+    public ComponentName getComponent() {
+        return mIntent.getComponent();
+    }
+
     public boolean hasPendingClick() {
         synchronized (mQueuedMessages) {
             return mQueuedMessages.contains(MSG_ON_CLICK);
@@ -108,6 +114,7 @@
             if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent);
             // Give it another chance next time it needs to be bound, out of kindness.
             mBindTryCount = 0;
+            mWrapper = null;
             mContext.unbindService(this);
         }
     }
@@ -122,6 +129,8 @@
             service.linkToDeath(this, 0);
         } catch (RemoteException e) {
         }
+        setQSService(mService);
+        setQSTile(mTile);
         handlePendingMessages();
     }
 
@@ -145,7 +154,6 @@
         }
         if (mListening) {
             if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
-            setQSTile(mTile);
             onStartListening();
         }
         if (queue.contains(MSG_ON_CLICK)) {
@@ -273,6 +281,14 @@
     }
 
     @Override
+    public void setQSService(IQSService service) {
+        mService = service;
+        if (mWrapper == null || !mWrapper.setQSService(service)) {
+            handleDeath();
+        }
+    }
+
+    @Override
     public void onTileAdded() {
         if (DEBUG) Log.d(TAG, "onTileAdded");
         if (mWrapper == null || !mWrapper.onTileAdded()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index ca589df..2f77a30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -21,6 +21,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.TileService;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
@@ -38,6 +39,9 @@
 
     private static final String TAG = "TileServiceManager";
 
+    @VisibleForTesting
+    static final String PREFS_FILE = "CustomTileModes";
+
     private final TileServices mServices;
     private final TileLifecycleManager mStateManager;
     private final Handler mHandler;
@@ -47,6 +51,7 @@
     private int mPriority;
     private boolean mJustBound;
     private long mLastUpdate;
+    private int mType;
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component) {
         this(tileServices, handler, new TileLifecycleManager(handler,
@@ -60,6 +65,25 @@
         mServices = tileServices;
         mHandler = handler;
         mStateManager = tileLifecycleManager;
+        mType = tileServices.getContext().getSharedPreferences(PREFS_FILE, 0)
+                .getInt(tileLifecycleManager.getComponent().flattenToString(),
+                        TileService.TILE_MODE_UNSET);
+        mStateManager.setQSService(tileServices);
+        if (mType == TileService.TILE_MODE_UNSET) {
+            bindService();
+            mStateManager.onTileAdded();
+        }
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public void setType(int type) {
+        mServices.getContext().getSharedPreferences(PREFS_FILE, 0).edit()
+                .putInt(mStateManager.getComponent().flattenToString(), type).commit();
+        mType = type;
+        mServices.recalculateBindAllowance();
     }
 
     public IQSTileService getTileService() {
@@ -70,17 +94,22 @@
         if (mBindRequested == bindRequested) return;
         mBindRequested = bindRequested;
         if (mBindAllowed && mBindRequested && !mBound) {
+            mHandler.removeCallbacks(mUnbind);
             bindService();
         } else {
             mServices.recalculateBindAllowance();
         }
         if (mBound && !mBindRequested) {
-            // TODO: Schedule unbind.
+            mHandler.postDelayed(mUnbind, UNBIND_DELAY);
         }
     }
 
     public void setLastUpdate(long lastUpdate) {
         mLastUpdate = lastUpdate;
+        if (mBound && mType == TileService.TILE_MODE_ACTIVE) {
+            mStateManager.onStopListening();
+            setBindRequested(false);
+        }
         mServices.recalculateBindAllowance();
     }
 
@@ -148,6 +177,15 @@
         return mPriority;
     }
 
+    private final Runnable mUnbind = new Runnable() {
+        @Override
+        public void run() {
+            if (mBound && !mBindRequested) {
+                unbindService();
+            }
+        }
+    };
+
     @VisibleForTesting
     final Runnable mJustBoundOver = new Runnable() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index d110d97..00f7699 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,15 +15,21 @@
  */
 package com.android.systemui.qs.external;
 
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
 import android.util.ArrayMap;
+import android.util.Log;
 import com.android.systemui.statusbar.phone.QSTileHost;
 
 import java.util.ArrayList;
@@ -48,6 +54,8 @@
     public TileServices(QSTileHost host, Looper looper) {
         mHost = host;
         mContext = mHost.getContext();
+        mContext.registerReceiver(mRequestListeningReceiver,
+                new IntentFilter(TileService.ACTION_REQUEST_LISTENING));
         mHandler = new Handler(looper);
     }
 
@@ -121,12 +129,44 @@
         }
     }
 
+    private void requestListening(ComponentName component) {
+        synchronized (mServices) {
+            CustomTile customTile = getTileForComponent(component);
+            if (customTile == null) {
+                Log.d("TileServices", "Couldn't find tile for " + component);
+                return;
+            }
+            TileServiceManager service = mServices.get(customTile);
+            if (service.getType() != TileService.TILE_MODE_ACTIVE) {
+                return;
+            }
+            service.setBindRequested(true);
+            try {
+                service.getTileService().onStartListening();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    @Override
+    public void setTileMode(ComponentName component, int mode) {
+        verifyCaller(component.getPackageName());
+        CustomTile customTile = getTileForComponent(component);
+        if (customTile != null) {
+            synchronized (mServices) {
+                mServices.get(customTile).setType(mode);
+            }
+        }
+    }
+
     @Override
     public void updateQsTile(Tile tile) {
         verifyCaller(tile.getComponentName().getPackageName());
         CustomTile customTile = getTileForComponent(tile.getComponentName());
         if (customTile != null) {
-            mServices.get(customTile).setLastUpdate(System.currentTimeMillis());
+            synchronized (mServices) {
+                mServices.get(customTile).setLastUpdate(System.currentTimeMillis());
+            }
             customTile.updateState(tile);
             customTile.refreshState();
         }
@@ -143,9 +183,21 @@
     }
 
     private CustomTile getTileForComponent(ComponentName component) {
-        return mTiles.get(component);
+        synchronized (mServices) {
+            return mTiles.get(component);
+        }
     }
 
+    private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
+                requestListening(
+                        (ComponentName) intent.getParcelableExtra(TileService.EXTRA_COMPONENT));
+            }
+        }
+    };
+
     private static final Comparator<TileServiceManager> SERVICE_SORT =
             new Comparator<TileServiceManager>() {
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
index 0594211..6ebf488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.external;
 
+import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -23,9 +24,13 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.service.quicksettings.TileService;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
 import android.test.AndroidTestCase;
 import android.util.ArraySet;
 import android.util.Log;
@@ -238,10 +243,50 @@
         }
     };
 
-    public static class FakeTileService extends TileService {
+    public static class FakeTileService extends Service {
         public static final String ACTION_KILL = "com.android.systemui.test.KILL";
 
         @Override
+        public IBinder onBind(Intent intent) {
+            return new IQSTileService.Stub() {
+
+                @Override
+                public void setQSService(IQSService service) {
+
+                }
+
+                @Override
+                public void setQSTile(Tile tile) throws RemoteException {
+                }
+
+                @Override
+                public void onTileAdded() throws RemoteException {
+                    sendCallback("onTileAdded");
+                }
+
+                @Override
+                public void onTileRemoved() throws RemoteException {
+                    sendCallback("onTileRemoved");
+                }
+
+                @Override
+                public void onStartListening() throws RemoteException {
+                    sendCallback("onStartListening");
+                }
+
+                @Override
+                public void onStopListening() throws RemoteException {
+                    sendCallback("onStopListening");
+                }
+
+                @Override
+                public void onClick(IBinder iBinder) throws RemoteException {
+                    sendCallback("onClick");
+                }
+            };
+        }
+
+        @Override
         public void onCreate() {
             super.onCreate();
             registerReceiver(mReceiver, new IntentFilter(ACTION_KILL));
@@ -255,31 +300,6 @@
             sendCallback("onDestroy");
         }
 
-        @Override
-        public void onTileAdded() {
-            sendCallback("onTileAdded");
-        }
-
-        @Override
-        public void onTileRemoved() {
-            sendCallback("onTileRemoved");
-        }
-
-        @Override
-        public void onStartListening() {
-            sendCallback("onStartListening");
-        }
-
-        @Override
-        public void onStopListening() {
-            sendCallback("onStopListening");
-        }
-
-        @Override
-        public void onClick() {
-            sendCallback("onClick");
-        }
-
         private void sendCallback(String callback) {
             Log.d("TileLifecycleManager", "Relaying: " + callback);
             sendBroadcast(new Intent(TILE_UPDATE_BROADCAST)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
index c4f686e..4586c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
@@ -15,8 +15,10 @@
  */
 package com.android.systemui.qs.external;
 
+import android.content.ComponentName;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.service.quicksettings.TileService;
 import com.android.systemui.SysuiTestCase;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -36,7 +38,13 @@
         mThread.start();
         mHandler = new Handler(mThread.getLooper());
         mTileServices = Mockito.mock(TileServices.class);
+        Mockito.when(mTileServices.getContext()).thenReturn(mContext);
         mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
+        ComponentName componentName = new ComponentName(mContext,
+                TileServiceManagerTests.class);
+        Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
+        mContext.getSharedPreferences(TileServiceManager.PREFS_FILE, 0).edit()
+                .putInt(componentName.flattenToString(), TileService.TILE_MODE_PASSIVE).commit();
         mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
     }