1 /*
2  * Copyright (C) 2017 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 
17 package android.app;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.InstantAppResolveInfo;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.IRemoteCallback;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.util.Log;
35 import android.util.Slog;
36 
37 import com.android.internal.os.SomeArgs;
38 
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 
43 /**
44  * Base class for implementing the resolver service.
45  * @hide
46  */
47 @SystemApi
48 public abstract class InstantAppResolverService extends Service {
49     private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
50     private static final String TAG = "PackageManager";
51 
52     /** @hide */
53     public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
54     /** @hide */
55     public static final String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
56     Handler mHandler;
57 
58     /**
59      * Called to retrieve resolve info for instant applications immediately.
60      *
61      * @param digestPrefix The hash prefix of the instant app's domain.
62      * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
63      *             String, InstantAppResolutionCallback)}.
64      */
65     @Deprecated
onGetInstantAppResolveInfo(@ullable int[] digestPrefix, @NonNull String token, @NonNull InstantAppResolutionCallback callback)66     public void onGetInstantAppResolveInfo(@Nullable int[] digestPrefix, @NonNull String token,
67             @NonNull InstantAppResolutionCallback callback) {
68         throw new IllegalStateException("Must define onGetInstantAppResolveInfo");
69     }
70 
71     /**
72      * Called to retrieve intent filters for instant applications from potentially expensive
73      * sources.
74      *
75      * @param digestPrefix The hash prefix of the instant app's domain.
76      * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
77      *             String, InstantAppResolutionCallback)}.
78      */
79     @Deprecated
onGetInstantAppIntentFilter(@ullable int[] digestPrefix, @NonNull String token, @NonNull InstantAppResolutionCallback callback)80     public void onGetInstantAppIntentFilter(@Nullable int[] digestPrefix, @NonNull String token,
81             @NonNull InstantAppResolutionCallback callback) {
82         throw new IllegalStateException("Must define onGetInstantAppIntentFilter");
83     }
84 
85     /**
86      * Called to retrieve resolve info for instant applications immediately. The response will be
87      * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
88      * in response to this method may be partial to request a second phase of resolution which will
89      * result in a subsequent call to
90      * {@link #onGetInstantAppIntentFilter(Intent, int[], String, InstantAppResolutionCallback)}
91      *
92      * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent
93      *                        is an intent with potential PII removed from the original intent.
94      *                        Fields removed include extras and the host + path of the data, if
95      *                        defined.
96      * @param hostDigestPrefix The hash prefix of the instant app's domain.
97      * @param token A unique identifier that will be provided in calls to
98      *              {@link #onGetInstantAppIntentFilter(Intent, int[], String,
99      *              InstantAppResolutionCallback)}
100      *              and provided to the installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN} to
101      *              tie a single launch together.
102      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
103      *
104      * @see InstantAppResolveInfo
105      *
106      * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
107      *             String, InstantAppResolutionCallback)}.
108      */
109     @Deprecated
onGetInstantAppResolveInfo(@onNull Intent sanitizedIntent, @Nullable int[] hostDigestPrefix, @NonNull String token, @NonNull InstantAppResolutionCallback callback)110     public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
111             @Nullable int[] hostDigestPrefix, @NonNull String token,
112             @NonNull InstantAppResolutionCallback callback) {
113         // if not overridden, forward to old methods and filter out non-web intents
114         if (sanitizedIntent.isWebIntent()) {
115             onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
116         } else {
117             callback.onInstantAppResolveInfo(Collections.emptyList());
118         }
119     }
120 
121     /**
122      * Called to retrieve intent filters for potentially matching instant applications. Unlike
123      * {@link #onGetInstantAppResolveInfo(Intent, int[], String, InstantAppResolutionCallback)},
124      * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
125      * provided in response to this method must be completely populated.
126      *
127      * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
128      * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
129      *                         defined.
130      * @param token A unique identifier that was provided in
131      *              {@link #onGetInstantAppResolveInfo(Intent, int[], String,
132      *              InstantAppResolutionCallback)}
133      *              and provided to the currently visible installer via
134      *              {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
135      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
136      *
137      * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
138      *             String, InstantAppResolutionCallback)}.
139      */
140     @Deprecated
onGetInstantAppIntentFilter(@onNull Intent sanitizedIntent, @Nullable int[] hostDigestPrefix, @NonNull String token, @NonNull InstantAppResolutionCallback callback)141     public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
142             @Nullable int[] hostDigestPrefix,
143             @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
144         Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
145         // if not overridden, forward to old methods and filter out non-web intents
146         if (sanitizedIntent.isWebIntent()) {
147             onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
148         } else {
149             callback.onInstantAppResolveInfo(Collections.emptyList());
150         }
151     }
152 
153     /**
154      * Called to retrieve resolve info for instant applications immediately. The response will be
155      * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
156      * in response to this method may be partial to request a second phase of resolution which will
157      * result in a subsequent call to {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
158      * String, InstantAppResolutionCallback)}
159      *
160      * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent
161      *                        is an intent with potential PII removed from the original intent.
162      *                        Fields removed include extras and the host + path of the data, if
163      *                        defined.
164      * @param hostDigestPrefix The hash prefix of the instant app's domain.
165      * @param userHandle The user for which to resolve the instant app.
166      * @param token A unique identifier that will be provided in calls to {@link
167      *              #onGetInstantAppIntentFilter(Intent, int[], UserHandle, String,
168      *              InstantAppResolutionCallback)} and provided to the installer via {@link
169      *              Intent#EXTRA_INSTANT_APP_TOKEN} to tie a single launch together.
170      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
171      *
172      * @see InstantAppResolveInfo
173      */
onGetInstantAppResolveInfo(@onNull Intent sanitizedIntent, @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle, @NonNull String token, @NonNull InstantAppResolutionCallback callback)174     public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
175             @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
176             @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
177         // If not overridden, forward to the old method.
178         onGetInstantAppResolveInfo(sanitizedIntent, hostDigestPrefix, token, callback);
179     }
180 
181     /**
182      * Called to retrieve intent filters for potentially matching instant applications. Unlike
183      * {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle, String,
184      * InstantAppResolutionCallback)}, the response may take as long as necessary to respond. All
185      * {@link InstantAppResolveInfo}s provided in response to this method must be completely
186      * populated.
187      *
188      * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
189      * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
190      *                         defined.
191      * @param userHandle The user for which to resolve the instant app.
192      * @param token A unique identifier that was provided in {@link #onGetInstantAppResolveInfo(
193      *              Intent, int[], UserHandle, String, InstantAppResolutionCallback)} and provided
194      *              to the currently visible installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
195      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
196      */
onGetInstantAppIntentFilter(@onNull Intent sanitizedIntent, @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle, @NonNull String token, @NonNull InstantAppResolutionCallback callback)197     public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
198             @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
199             @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
200         // If not overridden, forward to the old method.
201         onGetInstantAppIntentFilter(sanitizedIntent, hostDigestPrefix, token, callback);
202     }
203 
204     /**
205      * Returns a {@link Looper} to perform service operations on.
206      */
getLooper()207     Looper getLooper() {
208         return getBaseContext().getMainLooper();
209     }
210 
211     @Override
attachBaseContext(Context base)212     public final void attachBaseContext(Context base) {
213         super.attachBaseContext(base);
214         mHandler = new ServiceHandler(getLooper());
215     }
216 
217     @Override
onBind(Intent intent)218     public final IBinder onBind(Intent intent) {
219         return new IInstantAppResolver.Stub() {
220             @Override
221             public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
222                     int userId, String token, int sequence, IRemoteCallback callback) {
223                 if (DEBUG_INSTANT) {
224                     Slog.v(TAG, "[" + token + "] Phase1 called; posting");
225                 }
226                 final SomeArgs args = SomeArgs.obtain();
227                 args.arg1 = callback;
228                 args.arg2 = digestPrefix;
229                 args.arg3 = userId;
230                 args.arg4 = token;
231                 args.arg5 = sanitizedIntent;
232                 mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
233                         sequence, 0, args).sendToTarget();
234             }
235 
236             @Override
237             public void getInstantAppIntentFilterList(Intent sanitizedIntent,
238                     int[] digestPrefix, int userId, String token, IRemoteCallback callback) {
239                 if (DEBUG_INSTANT) {
240                     Slog.v(TAG, "[" + token + "] Phase2 called; posting");
241                 }
242                 final SomeArgs args = SomeArgs.obtain();
243                 args.arg1 = callback;
244                 args.arg2 = digestPrefix;
245                 args.arg3 = userId;
246                 args.arg4 = token;
247                 args.arg5 = sanitizedIntent;
248                 mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
249                         args).sendToTarget();
250             }
251         };
252     }
253 
254     /**
255      * Callback to post results from instant app resolution.
256      */
257     public static final class InstantAppResolutionCallback {
258         private final IRemoteCallback mCallback;
259         private final int mSequence;
260         InstantAppResolutionCallback(int sequence, IRemoteCallback callback) {
261             mCallback = callback;
262             mSequence = sequence;
263         }
264 
265         public void onInstantAppResolveInfo(List<InstantAppResolveInfo> resolveInfo) {
266             final Bundle data = new Bundle();
267             data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
268             data.putInt(EXTRA_SEQUENCE, mSequence);
269             try {
270                 mCallback.sendResult(data);
271             } catch (RemoteException e) {
272             }
273         }
274     }
275 
276     private final class ServiceHandler extends Handler {
277         public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1;
278         public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2;
279         public ServiceHandler(Looper looper) {
280             super(looper, null /*callback*/, true /*async*/);
281         }
282 
283         @Override
284         @SuppressWarnings("unchecked")
285         public void handleMessage(Message message) {
286             final int action = message.what;
287             switch (action) {
288                 case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
289                     final SomeArgs args = (SomeArgs) message.obj;
290                     final IRemoteCallback callback = (IRemoteCallback) args.arg1;
291                     final int[] digestPrefix = (int[]) args.arg2;
292                     final int userId = (int) args.arg3;
293                     final String token = (String) args.arg4;
294                     final Intent intent = (Intent) args.arg5;
295                     final int sequence = message.arg1;
296                     if (DEBUG_INSTANT) {
297                         Slog.d(TAG, "[" + token + "] Phase1 request;"
298                                 + " prefix: " + Arrays.toString(digestPrefix)
299                                 + ", userId: " + userId);
300                     }
301                     onGetInstantAppResolveInfo(intent, digestPrefix, UserHandle.of(userId), token,
302                             new InstantAppResolutionCallback(sequence, callback));
303                 } break;
304 
305                 case MSG_GET_INSTANT_APP_INTENT_FILTER: {
306                     final SomeArgs args = (SomeArgs) message.obj;
307                     final IRemoteCallback callback = (IRemoteCallback) args.arg1;
308                     final int[] digestPrefix = (int[]) args.arg2;
309                     final int userId = (int) args.arg3;
310                     final String token = (String) args.arg4;
311                     final Intent intent = (Intent) args.arg5;
312                     if (DEBUG_INSTANT) {
313                         Slog.d(TAG, "[" + token + "] Phase2 request;"
314                                 + " prefix: " + Arrays.toString(digestPrefix)
315                                 + ", userId: " + userId);
316                     }
317                     onGetInstantAppIntentFilter(intent, digestPrefix, UserHandle.of(userId), token,
318                             new InstantAppResolutionCallback(-1 /*sequence*/, callback));
319                 } break;
320 
321                 default: {
322                     throw new IllegalArgumentException("Unknown message: " + action);
323                 }
324             }
325         }
326     }
327 }
328