1/*
2 * Copyright (C) 2015 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
17package com.android.server.pm;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.content.pm.EphemeralResolveInfo;
24import android.os.Build;
25import android.os.Bundle;
26import android.os.IBinder;
27import android.os.IRemoteCallback;
28import android.os.RemoteException;
29import android.os.SystemClock;
30import android.os.UserHandle;
31import android.util.TimedRemoteCaller;
32
33import com.android.internal.app.EphemeralResolverService;
34import com.android.internal.app.IEphemeralResolver;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import java.util.ArrayList;
39import java.util.List;
40import java.util.concurrent.TimeoutException;
41
42/**
43 * Represents a remote ephemeral resolver. It is responsible for binding to the remote
44 * service and handling all interactions in a timely manner.
45 * @hide
46 */
47final class EphemeralResolverConnection {
48    // This is running in a critical section and the timeout must be sufficiently low
49    private static final long BIND_SERVICE_TIMEOUT_MS =
50            ("eng".equals(Build.TYPE)) ? 300 : 200;
51
52    private final Object mLock = new Object();
53    private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
54            new GetEphemeralResolveInfoCaller();
55    private final ServiceConnection mServiceConnection = new MyServiceConnection();
56    private final Context mContext;
57    /** Intent used to bind to the service */
58    private final Intent mIntent;
59
60    private IEphemeralResolver mRemoteInstance;
61
62    public EphemeralResolverConnection(Context context, ComponentName componentName) {
63        mContext = context;
64        mIntent = new Intent().setComponent(componentName);
65    }
66
67    public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
68        throwIfCalledOnMainThread();
69        try {
70            return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
71                    getRemoteInstanceLazy(), hashPrefix);
72        } catch (RemoteException re) {
73        } catch (TimeoutException te) {
74        } finally {
75            synchronized (mLock) {
76                mLock.notifyAll();
77            }
78        }
79        return null;
80    }
81
82    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
83        synchronized (mLock) {
84            pw.append(prefix).append("bound=")
85                    .append((mRemoteInstance != null) ? "true" : "false").println();
86
87            pw.flush();
88
89            try {
90                getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix });
91            } catch (TimeoutException te) {
92                /* ignore */
93            } catch (RemoteException re) {
94                /* ignore */
95            }
96        }
97    }
98
99    private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException {
100        synchronized (mLock) {
101            if (mRemoteInstance != null) {
102                return mRemoteInstance;
103            }
104            bindLocked();
105            return mRemoteInstance;
106        }
107    }
108
109    private void bindLocked() throws TimeoutException {
110        if (mRemoteInstance != null) {
111            return;
112        }
113
114        mContext.bindServiceAsUser(mIntent, mServiceConnection,
115                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM);
116
117        final long startMillis = SystemClock.uptimeMillis();
118        while (true) {
119            if (mRemoteInstance != null) {
120                break;
121            }
122            final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
123            final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
124            if (remainingMillis <= 0) {
125                throw new TimeoutException("Didn't bind to resolver in time.");
126            }
127            try {
128                mLock.wait(remainingMillis);
129            } catch (InterruptedException ie) {
130                /* ignore */
131            }
132        }
133
134        mLock.notifyAll();
135    }
136
137    private void throwIfCalledOnMainThread() {
138        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
139            throw new RuntimeException("Cannot invoke on the main thread");
140        }
141    }
142
143    private final class MyServiceConnection implements ServiceConnection {
144        @Override
145        public void onServiceConnected(ComponentName name, IBinder service) {
146            synchronized (mLock) {
147                mRemoteInstance = IEphemeralResolver.Stub.asInterface(service);
148                mLock.notifyAll();
149            }
150        }
151
152        @Override
153        public void onServiceDisconnected(ComponentName name) {
154            synchronized (mLock) {
155                mRemoteInstance = null;
156            }
157        }
158    }
159
160    private static final class GetEphemeralResolveInfoCaller
161            extends TimedRemoteCaller<List<EphemeralResolveInfo>> {
162        private final IRemoteCallback mCallback;
163
164        public GetEphemeralResolveInfoCaller() {
165            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
166            mCallback = new IRemoteCallback.Stub() {
167                    @Override
168                    public void sendResult(Bundle data) throws RemoteException {
169                        final ArrayList<EphemeralResolveInfo> resolveList =
170                                data.getParcelableArrayList(
171                                        EphemeralResolverService.EXTRA_RESOLVE_INFO);
172                        int sequence =
173                                data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1);
174                        onRemoteMethodResult(resolveList, sequence);
175                    }
176            };
177        }
178
179        public List<EphemeralResolveInfo> getEphemeralResolveInfoList(
180                IEphemeralResolver target, int hashPrefix)
181                        throws RemoteException, TimeoutException {
182            final int sequence = onBeforeRemoteCall();
183            target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
184            return getResultTimed(sequence);
185        }
186    }
187}
188