Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
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 com.facebook;
18
 
19
import android.app.Activity;
20
import android.content.Context;
21
import android.content.Intent;
22
import android.net.Uri;
23
import android.os.Bundle;
24
import android.text.TextUtils;
25
import android.util.Log;
26
import com.facebook.internal.AttributionIdentifiers;
27
import com.facebook.internal.Utility;
28
import com.facebook.internal.Validate;
29
import com.facebook.model.GraphObject;
30
import org.json.JSONArray;
31
import org.json.JSONException;
32
import org.json.JSONObject;
33
 
34
import java.util.Iterator;
35
 
36
/**
37
 * Class to encapsulate an app link, and provide methods for constructing the data from various sources
38
 */
39
public class AppLinkData {
40
 
41
    /**
42
     * Key that should be used to pull out the UTC Unix tap-time from the arguments for this app link.
43
     */
44
    public static final String ARGUMENTS_TAPTIME_KEY = "com.facebook.platform.APPLINK_TAP_TIME_UTC";
45
    /**
46
     * Key that should be used to get the "referer_data" field for this app link.
47
     */
48
    public static final String ARGUMENTS_REFERER_DATA_KEY = "referer_data";
49
 
50
    /**
51
     * Key that should be used to pull out the native class that would have been used if the applink was deferred.
52
     */
53
    public static final String ARGUMENTS_NATIVE_CLASS_KEY = "com.facebook.platform.APPLINK_NATIVE_CLASS";
54
 
55
    /**
56
     * Key that should be used to pull out the native url that would have been used if the applink was deferred.
57
     */
58
    public static final String ARGUMENTS_NATIVE_URL = "com.facebook.platform.APPLINK_NATIVE_URL";
59
 
60
    static final String BUNDLE_APPLINK_ARGS_KEY = "com.facebook.platform.APPLINK_ARGS";
61
    private static final String BUNDLE_AL_APPLINK_DATA_KEY = "al_applink_data";
62
    private static final String APPLINK_BRIDGE_ARGS_KEY = "bridge_args";
63
    private static final String APPLINK_METHOD_ARGS_KEY = "method_args";
64
    private static final String APPLINK_VERSION_KEY = "version";
65
    private static final String BRIDGE_ARGS_METHOD_KEY = "method";
66
    private static final String DEFERRED_APP_LINK_EVENT = "DEFERRED_APP_LINK";
67
    private static final String DEFERRED_APP_LINK_PATH = "%s/activities";
68
 
69
    private static final String DEFERRED_APP_LINK_ARGS_FIELD = "applink_args";
70
    private static final String DEFERRED_APP_LINK_CLASS_FIELD = "applink_class";
71
    private static final String DEFERRED_APP_LINK_CLICK_TIME_FIELD = "click_time";
72
    private static final String DEFERRED_APP_LINK_URL_FIELD = "applink_url";
73
 
74
    private static final String METHOD_ARGS_TARGET_URL_KEY = "target_url";
75
    private static final String METHOD_ARGS_REF_KEY = "ref";
76
    private static final String REFERER_DATA_REF_KEY = "fb_ref";
77
    private static final String TAG = AppLinkData.class.getCanonicalName();
78
 
79
    private String ref;
80
    private Uri targetUri;
81
    private JSONObject arguments;
82
    private Bundle argumentBundle;
83
 
84
    /**
85
     * Asynchronously fetches app link information that might have been stored for use
86
     * after installation of the app
87
     * @param context The context
88
     * @param completionHandler CompletionHandler to be notified with the AppLinkData object or null if none is
89
     *                          available.  Must not be null.
90
     */
91
    public static void fetchDeferredAppLinkData(Context context, CompletionHandler completionHandler) {
92
        fetchDeferredAppLinkData(context, null, completionHandler);
93
    }
94
 
95
    /**
96
     * Asynchronously fetches app link information that might have been stored for use
97
     * after installation of the app
98
     * @param context The context
99
     * @param applicationId Facebook application Id. If null, it is taken from the manifest
100
     * @param completionHandler CompletionHandler to be notified with the AppLinkData object or null if none is
101
     *                          available.  Must not be null.
102
     */
103
    public static void fetchDeferredAppLinkData(
104
            Context context,
105
            String applicationId,
106
            final CompletionHandler completionHandler) {
107
        Validate.notNull(context, "context");
108
        Validate.notNull(completionHandler, "completionHandler");
109
 
110
        if (applicationId == null) {
111
            applicationId = Utility.getMetadataApplicationId(context);
112
        }
113
 
114
        Validate.notNull(applicationId, "applicationId");
115
 
116
        final Context applicationContext = context.getApplicationContext();
117
        final String applicationIdCopy = applicationId;
118
        Settings.getExecutor().execute(new Runnable() {
119
            @Override
120
            public void run() {
121
                fetchDeferredAppLinkFromServer(applicationContext, applicationIdCopy, completionHandler);
122
            }
123
        });
124
    }
125
 
126
    private static void fetchDeferredAppLinkFromServer(
127
            Context context,
128
            String applicationId,
129
            final CompletionHandler completionHandler) {
130
 
131
        GraphObject deferredApplinkParams = GraphObject.Factory.create();
132
        deferredApplinkParams.setProperty("event", DEFERRED_APP_LINK_EVENT);
133
        Utility.setAppEventAttributionParameters(deferredApplinkParams,
134
                AttributionIdentifiers.getAttributionIdentifiers(context),
135
                Utility.getHashedDeviceAndAppID(context, applicationId),
136
                Settings.getLimitEventAndDataUsage(context));
137
        deferredApplinkParams.setProperty("application_package_name", context.getPackageName());
138
 
139
        String deferredApplinkUrlPath = String.format(DEFERRED_APP_LINK_PATH, applicationId);
140
        AppLinkData appLinkData = null;
141
 
142
        try {
143
            Request deferredApplinkRequest = Request.newPostRequest(
144
                    null, deferredApplinkUrlPath, deferredApplinkParams, null);
145
            Response deferredApplinkResponse = deferredApplinkRequest.executeAndWait();
146
            GraphObject wireResponse = deferredApplinkResponse.getGraphObject();
147
            JSONObject jsonResponse = wireResponse != null ? wireResponse.getInnerJSONObject() : null;
148
            if (jsonResponse != null) {
149
                final String appLinkArgsJsonString = jsonResponse.optString(DEFERRED_APP_LINK_ARGS_FIELD);
150
                final long tapTimeUtc = jsonResponse.optLong(DEFERRED_APP_LINK_CLICK_TIME_FIELD, -1);
151
                final String appLinkClassName = jsonResponse.optString(DEFERRED_APP_LINK_CLASS_FIELD);
152
                final String appLinkUrl = jsonResponse.optString(DEFERRED_APP_LINK_URL_FIELD);
153
 
154
                if (!TextUtils.isEmpty(appLinkArgsJsonString)) {
155
                    appLinkData = createFromJson(appLinkArgsJsonString);
156
 
157
                    if (tapTimeUtc != -1) {
158
                        try {
159
                            if (appLinkData.arguments != null) {
160
                                appLinkData.arguments.put(ARGUMENTS_TAPTIME_KEY, tapTimeUtc);
161
                            }
162
                            if (appLinkData.argumentBundle != null) {
163
                                appLinkData.argumentBundle.putString(ARGUMENTS_TAPTIME_KEY, Long.toString(tapTimeUtc));
164
                            }
165
                        } catch (JSONException e) {
166
                            Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");
167
                        }
168
                    }
169
 
170
                    if (appLinkClassName != null) {
171
                        try {
172
                            if (appLinkData.arguments != null) {
173
                                appLinkData.arguments.put(ARGUMENTS_NATIVE_CLASS_KEY, appLinkClassName);
174
                            }
175
                            if (appLinkData.argumentBundle != null) {
176
                                appLinkData.argumentBundle.putString(ARGUMENTS_NATIVE_CLASS_KEY, appLinkClassName);
177
                            }
178
                        } catch (JSONException e) {
179
                            Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");
180
                        }
181
                    }
182
 
183
                    if (appLinkUrl != null) {
184
                        try {
185
                            if (appLinkData.arguments != null) {
186
                                appLinkData.arguments.put(ARGUMENTS_NATIVE_URL, appLinkUrl);
187
                            }
188
                            if (appLinkData.argumentBundle != null) {
189
                                appLinkData.argumentBundle.putString(ARGUMENTS_NATIVE_URL, appLinkUrl);
190
                            }
191
                        } catch (JSONException e) {
192
                            Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");
193
                        }
194
                    }
195
                }
196
            }
197
        } catch (Exception e) {
198
            Utility.logd(TAG, "Unable to fetch deferred applink from server");
199
        }
200
 
201
        completionHandler.onDeferredAppLinkDataFetched(appLinkData);
202
    }
203
 
204
    /**
205
     * Parses out any app link data from the Intent of the Activity passed in.
206
     * @param activity Activity that was started because of an app link
207
     * @return AppLinkData if found. null if not.
208
     */
209
    public static AppLinkData createFromActivity(Activity activity) {
210
        Validate.notNull(activity, "activity");
211
        Intent intent = activity.getIntent();
212
        if (intent == null) {
213
            return null;
214
        }
215
 
216
        AppLinkData appLinkData = createFromAlApplinkData(intent);
217
        if (appLinkData == null) {
218
            String appLinkArgsJsonString = intent.getStringExtra(BUNDLE_APPLINK_ARGS_KEY);
219
            appLinkData = createFromJson(appLinkArgsJsonString);
220
        }
221
        if (appLinkData == null) {
222
            // Try regular app linking
223
            appLinkData = createFromUri(intent.getData());
224
        }
225
 
226
        return appLinkData;
227
    }
228
 
229
    private static AppLinkData createFromAlApplinkData(Intent intent) {
230
        Bundle applinks = intent.getBundleExtra(BUNDLE_AL_APPLINK_DATA_KEY);
231
        if (applinks == null) {
232
            return null;
233
        }
234
 
235
        AppLinkData appLinkData = new AppLinkData();
236
        appLinkData.targetUri = intent.getData();
237
        if (appLinkData.targetUri == null) {
238
            String targetUriString = applinks.getString(METHOD_ARGS_TARGET_URL_KEY);
239
            if (targetUriString != null) {
240
                appLinkData.targetUri = Uri.parse(targetUriString);
241
            }
242
        }
243
        appLinkData.argumentBundle = applinks;
244
        appLinkData.arguments = null;
245
        Bundle refererData = applinks.getBundle(ARGUMENTS_REFERER_DATA_KEY);
246
        if (refererData != null) {
247
            appLinkData.ref = refererData.getString(REFERER_DATA_REF_KEY);
248
        }
249
 
250
        return appLinkData;
251
    }
252
 
253
    private static AppLinkData createFromJson(String jsonString) {
254
        if (jsonString  == null) {
255
            return null;
256
        }
257
 
258
        try {
259
            // Any missing or malformed data will result in a JSONException
260
            JSONObject appLinkArgsJson = new JSONObject(jsonString);
261
            String version = appLinkArgsJson.getString(APPLINK_VERSION_KEY);
262
 
263
            JSONObject bridgeArgs = appLinkArgsJson.getJSONObject(APPLINK_BRIDGE_ARGS_KEY);
264
            String method = bridgeArgs.getString(BRIDGE_ARGS_METHOD_KEY);
265
            if (method.equals("applink") && version.equals("2")) {
266
                // We have a new deep link
267
                AppLinkData appLinkData = new AppLinkData();
268
 
269
                appLinkData.arguments = appLinkArgsJson.getJSONObject(APPLINK_METHOD_ARGS_KEY);
270
                // first look for the "ref" key in the top level args
271
                if (appLinkData.arguments.has(METHOD_ARGS_REF_KEY)) {
272
                    appLinkData.ref = appLinkData.arguments.getString(METHOD_ARGS_REF_KEY);
273
                } else if (appLinkData.arguments.has(ARGUMENTS_REFERER_DATA_KEY)) {
274
                    // if it's not in the top level args, it could be in the "referer_data" blob
275
                    JSONObject refererData = appLinkData.arguments.getJSONObject(ARGUMENTS_REFERER_DATA_KEY);
276
                    if (refererData.has(REFERER_DATA_REF_KEY)) {
277
                        appLinkData.ref = refererData.getString(REFERER_DATA_REF_KEY);
278
                    }
279
                }
280
 
281
                if (appLinkData.arguments.has(METHOD_ARGS_TARGET_URL_KEY)) {
282
                    appLinkData.targetUri = Uri.parse(appLinkData.arguments.getString(METHOD_ARGS_TARGET_URL_KEY));
283
                }
284
 
285
                appLinkData.argumentBundle = toBundle(appLinkData.arguments);
286
 
287
                return appLinkData;
288
            }
289
        } catch (JSONException e) {
290
            Log.d(TAG, "Unable to parse AppLink JSON", e);
291
        } catch (FacebookException e) {
292
            Log.d(TAG, "Unable to parse AppLink JSON", e);
293
        }
294
 
295
        return null;
296
    }
297
 
298
    private static AppLinkData createFromUri(Uri appLinkDataUri) {
299
        if (appLinkDataUri == null) {
300
            return null;
301
        }
302
 
303
        AppLinkData appLinkData = new AppLinkData();
304
        appLinkData.targetUri = appLinkDataUri;
305
        return appLinkData;
306
    }
307
 
308
    private static Bundle toBundle(JSONObject node) throws JSONException {
309
        Bundle bundle = new Bundle();
310
        @SuppressWarnings("unchecked")
311
        Iterator<String> fields = node.keys();
312
        while (fields.hasNext()) {
313
            String key = fields.next();
314
            Object value;
315
            value = node.get(key);
316
 
317
            if (value instanceof JSONObject) {
318
                bundle.putBundle(key, toBundle((JSONObject) value));
319
            } else if (value instanceof JSONArray) {
320
                JSONArray valueArr = (JSONArray) value;
321
                if (valueArr.length() == 0) {
322
                    bundle.putStringArray(key, new String[0]);
323
                } else {
324
                    Object firstNode = valueArr.get(0);
325
                    if (firstNode instanceof JSONObject) {
326
                        Bundle[] bundles = new Bundle[valueArr.length()];
327
                        for (int i = 0; i < valueArr.length(); i++) {
328
                            bundles[i] = toBundle(valueArr.getJSONObject(i));
329
                        }
330
                        bundle.putParcelableArray(key, bundles);
331
                    } else if (firstNode instanceof JSONArray) {
332
                        // we don't support nested arrays
333
                        throw new FacebookException("Nested arrays are not supported.");
334
                    } else { // just use the string value
335
                        String[] arrValues = new String[valueArr.length()];
336
                        for (int i = 0; i < valueArr.length(); i++) {
337
                            arrValues[i] = valueArr.get(i).toString();
338
                        }
339
                        bundle.putStringArray(key, arrValues);
340
                    }
341
                }
342
            } else {
343
                bundle.putString(key, value.toString());
344
            }
345
        }
346
        return bundle;
347
    }
348
 
349
 
350
    private AppLinkData() {
351
    }
352
 
353
    /**
354
     * Returns the target uri for this App Link.
355
     * @return target uri
356
     */
357
    public Uri getTargetUri() {
358
        return targetUri;
359
    }
360
 
361
    /**
362
     * Returns the ref for this App Link.
363
     * @return ref
364
     */
365
    public String getRef() {
366
        return ref;
367
    }
368
 
369
    /**
370
     * This method has been deprecated. Please use {@link AppLinkData#getArgumentBundle()} instead.
371
     * @return JSONObject property bag.
372
     */
373
    @Deprecated
374
    public JSONObject getArguments() {
375
        return arguments;
376
    }
377
 
378
    /**
379
     * The full set of arguments for this app link. Properties like target uri & ref are typically
380
     * picked out of this set of arguments.
381
     * @return App link related arguments as a bundle.
382
     */
383
    public Bundle getArgumentBundle() {
384
        return argumentBundle;
385
    }
386
 
387
    /**
388
     * The referer data associated with the app link. This will contain Facebook specific information like
389
     * fb_access_token, fb_expires_in, and fb_ref.
390
     * @return the referer data.
391
     */
392
    public Bundle getRefererData() {
393
        if (argumentBundle != null) {
394
            return argumentBundle.getBundle(ARGUMENTS_REFERER_DATA_KEY);
395
        }
396
        return null;
397
    }
398
 
399
    /**
400
     * Interface to asynchronously receive AppLinkData after it has been fetched.
401
     */
402
    public interface CompletionHandler {
403
        /**
404
         * This method is called when deferred app link data has been fetched. If no app link data was found,
405
         * this method is called with null
406
         * @param appLinkData The app link data that was fetched. Null if none was found.
407
         */
408
        void onDeferredAppLinkDataFetched(AppLinkData appLinkData);
409
    }
410
}