summaryrefslogtreecommitdiff
path: root/core/java/android/app/InstantAppResolverService.java
blob: a7be5421adb2d87f293b1c6a02df9c8cc39f67c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
 * Copyright (C) 2017 The Android Open Source 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 android.app;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.InstantAppResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;

import com.android.internal.os.SomeArgs;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Base class for implementing the resolver service.
 * @hide
 */
@SystemApi
public abstract class InstantAppResolverService extends Service {
    private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
    private static final String TAG = "PackageManager";

    /** @hide */
    public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
    /** @hide */
    public static final String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
    Handler mHandler;

    /**
     * Called to retrieve resolve info for instant applications immediately.
     *
     * @param digestPrefix The hash prefix of the instant app's domain.
     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
     *             String, InstantAppResolutionCallback)}.
     */
    @Deprecated
    public void onGetInstantAppResolveInfo(@Nullable int[] digestPrefix, @NonNull String token,
            @NonNull InstantAppResolutionCallback callback) {
        throw new IllegalStateException("Must define onGetInstantAppResolveInfo");
    }

    /**
     * Called to retrieve intent filters for instant applications from potentially expensive
     * sources.
     *
     * @param digestPrefix The hash prefix of the instant app's domain.
     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
     *             String, InstantAppResolutionCallback)}.
     */
    @Deprecated
    public void onGetInstantAppIntentFilter(@Nullable int[] digestPrefix, @NonNull String token,
            @NonNull InstantAppResolutionCallback callback) {
        throw new IllegalStateException("Must define onGetInstantAppIntentFilter");
    }

