RetailDemoModeService.java revision 6472501f2e7ba018d8aa43c61e55874d756cecb8
1/*
2 * Copyright (C) 2016 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.retaildemo;
18
19import android.app.ActivityManagerInternal;
20import android.app.ActivityManagerNative;
21import android.app.AppGlobals;
22import android.app.Notification;
23import android.app.NotificationManager;
24import android.app.PendingIntent;
25import android.app.RetailDemoModeServiceInternal;
26import android.content.BroadcastReceiver;
27import android.content.ComponentName;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.pm.IPackageManager;
34import android.content.pm.PackageManager;
35import android.content.pm.UserInfo;
36import android.content.res.Configuration;
37import android.database.ContentObserver;
38import android.hardware.camera2.CameraAccessException;
39import android.hardware.camera2.CameraCharacteristics;
40import android.hardware.camera2.CameraManager;
41import android.media.AudioManager;
42import android.media.AudioSystem;
43import android.net.Uri;
44import android.os.Environment;
45import android.os.FileUtils;
46import android.os.Handler;
47import android.os.Looper;
48import android.os.Message;
49import android.os.PowerManager;
50import android.os.RemoteException;
51import android.os.SystemClock;
52import android.os.UserHandle;
53import android.os.UserManager;
54import android.provider.Settings;
55import android.util.Slog;
56import com.android.internal.os.BackgroundThread;
57import com.android.internal.R;
58import com.android.internal.widget.LockPatternUtils;
59import com.android.server.LocalServices;
60import com.android.server.ServiceThread;
61import com.android.server.SystemService;
62import com.android.server.am.ActivityManagerService;
63import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener;
64
65import java.io.File;
66import java.util.ArrayList;
67
68public class RetailDemoModeService extends SystemService {
69    private static final boolean DEBUG = false;
70
71    private static final String TAG = RetailDemoModeService.class.getSimpleName();
72    private static final String DEMO_USER_NAME = "Demo";
73    private static final String ACTION_RESET_DEMO = "com.android.server.am.ACTION_RESET_DEMO";
74
75    private static final int MSG_TURN_SCREEN_ON = 0;
76    private static final int MSG_INACTIVITY_TIME_OUT = 1;
77    private static final int MSG_START_NEW_SESSION = 2;
78
79    private static final long SCREEN_WAKEUP_DELAY = 2500;
80    private static final long USER_INACTIVITY_TIMEOUT = 30000;
81    private static final long WARNING_DIALOG_TIMEOUT = 6000;
82    private static final long MILLIS_PER_SECOND = 1000;
83
84    private static final int[] VOLUME_STREAMS_TO_MUTE = {
85            AudioSystem.STREAM_RING,
86            AudioSystem.STREAM_MUSIC
87    };
88
89    boolean mDeviceInDemoMode = false;
90    int mCurrentUserId;
91    private ActivityManagerService mAms;
92    private ActivityManagerInternal mAmi;
93    private AudioManager mAudioManager;
94    private NotificationManager mNm;
95    private UserManager mUm;
96    private PowerManager mPm;
97    private PowerManager.WakeLock mWakeLock;
98    Handler mHandler;
99    private ServiceThread mHandlerThread;
100    private PendingIntent mResetDemoPendingIntent;
101    private CameraManager mCameraManager;
102    private String[] mCameraIdsWithFlash;
103    private Configuration mPrimaryUserConfiguration;
104
105    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
106        @Override
107        public void onReceive(Context context, Intent intent) {
108            if (!mDeviceInDemoMode) {
109                return;
110            }
111            switch (intent.getAction()) {
112                case Intent.ACTION_SCREEN_OFF:
113                    mHandler.removeMessages(MSG_TURN_SCREEN_ON);
114                    mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
115                    break;
116                case ACTION_RESET_DEMO:
117                    mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
118                    break;
119            }
120        }
121    };
122
123    final class MainHandler extends Handler {
124
125        MainHandler(Looper looper) {
126            super(looper, null, true);
127        }
128
129        @Override
130        public void handleMessage(Message msg) {
131            switch (msg.what) {
132                case MSG_TURN_SCREEN_ON:
133                    if (mWakeLock.isHeld()) {
134                        mWakeLock.release();
135                    }
136                    mWakeLock.acquire();
137                    break;
138                case MSG_INACTIVITY_TIME_OUT:
139                    final IPackageManager pm = AppGlobals.getPackageManager();
140                    int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
141                    String demoLauncherComponent = getContext().getResources()
142                            .getString(R.string.config_demoModeLauncherComponent);
143                    try {
144                        enabledState = pm.getComponentEnabledSetting(
145                                ComponentName.unflattenFromString(demoLauncherComponent),
146                                mCurrentUserId);
147                    } catch (RemoteException exc) {
148                        Slog.e(TAG, "Unable to talk to Package Manager", exc);
149                    }
150                    if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
151                        Slog.i(TAG, "User inactivity timeout reached");
152                        showInactivityCountdownDialog();
153                    }
154                    break;
155                case MSG_START_NEW_SESSION:
156                    if (DEBUG) {
157                        Slog.d(TAG, "Switching to a new demo user");
158                    }
159                    removeMessages(MSG_START_NEW_SESSION);
160                    removeMessages(MSG_INACTIVITY_TIME_OUT);
161                    final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
162                            UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
163                    if (demoUser != null) {
164                        setupDemoUser(demoUser);
165                        getActivityManager().switchUser(demoUser.id);
166                    }
167                    break;
168            }
169        }
170    }
171
172    private void showInactivityCountdownDialog() {
173        UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
174                WARNING_DIALOG_TIMEOUT, MILLIS_PER_SECOND);
175        dialog.setPositiveButtonClickListener(null);
176        dialog.setNegativeButtonClickListener(new DialogInterface.OnClickListener() {
177            @Override
178            public void onClick(DialogInterface dialog, int which) {
179                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
180            }
181        });
182        dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() {
183            @Override
184            public void onCountDownExpired() {
185                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
186            }
187        });
188        dialog.show();
189    }
190
191    public RetailDemoModeService(Context context) {
192        super(context);
193    }
194
195    private Notification createResetNotification() {
196        return new Notification.Builder(getContext())
197                .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title))
198                .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text))
199                .setOngoing(true)
200                .setSmallIcon(R.drawable.platlogo)
201                .setShowWhen(false)
202                .setVisibility(Notification.VISIBILITY_PUBLIC)
203                .setContentIntent(getResetDemoPendingIntent())
204                .setColor(getContext().getColor(R.color.system_notification_accent_color))
205                .build();
206    }
207
208    private PendingIntent getResetDemoPendingIntent() {
209        if (mResetDemoPendingIntent == null) {
210            Intent intent = new Intent(ACTION_RESET_DEMO);
211            mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
212        }
213        return mResetDemoPendingIntent;
214    }
215
216    private void setupDemoUser(UserInfo userInfo) {
217        UserManager um = getUserManager();
218        UserHandle user = UserHandle.of(userInfo.id);
219        LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
220        lockPatternUtils.setLockScreenDisabled(true, userInfo.id);
221        um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
222        um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
223        um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
224        um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
225        Settings.Secure.putIntForUser(getContext().getContentResolver(),
226                Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
227    }
228
229    private ActivityManagerService getActivityManager() {
230        if (mAms == null) {
231            mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
232        }
233        return mAms;
234    }
235
236    private UserManager getUserManager() {
237        if (mUm == null) {
238            mUm = getContext().getSystemService(UserManager.class);
239        }
240        return mUm;
241    }
242
243    private AudioManager getAudioManager() {
244        if (mAudioManager == null) {
245            mAudioManager = getContext().getSystemService(AudioManager.class);
246        }
247        return mAudioManager;
248    }
249
250    private void registerSettingsChangeObserver() {
251        final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
252        final Uri deviceProvisionedUri = Settings.Global.getUriFor(
253                Settings.Global.DEVICE_PROVISIONED);
254        final ContentResolver cr = getContext().getContentResolver();
255        final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) {
256            @Override
257            public void onChange(boolean selfChange, Uri uri, int userId) {
258                if (deviceDemoModeUri.equals(uri)) {
259                    mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
260                    if (mDeviceInDemoMode) {
261                        mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
262                    } else if (mWakeLock.isHeld()) {
263                        mWakeLock.release();
264                    }
265                }
266                // If device is provisioned and left demo mode - run the cleanup in demo folder
267                if (!mDeviceInDemoMode && isDeviceProvisioned()) {
268                    // Run on the bg thread to not block the fg thread
269                    BackgroundThread.getHandler().post(new Runnable() {
270                        @Override
271                        public void run() {
272                            if (!deleteDemoFolderContents()) {
273                                Slog.w(TAG, "Failed to delete demo folder contents");
274                            }
275                        }
276                    });
277                }
278            }
279        };
280        cr.registerContentObserver(deviceDemoModeUri, false, deviceDemoModeSettingObserver,
281                UserHandle.USER_SYSTEM);
282        cr.registerContentObserver(deviceProvisionedUri, false, deviceDemoModeSettingObserver,
283                UserHandle.USER_SYSTEM);
284    }
285
286    private boolean isDeviceProvisioned() {
287        return Settings.Global.getInt(
288                getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
289    }
290
291    private boolean deleteDemoFolderContents() {
292        final File dir = Environment.getDataPreloadsDemoDirectory();
293        Slog.i(TAG, "Deleting contents of " + dir);
294        return FileUtils.deleteContents(dir);
295    }
296
297    private void registerBroadcastReceiver() {
298        final IntentFilter filter = new IntentFilter();
299        filter.addAction(Intent.ACTION_SCREEN_OFF);
300        filter.addAction(ACTION_RESET_DEMO);
301        getContext().registerReceiver(mBroadcastReceiver, filter);
302    }
303
304    private String[] getCameraIdsWithFlash() {
305        ArrayList<String> cameraIdsList = new ArrayList<String>();
306        try {
307            for (String cameraId : mCameraManager.getCameraIdList()) {
308                CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
309                if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
310                    cameraIdsList.add(cameraId);
311                }
312            }
313        } catch (CameraAccessException e) {
314            Slog.e(TAG, "Unable to access camera while getting camera id list", e);
315        }
316        return cameraIdsList.toArray(new String[cameraIdsList.size()]);
317    }
318
319    private void turnOffAllFlashLights() {
320        for (String cameraId : mCameraIdsWithFlash) {
321            try {
322                mCameraManager.setTorchMode(cameraId, false);
323            } catch (CameraAccessException e) {
324                Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e);
325            }
326        }
327    }
328
329    private void muteVolumeStreams() {
330        for (int stream : VOLUME_STREAMS_TO_MUTE) {
331            getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream),
332                    0);
333        }
334    }
335
336    private Configuration getPrimaryUsersConfiguration() {
337        if (mPrimaryUserConfiguration == null) {
338            Settings.System.getConfiguration(getContext().getContentResolver(),
339                    mPrimaryUserConfiguration = new Configuration());
340        }
341        return mPrimaryUserConfiguration;
342    }
343
344    @Override
345    public void onStart() {
346        if (DEBUG) {
347            Slog.d(TAG, "Service starting up");
348        }
349        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
350                false);
351        mHandlerThread.start();
352        mHandler = new MainHandler(mHandlerThread.getLooper());
353        publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
354    }
355
356    @Override
357    public void onBootPhase(int bootPhase) {
358        if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) {
359            return;
360        }
361        mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
362        mAmi = LocalServices.getService(ActivityManagerInternal.class);
363        mWakeLock = mPm
364                .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
365        mNm = NotificationManager.from(getContext());
366        mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
367        mCameraIdsWithFlash = getCameraIdsWithFlash();
368
369        if (UserManager.isDeviceInDemoMode(getContext())) {
370            mDeviceInDemoMode = true;
371            mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
372        }
373        registerSettingsChangeObserver();
374        registerBroadcastReceiver();
375    }
376
377    @Override
378    public void onSwitchUser(int userId) {
379        if (!mDeviceInDemoMode) {
380            return;
381        }
382        if (DEBUG) {
383            Slog.d(TAG, "onSwitchUser: " + userId);
384        }
385        final UserInfo ui = getUserManager().getUserInfo(userId);
386        if (!ui.isDemo()) {
387            Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
388            return;
389        }
390        if (!mWakeLock.isHeld()) {
391            mWakeLock.acquire();
392        }
393        mCurrentUserId = userId;
394        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
395        turnOffAllFlashLights();
396        muteVolumeStreams();
397        mAmi.updatePersistentConfigurationForUser(getPrimaryUsersConfiguration(), userId);
398    }
399
400    private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
401        private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
402        private long mLastUserActivityTime = 0;
403
404        @Override
405        public void onUserActivity() {
406            if (!mDeviceInDemoMode) {
407                return;
408            }
409            long timeOfActivity = SystemClock.uptimeMillis();
410            if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
411                return;
412            }
413            mLastUserActivityTime = timeOfActivity;
414            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
415            mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT);
416        }
417    };
418}
419