1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.util; 17 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.ServiceConnection; 22 import android.os.Handler; 23 import android.os.IBinder; 24 import android.os.Looper; 25 import android.util.Log; 26 27 import androidx.annotation.Nullable; 28 29 import com.android.launcher3.taskbar.TaskbarManager; 30 import com.android.quickstep.OverviewCommandHelper; 31 import com.android.quickstep.TouchInteractionService; 32 import com.android.quickstep.TouchInteractionService.TISBinder; 33 34 import java.util.ArrayList; 35 import java.util.function.Consumer; 36 37 /** 38 * Utility class to simplify binding to {@link TouchInteractionService} 39 */ 40 public class TISBindHelper implements ServiceConnection { 41 42 private static final String TAG = "TISBindHelper"; 43 44 private static final long BACKOFF_MILLIS = 1000; 45 46 // Max backoff caps at 5 mins 47 private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; 48 49 private final Handler mHandler = new Handler(Looper.getMainLooper()); 50 private final Runnable mConnectionRunnable = this::internalBindToTIS; 51 private final Context mContext; 52 private final Consumer<TISBinder> mConnectionCallback; 53 private final ArrayList<Runnable> mPendingConnectedCallbacks = new ArrayList<>(); 54 55 private short mConnectionAttempts; 56 private boolean mTisServiceBound; 57 private boolean mIsConnected; 58 @Nullable private TISBinder mBinder; 59 TISBindHelper(Context context, Consumer<TISBinder> connectionCallback)60 public TISBindHelper(Context context, Consumer<TISBinder> connectionCallback) { 61 mContext = context; 62 mConnectionCallback = connectionCallback; 63 internalBindToTIS(); 64 } 65 66 @Override onServiceConnected(ComponentName componentName, IBinder iBinder)67 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 68 if (!(iBinder instanceof TISBinder)) { 69 // Seems like there can be a race condition when user unlocks, which kills the TIS 70 // process and re-starts it. I guess in the meantime service can be connected to 71 // a killed TIS? Either way, unbind and try to re-connect in that case. 72 internalUnbindToTIS(); 73 mHandler.postDelayed(mConnectionRunnable, BACKOFF_MILLIS); 74 return; 75 } 76 77 Log.d(TAG, "TIS service connected"); 78 mIsConnected = true; 79 mBinder = (TISBinder) iBinder; 80 mConnectionCallback.accept(mBinder); 81 // Flush the pending callbacks 82 for (Runnable r : mPendingConnectedCallbacks) { 83 r.run(); 84 } 85 mPendingConnectedCallbacks.clear(); 86 resetServiceBindRetryState(); 87 } 88 89 @Override onServiceDisconnected(ComponentName componentName)90 public void onServiceDisconnected(ComponentName componentName) { 91 Log.d(TAG, "TIS service disconnected"); 92 mBinder = null; 93 mIsConnected = false; 94 } 95 96 @Override onBindingDied(ComponentName name)97 public void onBindingDied(ComponentName name) { 98 Log.w(TAG, "TIS binding died"); 99 internalBindToTIS(); 100 } 101 102 @Nullable getBinder()103 public TISBinder getBinder() { 104 return mBinder; 105 } 106 107 @Nullable getTaskbarManager()108 public TaskbarManager getTaskbarManager() { 109 return mBinder == null ? null : mBinder.getTaskbarManager(); 110 } 111 112 /** 113 * Sets flag whether a predictive back-to-home animation is in progress 114 */ setPredictiveBackToHomeInProgress(boolean isInProgress)115 public void setPredictiveBackToHomeInProgress(boolean isInProgress) { 116 if (mBinder != null) { 117 mBinder.setPredictiveBackToHomeInProgress(isInProgress); 118 } 119 } 120 121 @Nullable getOverviewCommandHelper()122 public OverviewCommandHelper getOverviewCommandHelper() { 123 return mBinder == null ? null : mBinder.getOverviewCommandHelper(); 124 } 125 126 /** 127 * Runs the given {@param r} runnable when the service is connected. 128 */ runOnBindToTouchInteractionService(Runnable r)129 public void runOnBindToTouchInteractionService(Runnable r) { 130 if (mIsConnected) { 131 r.run(); 132 } else { 133 mPendingConnectedCallbacks.add(r); 134 } 135 } 136 137 /** 138 * Binds to {@link TouchInteractionService}. If the binding fails, attempts to retry via 139 * {@link #mConnectionRunnable}. Unbind via {@link #internalUnbindToTIS()} 140 */ internalBindToTIS()141 private void internalBindToTIS() { 142 mTisServiceBound = mContext.bindService(new Intent(mContext, TouchInteractionService.class), 143 this, 0); 144 if (mTisServiceBound) { 145 resetServiceBindRetryState(); 146 return; 147 } 148 149 Log.w(TAG, "Retrying TIS Binder connection attempt: " + mConnectionAttempts); 150 final long timeoutMs = (long) Math.min( 151 Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS); 152 mHandler.postDelayed(mConnectionRunnable, timeoutMs); 153 mConnectionAttempts++; 154 } 155 156 /** See {@link #internalBindToTIS()} */ internalUnbindToTIS()157 private void internalUnbindToTIS() { 158 if (mTisServiceBound) { 159 mContext.unbindService(this); 160 mTisServiceBound = false; 161 } 162 } 163 resetServiceBindRetryState()164 private void resetServiceBindRetryState() { 165 if (mHandler.hasCallbacks(mConnectionRunnable)) { 166 mHandler.removeCallbacks(mConnectionRunnable); 167 } 168 mConnectionAttempts = 0; 169 } 170 171 /** 172 * Called when the activity is destroyed to clear the binding 173 */ onDestroy()174 public void onDestroy() { 175 internalUnbindToTIS(); 176 resetServiceBindRetryState(); 177 mBinder = null; 178 mIsConnected = false; 179 mPendingConnectedCallbacks.clear(); 180 } 181 } 182