    /**
     * Called to retrieve resolve info for instant applications immediately. The response will be
     * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
     * in response to this method may be partial to request a second phase of resolution which will
     * result in a subsequent call to
     * {@link #onGetInstantAppIntentFilter(Intent, int[], String, InstantAppResolutionCallback)}
     *
     * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent
     *                        is an intent with potential PII removed from the original intent.
     *                        Fields removed include extras and the host + path of the data, if
     *                        defined.
     * @param hostDigestPrefix The hash prefix of the instant app's domain.
     * @param token A unique identifier that will be provided in calls to
     *              {@link #onGetInstantAppIntentFilter(Intent, int[], String,
     *              InstantAppResolutionCallback)}
     *              and provided to the installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN} to
     *              tie a single launch together.
     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
     *
     * @see InstantAppResolveInfo
     *
     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
     *             String, InstantAppResolutionCallback)}.
     */
    @Deprecated
    public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
            @Nullable int[] hostDigestPrefix, @NonNull String token,
            @NonNull InstantAppResolutionCallback callback) {
        // if not overridden, forward to old methods and filter out non-web intents
        if (sanitizedIntent.isWebIntent()) {
            onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
        } else {
            callback.onInstantAppResolveInfo(Collections.emptyList());
        }
    }

    /**
     * Called to retrieve intent filters for potentially matching instant applications. Unlike
     * {@link #onGetInstantAppResolveInfo(Intent, int[], String, InstantAppResolutionCallback)},
     * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
     * provided in response to this method must be completely populated.
     *
     * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
     * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
     *                         defined.
     * @param token A unique identifier that was provided in
     *              {@link #onGetInstantAppResolveInfo(Intent, int[], String,
     *              InstantAppResolutionCallback)}
     *              and provided to the currently visible installer via
     *              {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
     *
     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
     *             String, InstantAppResolutionCallback)}.
     */
    @Deprecated
    public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
            @Nullable int[] hostDigestPrefix,
            @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
        Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
        // if not overridden, forward to old methods and filter out non-web intents
        if (sanitizedIntent.isWebIntent()) {
            onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
        } else {
            callback.onInstantAppResolveInfo(Collections.emptyList());
        }
    }

    /**
     * Called to retrieve resolve info for instant applications immediately. The response will be
     * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
     * in response to this method may be partial to request a second phase of resolution which will
     * result in a subsequent call to {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
     * String, InstantAppResolutionCallback)}
     *
     * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent
     *                        is an intent with potential PII removed from the original intent.
     *                        Fields removed include extras and the host + path of the data, if
     *                        defined.
     * @param hostDigestPrefix The hash prefix of the instant app's domain.
     * @param userHandle The user for which to resolve the instant app.
     * @param token A unique identifier that will be provided in calls to {@link
     *              #onGetInstantAppIntentFilter(Intent, int[], UserHandle, String,
     *              InstantAppResolutionCallback)} and provided to the installer via {@link
     *              Intent#EXTRA_INSTANT_APP_TOKEN} to tie a single launch together.
     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
     *
     * @see InstantAppResolveInfo
     */
    public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
            @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
            @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
        // If not overridden, forward to the old method.
        onGetInstantAppResolveInfo(sanitizedIntent, hostDigestPrefix, token, callback);
    }

    /**
     * Called to retrieve intent filters for potentially matching instant applications. Unlike
     * {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle, String,
     * InstantAppResolutionCallback)}, the response may take as long as necessary to respond. All
     * {@link InstantAppResolveInfo}s provided in response to this method must be completely
     * populated.
     *
     * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
     * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
     *                         defined.
     * @param userHandle The user for which to resolve the instant app.
     * @param token A unique identifier that was provided in {@link #onGetInstantAppResolveInfo(
     *              Intent, int[], UserHandle, String, InstantAppResolutionCallback)} and provided
     *              to the currently visible installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
     */
    public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
            @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
            @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
        // If not overridden, forward to the old method.
        onGetInstantAppIntentFilter(sanitizedIntent, hostDigestPrefix, token, callback);
    }

    /**
     * Returns a {@link Looper} to perform service operations on.
     */
    Looper getLooper() {
        return getBaseContext().getMainLooper();
    }

    @Override
    public final void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        mHandler = new ServiceHandler(getLooper());
    }

    @Override
    public final IBinder onBind(Intent intent) {
        return new IInstantAppResolver.Stub() {
            @Override
            public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
                    int userId, String token, int sequence, IRemoteCallback callback) {
                if (DEBUG_INSTANT) {
                    Slog.v(TAG, "[" + token + "] Phase1 called; posting");
                }
                final SomeArgs args = SomeArgs.obtain();
                args.arg1 = callback;
                args.arg2 = digestPrefix;
                args.arg3 = userId;
                args.arg4 = token;
                args.arg5 = sanitizedIntent;
                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
                        sequence, 0, args).sendToTarget();
            }

            @Override
            public void getInstantAppIntentFilterList(Intent sanitizedIntent,
                    int[] digestPrefix, int userId, String token, IRemoteCallback callback) {
                if (DEBUG_INSTANT) {
                    Slog.v(TAG, "[" + token + "] Phase2 called; posting");
                }
                final SomeArgs args = SomeArgs.obtain();
                args.arg1 = callback;
                args.arg2 = digestPrefix;
                args.arg3 = userId;
                args.arg4 = token;
                args.arg5 = sanitizedIntent;
                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
                        args).sendToTarget();
            }
        };
    }

    /**
     * Callback to post results from instant app resolution.
     */
    public static final class InstantAppResolutionCallback {
        private final IRemoteCallback mCallback;
        private final int mSequence;
        InstantAppResolutionCallback(int sequence, IRemoteCallback callback) {
            mCallback = callback;
            mSequence = sequence;
        }

        public void onInstantAppResolveInfo(List<InstantAppResolveInfo> resolveInfo) {
            final Bundle data = new Bundle();
            data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
            data.putInt(EXTRA_SEQUENCE, mSequence);
            try {
                mCallback.sendResult(data);
            } catch (RemoteException e) {
            }
        }
    }

    private final class ServiceHandler extends Handler {
        public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1;
        public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2;
        public ServiceHandler(Looper looper) {
            super(looper, null /*callback*/, true /*async*/);
        }

        @Override
        @SuppressWarnings("unchecked")
        public void handleMessage(Message message) {
            final int action = message.what;
            switch (action) {
                case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
                    final SomeArgs args = (SomeArgs) message.obj;
                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
                    final int[] digestPrefix = (int[]) args.arg2;
                    final int userId = (int) args.arg3;
                    final String token = (String) args.arg4;
                    final Intent intent = (Intent) args.arg5;
                    final int sequence = message.arg1;
                    if (DEBUG_INSTANT) {
                        Slog.d(TAG, "[" + token + "] Phase1 request;"
                                + " prefix: " + Arrays.toString(digestPrefix)
                                + ", userId: " + userId);
                    }
                    onGetInstantAppResolveInfo(intent, digestPrefix, UserHandle.of(userId), token,
                            new InstantAppResolutionCallback(sequence, callback));
                } break;

                case MSG_GET_INSTANT_APP_INTENT_FILTER: {
                    final SomeArgs args = (SomeArgs) message.obj;
                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
                    final int[] digestPrefix = (int[]) args.arg2;
                    final int userId = (int) args.arg3;
                    final String token = (String) args.arg4;
                    final Intent intent = (Intent) args.arg5;
                    if (DEBUG_INSTANT) {
                        Slog.d(TAG, "[" + token + "] Phase2 request;"
                                + " prefix: " + Arrays.toString(digestPrefix)
                                + ", userId: " + userId);
                    }
                    onGetInstantAppIntentFilter(intent, digestPrefix, UserHandle.of(userId), token,
                            new InstantAppResolutionCallback(-1 /*sequence*/, callback));
                } break;

                default: {
                    throw new IllegalArgumentException("Unknown message: " + action);
                }
            }
        }
    }
}