summaryrefslogtreecommitdiff
path: root/core/java/com/android/internal/os/BinderInternal.java
blob: 8063be64f36d600df02a21a1f5f092579b39f9fb (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
/*
 * Copyright (C) 2008 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 com.android.internal.os;

import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.EventLog;
import android.util.SparseIntArray;

import com.android.internal.util.Preconditions;

import dalvik.system.VMRuntime;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;

/**
 * Private and debugging Binder APIs.
 *
 * @see IBinder
 */
public class BinderInternal {
    private static final String TAG = "BinderInternal";
    static WeakReference<GcWatcher> sGcWatcher
            = new WeakReference<GcWatcher>(new GcWatcher());
    static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
    static Runnable[] sTmpWatchers = new Runnable[1];
    static long sLastGcTime;
    static final BinderProxyCountEventListenerDelegate sBinderProxyCountEventListenerDelegate =
            new BinderProxyCountEventListenerDelegate();

    static final class GcWatcher {
        @Override
        protected void finalize() throws Throwable {
            handleGc();
            sLastGcTime = SystemClock.uptimeMillis();
            synchronized (sGcWatchers) {
                sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
            }
            for (int i=0; i<sTmpWatchers.length; i++) {
                if (sTmpWatchers[i] != null) {
                    sTmpWatchers[i].run();
                }
            }
            sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
        }
    }

    public static void addGcWatcher(Runnable watcher) {
        synchronized (sGcWatchers) {
            sGcWatchers.add(watcher);
        }
    }

    /**
     * A session used by {@link Observer} in order to keep track of some data.
     */
    public static class CallSession {
        // Binder interface descriptor.
        public Class<? extends Binder> binderClass;
        // Binder transaction code.
        public int transactionCode;
        // CPU time at the beginning of the call.
        long cpuTimeStarted;
        // System time at the beginning of the call.
        long timeStarted;
        // Should be set to one when an exception is thrown.
        boolean exceptionThrown;
        // Detailed information should be recorded for this call when it ends.
        public boolean recordedCall;
    }

    /**
     * Responsible for resolving a work source.
     */
    @FunctionalInterface
    public interface WorkSourceProvider {
        /**
         * <p>This method is called in a critical path of the binder transaction.
         * <p>The implementation should never execute a binder call since it is called during a
         * binder transaction.
         *
         * @param untrustedWorkSourceUid The work source set by the caller.
         * @return the uid of the process to attribute the binder transaction to.
         */
        int resolveWorkSourceUid(int untrustedWorkSourceUid);
    }

    /**
     * Allows to track various steps of an API call.
     */
    public interface Observer {
        /**
         * Called when a binder call starts.
         *
         * @return a CallSession to pass to the callEnded method.
         */
        CallSession callStarted(Binder binder, int code, int workSourceUid);

        /**
         * Called when a binder call stops.
         *
         * <li>This method will be called even when an exception is thrown by the binder stub
         * implementation.
         */
        void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize,
                int workSourceUid);

        /**
         * Called if an exception is thrown while executing the binder transaction.
         *
         * <li>BinderCallsStats#callEnded will be called afterwards.
         * <li>Do not throw an exception in this method, it will swallow the original exception
         * thrown by the binder transaction.
         */
        public void callThrewException(CallSession s, Exception exception);
    }

    /**
     * Allows to track observe incoming binder call stats.
     */
    public interface CallStatsObserver {
        /**
         * Notes incoming binder call stats associated with this work source UID.
         */
        void noteCallStats(int workSourceUid, long incrementalCallCount,
                Collection<BinderCallsStats.CallStat> callStats);

        /**
         * Notes the native IDs of threads taking incoming binder calls.
         */
        void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
    }

    /**
     * Add the calling thread to the IPC thread pool.  This function does
     * not return until the current process is exiting.
     */
    public static final native void joinThreadPool();

    /**
     * Return the system time (as reported by {@link SystemClock#uptimeMillis
     * SystemClock.uptimeMillis()}) that the last garbage collection occurred
     * in this process.  This is not for general application use, and the
     * meaning of "when a garbage collection occurred" will change as the
     * garbage collector evolves.
     *
     * @return Returns the time as per {@link SystemClock#uptimeMillis
     * SystemClock.uptimeMillis()} of the last garbage collection.
     */
    public static long getLastGcTime() {
        return sLastGcTime;
    }

    /**
     * Return the global "context object" of the system.  This is usually
     * an implementation of IServiceManager, which you can use to find
     * other services.
     */
    @UnsupportedAppUsage
    public static final native IBinder getContextObject();

    /**
     * Special for system process to not allow incoming calls to run at
     * background scheduling priority.
     * @hide
     */
    public static final native void disableBackgroundScheduling(boolean disable);

    public static final native void setMaxThreads(int numThreads);

    @UnsupportedAppUsage
    static native final void handleGc();

    public static void forceGc(String reason) {
        EventLog.writeEvent(2741, reason);
        VMRuntime.getRuntime().requestConcurrentGC();
    }

    static void forceBinderGc() {
        forceGc("Binder");
    }

    /**
     * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
     * be called if this process holds too many Binder Proxies on behalf of a Uid.
     * @param enabled true to enable counting, false to disable
     */
    public static final native void nSetBinderProxyCountEnabled(boolean enabled);

    /**
     * Get the current number of Binder Proxies held for each uid.
     * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
     */
    public static final native SparseIntArray nGetBinderProxyPerUidCounts();

    /**
     * Get the current number of Binder Proxies held for an individual uid.
     * @param uid Requested uid for Binder Proxy count
     * @return int with the number of Binder proxies held for a uid
     */
    public static final native int nGetBinderProxyCount(int uid);

    /**
     * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
     * @param high  The limit at which the BinderProxyListener callback will be called.
     * @param low   The threshold a binder count must drop below before the callback
     *              can be called again. (This is to avoid many repeated calls to the
     *              callback in a brief period of time)
     * @param warning The threshold between {@code high} and {@code low} where if the binder count
     *                exceeds that, the warning callback would be triggered.
     */
    public static final native void nSetBinderProxyCountWatermarks(int high, int low, int warning);

    /**
     * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
     * be called with the uid of the app causing too many Binder Proxies
     */
    public interface BinderProxyCountEventListener {
        public void onLimitReached(int uid);

        /**
         * Call when the number of binder proxies from the uid of the app reaches
         * the warning threshold.
         */
        default void onWarningThresholdReached(int uid) {
        }
    }

    /**
     * Callback used by native code to trigger a callback in java code. The callback will be
     * triggered when too many binder proxies from a uid hits the allowed limit.
     * @param uid The uid of the bad behaving app sending too many binders
     */
    public static void binderProxyLimitCallbackFromNative(int uid) {
        sBinderProxyCountEventListenerDelegate.notifyLimitReached(uid);
    }

    /**
     * Callback used by native code to trigger a callback in java code. The callback will be
     * triggered when too many binder proxies from a uid hits the warning limit.
     * @param uid The uid of the bad behaving app sending too many binders
     */
    @SuppressWarnings("unused")
    public static void binderProxyWarningCallbackFromNative(int uid) {
        sBinderProxyCountEventListenerDelegate.notifyWarningReached(uid);
    }

    /**
     * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
     * @param listener OnLimitReached of listener will be called in the thread provided by handler
     * @param handler must not be null, callback will be posted through the handler;
     *
     */
    public static void setBinderProxyCountCallback(BinderProxyCountEventListener listener,
            @NonNull Handler handler) {
        Preconditions.checkNotNull(handler,
                "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
                        + "BinderProxyCountEventListener");
        sBinderProxyCountEventListenerDelegate.setListener(listener, handler);
    }

    /**
     * Clear the Binder Proxy callback
     */
    public static void clearBinderProxyCountCallback() {
        sBinderProxyCountEventListenerDelegate.setListener(null, null);
    }

    private static class BinderProxyCountEventListenerDelegate {
        private BinderProxyCountEventListener mBinderProxyCountEventListener;
        private Handler mHandler;

        void setListener(BinderProxyCountEventListener listener, Handler handler) {
            synchronized (this) {
                mBinderProxyCountEventListener = listener;
                mHandler = handler;
            }
        }

        void notifyLimitReached(final int uid) {
            synchronized (this) {
                if (mBinderProxyCountEventListener != null) {
                    mHandler.post(() -> mBinderProxyCountEventListener.onLimitReached(uid));
                }
            }
        }

        void notifyWarningReached(final int uid) {
            synchronized (this) {
                if (mBinderProxyCountEventListener != null) {
                    mHandler.post(() ->
                            mBinderProxyCountEventListener.onWarningThresholdReached(uid));
                }
            }
        }
    }
}