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.content.Context;
20import android.util.Slog;
21
22import java.lang.reflect.Constructor;
23import java.lang.reflect.InvocationTargetException;
24import java.util.ArrayList;
25
26/**
27 * Manages creating, starting, and other lifecycle events of
28 * {@link com.android.server.SystemService system services}.
29 *
30 * {@hide}
31 */
32public class SystemServiceManager {
33    private static final String TAG = "SystemServiceManager";
34
35    private final Context mContext;
36    private boolean mSafeMode;
37
38    // Services that should receive lifecycle events.
39    private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
40
41    private int mCurrentPhase = -1;
42
43    public SystemServiceManager(Context context) {
44        mContext = context;
45    }
46
47    /**
48     * Starts a service by class name.
49     *
50     * @return The service instance.
51     */
52    @SuppressWarnings("unchecked")
53    public SystemService startService(String className) {
54        final Class<SystemService> serviceClass;
55        try {
56            serviceClass = (Class<SystemService>)Class.forName(className);
57        } catch (ClassNotFoundException ex) {
58            Slog.i(TAG, "Starting " + className);
59            throw new RuntimeException("Failed to create service " + className
60                    + ": service class not found, usually indicates that the caller should "
61                    + "have called PackageManager.hasSystemFeature() to check whether the "
62                    + "feature is available on this device before trying to start the "
63                    + "services that implement it", ex);
64        }
65        return startService(serviceClass);
66    }
67
68    /**
69     * Creates and starts a system service. The class must be a subclass of
70     * {@link com.android.server.SystemService}.
71     *
72     * @param serviceClass A Java class that implements the SystemService interface.
73     * @return The service instance, never null.
74     * @throws RuntimeException if the service fails to start.
75     */
76    @SuppressWarnings("unchecked")
77    public <T extends SystemService> T startService(Class<T> serviceClass) {
78        final String name = serviceClass.getName();
79        Slog.i(TAG, "Starting " + name);
80
81        // Create the service.
82        if (!SystemService.class.isAssignableFrom(serviceClass)) {
83            throw new RuntimeException("Failed to create " + name
84                    + ": service must extend " + SystemService.class.getName());
85        }
86        final T service;
87        try {
88            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
89            service = constructor.newInstance(mContext);
90        } catch (InstantiationException ex) {
91            throw new RuntimeException("Failed to create service " + name
92                    + ": service could not be instantiated", ex);
93        } catch (IllegalAccessException ex) {
94            throw new RuntimeException("Failed to create service " + name
95                    + ": service must have a public constructor with a Context argument", ex);
96        } catch (NoSuchMethodException ex) {
97            throw new RuntimeException("Failed to create service " + name
98                    + ": service must have a public constructor with a Context argument", ex);
99        } catch (InvocationTargetException ex) {
100            throw new RuntimeException("Failed to create service " + name
101                    + ": service constructor threw an exception", ex);
102        }
103
104        // Register it.
105        mServices.add(service);
106
107        // Start it.
108        try {
109            service.onStart();
110        } catch (RuntimeException ex) {
111            throw new RuntimeException("Failed to start service " + name
112                    + ": onStart threw an exception", ex);
113        }
114        return service;
115    }
116
117    /**
118     * Starts the specified boot phase for all system services that have been started up to
119     * this point.
120     *
121     * @param phase The boot phase to start.
122     */
123    public void startBootPhase(final int phase) {
124        if (phase <= mCurrentPhase) {
125            throw new IllegalArgumentException("Next phase must be larger than previous");
126        }
127        mCurrentPhase = phase;
128
129        Slog.i(TAG, "Starting phase " + mCurrentPhase);
130
131        final int serviceLen = mServices.size();
132        for (int i = 0; i < serviceLen; i++) {
133            final SystemService service = mServices.get(i);
134            try {
135                service.onBootPhase(mCurrentPhase);
136            } catch (Exception ex) {
137                throw new RuntimeException("Failed to boot service "
138                        + service.getClass().getName()
139                        + ": onBootPhase threw an exception during phase "
140                        + mCurrentPhase, ex);
141            }
142        }
143    }
144
145    public void startUser(final int userHandle) {
146        final int serviceLen = mServices.size();
147        for (int i = 0; i < serviceLen; i++) {
148            final SystemService service = mServices.get(i);
149            try {
150                service.onStartUser(userHandle);
151            } catch (Exception ex) {
152                Slog.wtf(TAG, "Failure reporting start of user " + userHandle
153                        + " to service " + service.getClass().getName(), ex);
154            }
155        }
156    }
157
158    public void switchUser(final int userHandle) {
159        final int serviceLen = mServices.size();
160        for (int i = 0; i < serviceLen; i++) {
161            final SystemService service = mServices.get(i);
162            try {
163                service.onSwitchUser(userHandle);
164            } catch (Exception ex) {
165                Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
166                        + " to service " + service.getClass().getName(), ex);
167            }
168        }
169    }
170
171    public void stopUser(final int userHandle) {
172        final int serviceLen = mServices.size();
173        for (int i = 0; i < serviceLen; i++) {
174            final SystemService service = mServices.get(i);
175            try {
176                service.onStopUser(userHandle);
177            } catch (Exception ex) {
178                Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
179                        + " to service " + service.getClass().getName(), ex);
180            }
181        }
182    }
183
184    public void cleanupUser(final int userHandle) {
185        final int serviceLen = mServices.size();
186        for (int i = 0; i < serviceLen; i++) {
187            final SystemService service = mServices.get(i);
188            try {
189                service.onCleanupUser(userHandle);
190            } catch (Exception ex) {
191                Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
192                        + " to service " + service.getClass().getName(), ex);
193            }
194        }
195    }
196
197    /** Sets the safe mode flag for services to query. */
198    public void setSafeMode(boolean safeMode) {
199        mSafeMode = safeMode;
200    }
201
202    /**
203     * Returns whether we are booting into safe mode.
204     * @return safe mode flag
205     */
206    public boolean isSafeMode() {
207        return mSafeMode;
208    }
209
210    /**
211     * Outputs the state of this manager to the System log.
212     */
213    public void dump() {
214        StringBuilder builder = new StringBuilder();
215        builder.append("Current phase: ").append(mCurrentPhase).append("\n");
216        builder.append("Services:\n");
217        final int startedLen = mServices.size();
218        for (int i = 0; i < startedLen; i++) {
219            final SystemService service = mServices.get(i);
220            builder.append("\t")
221                    .append(service.getClass().getSimpleName())
222                    .append("\n");
223        }
224
225        Slog.e(TAG, builder.toString());
226    }
227}
228