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 android.support.v4.app;
18
19import android.os.Build;
20import android.os.Bundle;
21import android.os.IBinder;
22import android.util.Log;
23
24import java.lang.reflect.InvocationTargetException;
25import java.lang.reflect.Method;
26
27/**
28 * Helper for accessing features in {@link Bundle} in a backwards compatible fashion.
29 */
30public final class BundleCompat {
31
32    static class BundleCompatBaseImpl {
33        private static final String TAG = "BundleCompatBaseImpl";
34
35        private static Method sGetIBinderMethod;
36        private static boolean sGetIBinderMethodFetched;
37
38        private static Method sPutIBinderMethod;
39        private static boolean sPutIBinderMethodFetched;
40
41        public static IBinder getBinder(Bundle bundle, String key) {
42            if (!sGetIBinderMethodFetched) {
43                try {
44                    sGetIBinderMethod = Bundle.class.getMethod("getIBinder", String.class);
45                    sGetIBinderMethod.setAccessible(true);
46                } catch (NoSuchMethodException e) {
47                    Log.i(TAG, "Failed to retrieve getIBinder method", e);
48                }
49                sGetIBinderMethodFetched = true;
50            }
51
52            if (sGetIBinderMethod != null) {
53                try {
54                    return (IBinder) sGetIBinderMethod.invoke(bundle, key);
55                } catch (InvocationTargetException | IllegalAccessException
56                        | IllegalArgumentException e) {
57                    Log.i(TAG, "Failed to invoke getIBinder via reflection", e);
58                    sGetIBinderMethod = null;
59                }
60            }
61            return null;
62        }
63
64        public static void putBinder(Bundle bundle, String key, IBinder binder) {
65            if (!sPutIBinderMethodFetched) {
66                try {
67                    sPutIBinderMethod =
68                            Bundle.class.getMethod("putIBinder", String.class, IBinder.class);
69                    sPutIBinderMethod.setAccessible(true);
70                } catch (NoSuchMethodException e) {
71                    Log.i(TAG, "Failed to retrieve putIBinder method", e);
72                }
73                sPutIBinderMethodFetched = true;
74            }
75
76            if (sPutIBinderMethod != null) {
77                try {
78                    sPutIBinderMethod.invoke(bundle, key, binder);
79                } catch (InvocationTargetException | IllegalAccessException
80                        | IllegalArgumentException e) {
81                    Log.i(TAG, "Failed to invoke putIBinder via reflection", e);
82                    sPutIBinderMethod = null;
83                }
84            }
85        }
86    }
87
88    private BundleCompat() {}
89
90    /**
91     * A convenience method to handle getting an {@link IBinder} inside a {@link Bundle} for all
92     * Android versions.
93     * @param bundle The bundle to get the {@link IBinder}.
94     * @param key    The key to use while getting the {@link IBinder}.
95     * @return       The {@link IBinder} that was obtained.
96     */
97    public static IBinder getBinder(Bundle bundle, String key) {
98        if (Build.VERSION.SDK_INT >= 18) {
99            return bundle.getBinder(key);
100        } else {
101            return BundleCompatBaseImpl.getBinder(bundle, key);
102        }
103    }
104
105    /**
106     * A convenience method to handle putting an {@link IBinder} inside a {@link Bundle} for all
107     * Android versions.
108     * @param bundle The bundle to insert the {@link IBinder}.
109     * @param key    The key to use while putting the {@link IBinder}.
110     * @param binder The {@link IBinder} to put.
111     */
112    public static void putBinder(Bundle bundle, String key, IBinder binder) {
113        if (Build.VERSION.SDK_INT >= 18) {
114            bundle.putBinder(key, binder);
115        } else {
116            BundleCompatBaseImpl.putBinder(bundle, key, binder);
117        }
118    }
119}
120