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