1/*
2 * Copyright (C) 2013 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 com.android.server;
18
19import android.annotation.NonNull;
20import android.content.Context;
21import android.os.SystemClock;
22import android.os.Trace;
23import android.util.Slog;
24
25import java.lang.reflect.Constructor;
26import java.lang.reflect.InvocationTargetException;
27import java.util.ArrayList;
28
29/**
30 * Manages creating, starting, and other lifecycle events of
31 * {@link com.android.server.SystemService system services}.
32 *
33 * {@hide}
34 */
35public class SystemServiceManager {
36    private static final String TAG = "SystemServiceManager";
37    private static final int SERVICE_CALL_WARN_TIME_MS = 50;
38
39    private final Context mContext;
40    private boolean mSafeMode;
41    private boolean mRuntimeRestarted;
42    private long mRuntimeStartElapsedTime;
43    private long mRuntimeStartUptime;
44
45    // Services that should receive lifecycle events.
46    private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
47
48    private int mCurrentPhase = -1;
49
50    SystemServiceManager(Context context) {
51        mContext = context;
52    }
53
54    /**
55     * Starts a service by class name.
56     *
57     * @return The service instance.
58     */
59    @SuppressWarnings("unchecked")
60    public SystemService startService(String className) {
61        final Class<SystemService> serviceClass;
62        try {
63            serviceClass = (Class<SystemService>)Class.forName(className);
64        } catch (ClassNotFoundException ex) {
65            Slog.i(TAG, "Starting " + className);
66            throw new RuntimeException("Failed to create service " + className
67                    + ": service class not found, usually indicates that the caller should "
68                    + "have called PackageManager.hasSystemFeature() to check whether the "
69                    + "feature is available on this device before trying to start the "
70                    + "services that implement it", ex);
71        }
72        return startService(serviceClass);
73    }
74
75    /**
76     * Creates and starts a system service. The class must be a subclass of
77     * {@link com.android.server.SystemService}.
78     *
79     * @param serviceClass A Java class that implements the SystemService interface.
80     * @return The service instance, never null.
81     * @throws RuntimeException if the service fails to start.
82     */
83    @SuppressWarnings("unchecked")
84    public <T extends SystemService> T startService(Class<T> serviceClass) {
85        try {
86            final String name = serviceClass.getName();
87            Slog.i(TAG, "Starting " + name);
88            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);
89
90            // Create the service.
91            if (!SystemService.class.isAssignableFrom(serviceClass)) {
92                throw new RuntimeException("Failed to create " + name
93                        + ": service must extend " + SystemService.class.getName());
94            }
95            final T service;
96            try {
97                Constructor<T> constructor = serviceClass.getConstructor(Context.class);
98                service = constructor.newInstance(mContext);
99            } catch (InstantiationException ex) {
100                throw new RuntimeException("Failed to create service " + name
101                        + ": service could not be instantiated", ex);
102            } catch (IllegalAccessException ex) {
103                throw new RuntimeException("Failed to create service " + name
104                        + ": service must have a public constructor with a Context argument", ex);
105            } catch (NoSuchMethodException ex) {
106                throw new RuntimeException("Failed to create service " + name
107                        + ": service must have a public constructor with a Context argument", ex);
108            } catch (InvocationTargetException ex) {
109                throw new RuntimeException("Failed to create service " + name
110                        + ": service constructor threw an exception", ex);
111            }
112
113            startService(service);
114            return service;
115        } finally {
116            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
117        }
118    }
119
120    public void startService(@NonNull final SystemService service) {
121        // Register it.
122        mServices.add(service);
123        // Start it.
124        long time = SystemClock.elapsedRealtime();
125        try {
126            service.onStart();
127        } catch (RuntimeException ex) {
128            throw new RuntimeException("Failed to start service " + service.getClass().getName()
129                    + ": onStart threw an exception", ex);
130        }
131        warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
132    }
133
134    /**
135     * Starts the specified boot phase for all system services that have been started up to
136     * this point.
137     *
138     * @param phase The boot phase to start.
139     */
140    public void startBootPhase(final int phase) {
141        if (phase <= mCurrentPhase) {
142            throw new IllegalArgumentException("Next phase must be larger than previous");
143        }
144        mCurrentPhase = phase;
145
146        Slog.i(TAG, "Starting phase " + mCurrentPhase);
147        try {
148            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "OnBootPhase " + phase);
149            final int serviceLen = mServices.size();
150            for (int i = 0; i < serviceLen; i++) {
151                final SystemService service = mServices.get(i);
152                long time = SystemClock.elapsedRealtime();
153                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, service.getClass().getName());
154                try {
155                    service.onBootPhase(mCurrentPhase);
156                } catch (Exception ex) {
157                    throw new RuntimeException("Failed to boot service "
158                            + service.getClass().getName()
159                            + ": onBootPhase threw an exception during phase "
160                            + mCurrentPhase, ex);
161                }
162                warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onBootPhase");
163                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
164            }
165        } finally {
166            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
167        }
168    }
169
170    /**
171     * @return true if system has completed the boot; false otherwise.
172     */
173    public boolean isBootCompleted() {
174        return mCurrentPhase >= SystemService.PHASE_BOOT_COMPLETED;
175    }
176
177    public void startUser(final int userHandle) {
178        Slog.i(TAG, "Calling onStartUser u" + userHandle);
179        final int serviceLen = mServices.size();
180        for (int i = 0; i < serviceLen; i++) {
181            final SystemService service = mServices.get(i);
182            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onStartUser "
183                    + service.getClass().getName());
184            long time = SystemClock.elapsedRealtime();
185            try {
186                service.onStartUser(userHandle);
187            } catch (Exception ex) {
188                Slog.wtf(TAG, "Failure reporting start of user " + userHandle
189                        + " to service " + service.getClass().getName(), ex);
190            }
191            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStartUser ");
192            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
193        }
194    }
195
196    public void unlockUser(final int userHandle) {
197        Slog.i(TAG, "Calling onUnlockUser u" + userHandle);
198        final int serviceLen = mServices.size();
199        for (int i = 0; i < serviceLen; i++) {
200            final SystemService service = mServices.get(i);
201            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onUnlockUser "
202                    + service.getClass().getName());
203            long time = SystemClock.elapsedRealtime();
204            try {
205                service.onUnlockUser(userHandle);
206            } catch (Exception ex) {
207                Slog.wtf(TAG, "Failure reporting unlock of user " + userHandle
208                        + " to service " + service.getClass().getName(), ex);
209            }
210            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onUnlockUser ");
211            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
212        }
213    }
214
215    public void switchUser(final int userHandle) {
216        Slog.i(TAG, "Calling switchUser u" + userHandle);
217        final int serviceLen = mServices.size();
218        for (int i = 0; i < serviceLen; i++) {
219            final SystemService service = mServices.get(i);
220            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onSwitchUser "
221                    + service.getClass().getName());
222            long time = SystemClock.elapsedRealtime();
223            try {
224                service.onSwitchUser(userHandle);
225            } catch (Exception ex) {
226                Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
227                        + " to service " + service.getClass().getName(), ex);
228            }
229            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onSwitchUser");
230            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
231        }
232    }
233
234    public void stopUser(final int userHandle) {
235        Slog.i(TAG, "Calling onStopUser u" + userHandle);
236        final int serviceLen = mServices.size();
237        for (int i = 0; i < serviceLen; i++) {
238            final SystemService service = mServices.get(i);
239            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onStopUser "
240                    + service.getClass().getName());
241            long time = SystemClock.elapsedRealtime();
242            try {
243                service.onStopUser(userHandle);
244            } catch (Exception ex) {
245                Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
246                        + " to service " + service.getClass().getName(), ex);
247            }
248            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStopUser");
249            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
250        }
251    }
252
253    public void cleanupUser(final int userHandle) {
254        Slog.i(TAG, "Calling onCleanupUser u" + userHandle);
255        final int serviceLen = mServices.size();
256        for (int i = 0; i < serviceLen; i++) {
257            final SystemService service = mServices.get(i);
258            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onCleanupUser "
259                    + service.getClass().getName());
260            long time = SystemClock.elapsedRealtime();
261            try {
262                service.onCleanupUser(userHandle);
263            } catch (Exception ex) {
264                Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
265                        + " to service " + service.getClass().getName(), ex);
266            }
267            warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onCleanupUser");
268            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
269        }
270    }
271
272    /** Sets the safe mode flag for services to query. */
273    void setSafeMode(boolean safeMode) {
274        mSafeMode = safeMode;
275    }
276
277    /**
278     * Returns whether we are booting into safe mode.
279     * @return safe mode flag
280     */
281    public boolean isSafeMode() {
282        return mSafeMode;
283    }
284
285    /**
286     * @return true if runtime was restarted, false if it's normal boot
287     */
288    public boolean isRuntimeRestarted() {
289        return mRuntimeRestarted;
290    }
291
292    /**
293     * @return Time when SystemServer was started, in elapsed realtime.
294     */
295    public long getRuntimeStartElapsedTime() {
296        return mRuntimeStartElapsedTime;
297    }
298
299    /**
300     * @return Time when SystemServer was started, in uptime.
301     */
302    public long getRuntimeStartUptime() {
303        return mRuntimeStartUptime;
304    }
305
306    void setStartInfo(boolean runtimeRestarted,
307            long runtimeStartElapsedTime, long runtimeStartUptime) {
308        mRuntimeRestarted = runtimeRestarted;
309        mRuntimeStartElapsedTime = runtimeStartElapsedTime;
310        mRuntimeStartUptime = runtimeStartUptime;
311    }
312
313    private void warnIfTooLong(long duration, SystemService service, String operation) {
314        if (duration > SERVICE_CALL_WARN_TIME_MS) {
315            Slog.w(TAG, "Service " + service.getClass().getName() + " took " + duration + " ms in "
316                    + operation);
317        }
318    }
319
320    /**
321     * Outputs the state of this manager to the System log.
322     */
323    public void dump() {
324        StringBuilder builder = new StringBuilder();
325        builder.append("Current phase: ").append(mCurrentPhase).append("\n");
326        builder.append("Services:\n");
327        final int startedLen = mServices.size();
328        for (int i = 0; i < startedLen; i++) {
329            final SystemService service = mServices.get(i);
330            builder.append("\t")
331                    .append(service.getClass().getSimpleName())
332                    .append("\n");
333        }
334
335        Slog.e(TAG, builder.toString());
336    }
337}
338