StatsCompanionService.java revision 6bc51d737b81689567162f3c49226ca234cb7e91
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 */
16package com.android.server.stats;
17
18import android.app.AlarmManager;
19import android.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.IStatsCompanionService;
26import android.os.IStatsManager;
27import android.os.Process;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.util.Slog;
31
32import com.android.internal.annotations.GuardedBy;
33import com.android.server.SystemService;
34
35/**
36 * Helper service for statsd (the native stats management service in cmds/statsd/).
37 * Used for registering and receiving alarms on behalf of statsd.
38 * @hide
39 */
40public class StatsCompanionService extends IStatsCompanionService.Stub {
41    static final String TAG = "StatsCompanionService";
42    static final boolean DEBUG = true;
43
44    private final Context mContext;
45    private final AlarmManager mAlarmManager;
46    @GuardedBy("sStatsdLock")
47    private static IStatsManager sStatsd;
48    private static final Object sStatsdLock = new Object();
49
50    private final PendingIntent mAnomalyAlarmIntent;
51    private final PendingIntent mPollingAlarmIntent;
52
53    public StatsCompanionService(Context context) {
54        super();
55        mContext = context;
56        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
57
58        mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
59                new Intent(mContext, AnomalyAlarmReceiver.class), 0);
60        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
61                new Intent(mContext, PollingAlarmReceiver.class), 0);
62    }
63
64    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
65        @Override
66        public void onReceive(Context context, Intent intent) {
67            Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
68            synchronized (sStatsdLock) {
69                if (sStatsd == null) {
70                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
71                    return;
72                }
73                try {
74                    // Two-way call to statsd to retain AlarmManager wakelock
75                    sStatsd.informAnomalyAlarmFired();
76                } catch (RemoteException e) {
77                    Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
78                }
79            }
80            // AlarmManager releases its own wakelock here.
81        }
82    };
83
84    public final static class PollingAlarmReceiver extends BroadcastReceiver {
85        @Override
86        public void onReceive(Context context, Intent intent) {
87            if (DEBUG) Slog.d(TAG, "Time to poll something.");
88            synchronized (sStatsdLock) {
89                if (sStatsd == null) {
90                    Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
91                    return;
92                }
93                try {
94                    // Two-way call to statsd to retain AlarmManager wakelock
95                    sStatsd.informPollAlarmFired();
96                } catch (RemoteException e) {
97                    Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
98                }
99            }
100            // AlarmManager releases its own wakelock here.
101        }
102    };
103
104    @Override // Binder call
105    public void setAnomalyAlarm(long timestampMs) {
106        enforceCallingPermission();
107        if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
108        final long callingToken = Binder.clearCallingIdentity();
109        try {
110            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
111            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
112            // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm.
113            mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent);
114        } finally {
115            Binder.restoreCallingIdentity(callingToken);
116        }
117    }
118
119    @Override // Binder call
120    public void cancelAnomalyAlarm() {
121        enforceCallingPermission();
122        if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
123        final long callingToken = Binder.clearCallingIdentity();
124        try {
125            mAlarmManager.cancel(mAnomalyAlarmIntent);
126        } finally {
127            Binder.restoreCallingIdentity(callingToken);
128        }
129    }
130
131    @Override // Binder call
132    public void setPollingAlarms(long timestampMs, long intervalMs) {
133        enforceCallingPermission();
134        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
135                + " every " + intervalMs + "ms");
136        final long callingToken = Binder.clearCallingIdentity();
137        try {
138            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
139            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
140            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
141            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
142                    mPollingAlarmIntent);
143        } finally {
144            Binder.restoreCallingIdentity(callingToken);
145        }
146    }
147
148    @Override // Binder call
149    public void cancelPollingAlarms() {
150        enforceCallingPermission();
151        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
152        final long callingToken = Binder.clearCallingIdentity();
153        try {
154            mAlarmManager.cancel(mPollingAlarmIntent);
155        } finally {
156            Binder.restoreCallingIdentity(callingToken);
157        }
158    }
159
160    @Override
161    public void statsdReady() {
162        enforceCallingPermission();
163        if (DEBUG) Slog.d(TAG, "learned that statsdReady");
164        sayHiToStatsd(); // tell statsd that we're ready too and link to it
165    }
166
167    private void enforceCallingPermission() {
168        if (Binder.getCallingPid() == Process.myPid()) {
169            return;
170        }
171        mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
172    }
173
174    // Lifecycle and related code
175
176    /** Fetches the statsd IBinder service */
177    private static IStatsManager fetchStatsdService() {
178        return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
179    }
180
181    public static final class Lifecycle extends SystemService {
182        private StatsCompanionService mStatsCompanionService;
183
184        public Lifecycle(Context context) {
185            super(context);
186        }
187
188        @Override
189        public void onStart() {
190            mStatsCompanionService = new StatsCompanionService(getContext());
191            try {
192                publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService);
193                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
194            } catch (Exception e) {
195                Slog.e(TAG, "Failed to publishBinderService", e);
196            }
197        }
198
199        @Override
200        public void onBootPhase(int phase) {
201            super.onBootPhase(phase);
202            if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
203                mStatsCompanionService.systemReady();
204            }
205        }
206    }
207
208    /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
209    private void systemReady() {
210        if (DEBUG) Slog.d(TAG, "Learned that systemReady");
211        sayHiToStatsd();
212    }
213
214    /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
215    private void sayHiToStatsd() {
216        synchronized (sStatsdLock) {
217            if (sStatsd != null) {
218                Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
219                        new IllegalStateException("sStatsd is not null when being fetched"));
220                return;
221            }
222            sStatsd = fetchStatsdService();
223            if (sStatsd == null) {
224                Slog.w(TAG, "Could not access statsd");
225                return;
226            }
227            if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
228            try {
229                sStatsd.statsCompanionReady();
230                // If the statsCompanionReady two-way binder call returns, link to statsd.
231                try {
232                    sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
233                } catch (RemoteException e) {
234                    Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
235                    forgetEverything();
236                }
237            } catch (RemoteException e) {
238                Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
239                forgetEverything();
240            }
241        }
242    }
243
244    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
245        @Override
246        public void binderDied() {
247            Slog.i(TAG, "Statsd is dead - erase all my knowledge.");
248            forgetEverything();
249        }
250    }
251
252    private void forgetEverything() {
253        synchronized (sStatsdLock) {
254            sStatsd = null;
255            cancelAnomalyAlarm();
256            cancelPollingAlarms();
257        }
258    }
259
260}
261