1 /*
2  * Copyright (C) 2024 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 android.app.appsearch.functions;
17 
18 import android.annotation.NonNull;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.Looper;
26 import android.os.UserHandle;
27 import android.util.Log;
28 
29 import java.util.concurrent.Executor;
30 import java.util.function.Function;
31 
32 /**
33  * An implementation of {@link ServiceCallHelper} that that is based on {@link Context#bindService}.
34  *
35  * @hide
36  */
37 public class ServiceCallHelperImpl<T> implements ServiceCallHelper<T> {
38     private static final String TAG = "AppSearchAppFunction";
39 
40     @NonNull private final Context mContext;
41     @NonNull private final Function<IBinder, T> mInterfaceConverter;
42     private final Handler mHandler = new Handler(Looper.getMainLooper());
43     private final Executor mExecutor;
44 
45     /**
46      * @param interfaceConverter A function responsible for converting an IBinder object into the
47      *     desired service interface.
48      * @param executor An Executor instance to dispatch callback.
49      * @param context The system context.
50      */
ServiceCallHelperImpl( @onNull Context context, @NonNull Function<IBinder, T> interfaceConverter, @NonNull Executor executor)51     public ServiceCallHelperImpl(
52             @NonNull Context context,
53             @NonNull Function<IBinder, T> interfaceConverter,
54             @NonNull Executor executor) {
55         mContext = context;
56         mInterfaceConverter = interfaceConverter;
57         mExecutor = executor;
58     }
59 
60     @Override
runServiceCall( @onNull Intent intent, int bindFlags, long timeoutInMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback<T> callback)61     public boolean runServiceCall(
62             @NonNull Intent intent,
63             int bindFlags,
64             long timeoutInMillis,
65             @NonNull UserHandle userHandle,
66             @NonNull RunServiceCallCallback<T> callback) {
67         OneOffServiceConnection serviceConnection =
68                 new OneOffServiceConnection(
69                         intent, bindFlags, timeoutInMillis, userHandle, callback);
70 
71         return serviceConnection.bindAndRun();
72     }
73 
74     private class OneOffServiceConnection
75             implements ServiceConnection, ServiceUsageCompleteListener {
76         private final Intent mIntent;
77         private final int mFlags;
78         private final long mTimeoutMillis;
79         private final UserHandle mUserHandle;
80         private final RunServiceCallCallback<T> mCallback;
81         private final Runnable mTimeoutCallback;
82 
OneOffServiceConnection( @onNull Intent intent, int flags, long timeoutMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback<T> callback)83         OneOffServiceConnection(
84                 @NonNull Intent intent,
85                 int flags,
86                 long timeoutMillis,
87                 @NonNull UserHandle userHandle,
88                 @NonNull RunServiceCallCallback<T> callback) {
89             mIntent = intent;
90             mFlags = flags;
91             mTimeoutMillis = timeoutMillis;
92             mCallback = callback;
93             mTimeoutCallback =
94                     () ->
95                             mExecutor.execute(
96                                     () -> {
97                                         safeUnbind();
98                                         mCallback.onTimedOut();
99                                     });
100             mUserHandle = userHandle;
101         }
102 
bindAndRun()103         public boolean bindAndRun() {
104             boolean bindServiceResult =
105                     mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
106 
107             if (bindServiceResult) {
108                 mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis);
109             } else {
110                 safeUnbind();
111             }
112 
113             return bindServiceResult;
114         }
115 
116         @Override
onServiceConnected(ComponentName name, IBinder service)117         public void onServiceConnected(ComponentName name, IBinder service) {
118             T serviceInterface = mInterfaceConverter.apply(service);
119 
120             mExecutor.execute(() -> mCallback.onServiceConnected(serviceInterface, this));
121         }
122 
123         @Override
onServiceDisconnected(ComponentName name)124         public void onServiceDisconnected(ComponentName name) {
125             safeUnbind();
126             mExecutor.execute(mCallback::onFailedToConnect);
127         }
128 
129         @Override
onBindingDied(ComponentName name)130         public void onBindingDied(ComponentName name) {
131             safeUnbind();
132             mExecutor.execute(mCallback::onFailedToConnect);
133         }
134 
135         @Override
onNullBinding(ComponentName name)136         public void onNullBinding(ComponentName name) {
137             safeUnbind();
138             mExecutor.execute(mCallback::onFailedToConnect);
139         }
140 
safeUnbind()141         private void safeUnbind() {
142             try {
143                 mHandler.removeCallbacks(mTimeoutCallback);
144                 mContext.unbindService(this);
145             } catch (Exception ex) {
146                 Log.w(TAG, "Failed to unbind", ex);
147             }
148         }
149 
150         @Override
onCompleted()151         public void onCompleted() {
152             safeUnbind();
153         }
154     }
155 }
156