RetailDemoModeService.java revision 707a9fc0027b76942e07f3c1aca8ff3db46b719c
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.Manifest;
20import android.app.ActivityManagerInternal;
21import android.app.ActivityManagerNative;
22import android.app.AppGlobals;
23import android.app.Notification;
24import android.app.NotificationManager;
25import android.app.PendingIntent;
26import android.app.RetailDemoModeServiceInternal;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.pm.IPackageManager;
35import android.content.pm.PackageManager;
36import android.content.pm.ResolveInfo;
37import android.content.pm.UserInfo;
38import android.content.res.Configuration;
39import android.database.ContentObserver;
40import android.hardware.camera2.CameraAccessException;
41import android.hardware.camera2.CameraCharacteristics;
42import android.hardware.camera2.CameraManager;
43import android.media.AudioManager;
44import android.media.AudioSystem;
45import android.net.Uri;
46import android.os.Environment;
47import android.os.FileUtils;
48import android.os.Handler;
49import android.os.Looper;
50import android.os.Message;
51import android.os.PowerManager;
52import android.os.RemoteException;
53import android.os.SystemClock;
54import android.os.SystemProperties;
55import android.os.UserHandle;
56import android.os.UserManager;
57import android.provider.MediaStore;
58import android.provider.Settings;
59import android.util.KeyValueListParser;
60import android.util.Slog;
61import com.android.internal.os.BackgroundThread;
62import com.android.internal.R;
63import com.android.internal.annotations.GuardedBy;
64import com.android.internal.logging.MetricsLogger;
65import com.android.internal.widget.LockPatternUtils;
66import com.android.server.LocalServices;
67import com.android.server.ServiceThread;
68import com.android.server.SystemService;
69import com.android.server.am.ActivityManagerService;
70import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener;
71
72import java.io.File;
73import java.util.ArrayList;
74
75public class RetailDemoModeService extends SystemService {
76    private static final boolean DEBUG = false;
77
78    private static final String TAG = RetailDemoModeService.class.getSimpleName();
79    private static final String DEMO_USER_NAME = "Demo";
80    private static final String ACTION_RESET_DEMO =
81            "com.android.server.retaildemo.ACTION_RESET_DEMO";
82    private static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled";
83
84    private static final int MSG_TURN_SCREEN_ON = 0;
85    private static final int MSG_INACTIVITY_TIME_OUT = 1;
86    private static final int MSG_START_NEW_SESSION = 2;
87
88    private static final long SCREEN_WAKEUP_DELAY = 2500;
89    private static final long USER_INACTIVITY_TIMEOUT_MIN = 10000;
90    private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 30000;
91    private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 6000;
92    private static final long MILLIS_PER_SECOND = 1000;
93
94    private static final int[] VOLUME_STREAMS_TO_MUTE = {
95            AudioSystem.STREAM_RING,
96            AudioSystem.STREAM_MUSIC
97    };
98
99    // Tron Vars
100    private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
101    private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
102
103    boolean mDeviceInDemoMode = false;
104    int mCurrentUserId = UserHandle.USER_SYSTEM;
105    long mUserInactivityTimeout;
106    long mWarningDialogTimeout;
107    private ActivityManagerService mAms;
108    private ActivityManagerInternal mAmi;
109    private AudioManager mAudioManager;
110    private NotificationManager mNm;
111    private UserManager mUm;
112    private PowerManager mPm;
113    private PowerManager.WakeLock mWakeLock;
114    Handler mHandler;
115    private ServiceThread mHandlerThread;
116    private PendingIntent mResetDemoPendingIntent;
117    private CameraManager mCameraManager;
118    private String[] mCameraIdsWithFlash;
119    private Configuration mSystemUserConfiguration;
120    private PreloadAppsInstaller mPreloadAppsInstaller;
121
122    final Object mActivityLock = new Object();
123    // Whether the newly created demo user has interacted with the screen yet
124    @GuardedBy("mActivityLock")
125    boolean mUserUntouched;
126    @GuardedBy("mActivityLock")
127    long mFirstUserActivityTime;
128    @GuardedBy("mActivityLock")
129    long mLastUserActivityTime;
130
131    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
132        @Override
133        public void onReceive(Context context, Intent intent) {
134            if (!mDeviceInDemoMode) {
135                return;
136            }
137            switch (intent.getAction()) {
138                case Intent.ACTION_SCREEN_OFF:
139                    mHandler.removeMessages(MSG_TURN_SCREEN_ON);
140                    mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
141                    break;
142                case ACTION_RESET_DEMO:
143                    mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
144                    break;
145            }
146        }
147    };
148
149    final class MainHandler extends Handler {
150
151        MainHandler(Looper looper) {
152            super(looper, null, true);
153        }
154
155        @Override
156        public void handleMessage(Message msg) {
157            switch (msg.what) {
158                case MSG_TURN_SCREEN_ON:
159                    if (mWakeLock.isHeld()) {
160                        mWakeLock.release();
161                    }
162                    mWakeLock.acquire();
163                    break;
164                case MSG_INACTIVITY_TIME_OUT:
165                    if (isDemoLauncherDisabled()) {
166                        Slog.i(TAG, "User inactivity timeout reached");
167                        showInactivityCountdownDialog();
168                    }
169                    break;
170                case MSG_START_NEW_SESSION:
171                    if (DEBUG) {
172                        Slog.d(TAG, "Switching to a new demo user");
173                    }
174                    removeMessages(MSG_START_NEW_SESSION);
175                    removeMessages(MSG_INACTIVITY_TIME_OUT);
176                    if (mCurrentUserId != UserHandle.USER_SYSTEM) {
177                        logSessionDuration();
178                    }
179                    final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
180                            UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
181                    if (demoUser != null) {
182                        setupDemoUser(demoUser);
183                        getActivityManager().switchUser(demoUser.id);
184                    }
185                    break;
186            }
187        }
188    }
189
190    private class SettingsObserver extends ContentObserver {
191
192        private final static String KEY_USER_INACTIVITY_TIMEOUT = "user_inactivity_timeout_ms";
193        private final static String KEY_WARNING_DIALOG_TIMEOUT = "warning_dialog_timeout_ms";
194
195        private final Uri mDeviceDemoModeUri = Settings.Global
196                .getUriFor(Settings.Global.DEVICE_DEMO_MODE);
197        private final Uri mDeviceProvisionedUri = Settings.Global
198                .getUriFor(Settings.Global.DEVICE_PROVISIONED);
199        private final Uri mRetailDemoConstantsUri = Settings.Global
200                .getUriFor(Settings.Global.RETAIL_DEMO_MODE_CONSTANTS);
201
202        private final KeyValueListParser mParser = new KeyValueListParser(',');
203
204        public SettingsObserver(Handler handler) {
205            super(handler);
206        }
207
208        public void register() {
209            ContentResolver cr = getContext().getContentResolver();
210            cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
211            cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
212            cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
213                    UserHandle.USER_SYSTEM);
214        }
215
216        @Override
217        public void onChange(boolean selfChange, Uri uri) {
218            if (mRetailDemoConstantsUri.equals(uri)) {
219                refreshTimeoutConstants();
220                return;
221            }
222            if (mDeviceDemoModeUri.equals(uri)) {
223                mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
224                if (mDeviceInDemoMode) {
225                    SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
226                    mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
227                } else {
228                    SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
229                    if (mWakeLock.isHeld()) {
230                        mWakeLock.release();
231                    }
232                }
233            }
234            // If device is provisioned and left demo mode - run the cleanup in demo folder
235            if (!mDeviceInDemoMode && isDeviceProvisioned()) {
236                // Run on the bg thread to not block the fg thread
237                BackgroundThread.getHandler().post(new Runnable() {
238                    @Override
239                    public void run() {
240                        if (!deletePreloadsFolderContents()) {
241                            Slog.w(TAG, "Failed to delete preloads folder contents");
242                        }
243                    }
244                });
245            }
246        }
247
248        private void refreshTimeoutConstants() {
249            try {
250                mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
251                    Settings.Global.RETAIL_DEMO_MODE_CONSTANTS));
252            } catch (IllegalArgumentException exc) {
253                Slog.e(TAG, "Invalid string passed to KeyValueListParser");
254                // Consuming the exception to fall back to default values.
255            }
256            mWarningDialogTimeout = mParser.getLong(KEY_WARNING_DIALOG_TIMEOUT,
257                    WARNING_DIALOG_TIMEOUT_DEFAULT);
258            mUserInactivityTimeout = mParser.getLong(KEY_USER_INACTIVITY_TIMEOUT,
259                    USER_INACTIVITY_TIMEOUT_DEFAULT);
260            mUserInactivityTimeout = Math.max(mUserInactivityTimeout, USER_INACTIVITY_TIMEOUT_MIN);
261        }
262    }
263
264    private void showInactivityCountdownDialog() {
265        UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
266                mWarningDialogTimeout, MILLIS_PER_SECOND);
267        dialog.setNegativeButtonClickListener(null);
268        dialog.setPositiveButtonClickListener(new DialogInterface.OnClickListener() {
269            @Override
270            public void onClick(DialogInterface dialog, int which) {
271                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
272            }
273        });
274        dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() {
275            @Override
276            public void onCountDownExpired() {
277                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
278            }
279        });
280        dialog.show();
281    }
282
283    public RetailDemoModeService(Context context) {
284        super(context);
285        synchronized (mActivityLock) {
286            mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis();
287        }
288        mPreloadAppsInstaller = new PreloadAppsInstaller();
289    }
290
291    private Notification createResetNotification() {
292        return new Notification.Builder(getContext())
293                .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title))
294                .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text))
295                .setOngoing(true)
296                .setSmallIcon(R.drawable.platlogo)
297                .setShowWhen(false)
298                .setVisibility(Notification.VISIBILITY_PUBLIC)
299                .setContentIntent(getResetDemoPendingIntent())
300                .setColor(getContext().getColor(R.color.system_notification_accent_color))
301                .build();
302    }
303
304    private PendingIntent getResetDemoPendingIntent() {
305        if (mResetDemoPendingIntent == null) {
306            Intent intent = new Intent(ACTION_RESET_DEMO);
307            mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
308        }
309        return mResetDemoPendingIntent;
310    }
311
312    boolean isDemoLauncherDisabled() {
313        IPackageManager pm = AppGlobals.getPackageManager();
314        int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
315        String demoLauncherComponent = getContext().getResources()
316                .getString(R.string.config_demoModeLauncherComponent);
317        try {
318            enabledState = pm.getComponentEnabledSetting(
319                    ComponentName.unflattenFromString(demoLauncherComponent),
320                    mCurrentUserId);
321        } catch (RemoteException exc) {
322            Slog.e(TAG, "Unable to talk to Package Manager", exc);
323        }
324        return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
325    }
326
327    private void setupDemoUser(UserInfo userInfo) {
328        UserManager um = getUserManager();
329        UserHandle user = UserHandle.of(userInfo.id);
330        um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
331        um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
332        um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
333        um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
334        um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
335        um.setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, true, user);
336        // Disallow rebooting in safe mode - controlled by user 0
337        getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true,
338                UserHandle.SYSTEM);
339        Settings.Secure.putIntForUser(getContext().getContentResolver(),
340                Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
341        Settings.Secure.putIntForUser(getContext().getContentResolver(),
342                Settings.Global.PACKAGE_VERIFIER_ENABLE, 0, userInfo.id);
343
344        grantRuntimePermissionToCamera(userInfo.getUserHandle());
345    }
346
347    private void grantRuntimePermissionToCamera(UserHandle user) {
348        final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
349        final PackageManager pm = getContext().getPackageManager();
350        final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent,
351                PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
352                user.getIdentifier());
353        if (handler == null || handler.activityInfo == null) {
354            return;
355        }
356        try {
357            pm.grantRuntimePermission(handler.activityInfo.packageName,
358                    Manifest.permission.ACCESS_FINE_LOCATION, user);
359        } catch (Exception e) {
360            // Ignore
361        }
362
363    }
364
365    void logSessionDuration() {
366        final int sessionDuration;
367        synchronized (mActivityLock) {
368            sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
369        }
370        MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration);
371    }
372
373    private ActivityManagerService getActivityManager() {
374        if (mAms == null) {
375            mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
376        }
377        return mAms;
378    }
379
380    private UserManager getUserManager() {
381        if (mUm == null) {
382            mUm = getContext().getSystemService(UserManager.class);
383        }
384        return mUm;
385    }
386
387    private AudioManager getAudioManager() {
388        if (mAudioManager == null) {
389            mAudioManager = getContext().getSystemService(AudioManager.class);
390        }
391        return mAudioManager;
392    }
393
394    private boolean isDeviceProvisioned() {
395        return Settings.Global.getInt(
396                getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
397    }
398
399    private boolean deletePreloadsFolderContents() {
400        final File dir = Environment.getDataPreloadsDirectory();
401        Slog.i(TAG, "Deleting contents of " + dir);
402        return FileUtils.deleteContents(dir);
403    }
404
405    private void registerBroadcastReceiver() {
406        final IntentFilter filter = new IntentFilter();
407        filter.addAction(Intent.ACTION_SCREEN_OFF);
408        filter.addAction(ACTION_RESET_DEMO);
409        getContext().registerReceiver(mBroadcastReceiver, filter);
410    }
411
412    private String[] getCameraIdsWithFlash() {
413        ArrayList<String> cameraIdsList = new ArrayList<String>();
414        try {
415            for (String cameraId : mCameraManager.getCameraIdList()) {
416                CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
417                if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
418                    cameraIdsList.add(cameraId);
419                }
420            }
421        } catch (CameraAccessException e) {
422            Slog.e(TAG, "Unable to access camera while getting camera id list", e);
423        }
424        return cameraIdsList.toArray(new String[cameraIdsList.size()]);
425    }
426
427    private void turnOffAllFlashLights() {
428        for (String cameraId : mCameraIdsWithFlash) {
429            try {
430                mCameraManager.setTorchMode(cameraId, false);
431            } catch (CameraAccessException e) {
432                Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e);
433            }
434        }
435    }
436
437    private void muteVolumeStreams() {
438        for (int stream : VOLUME_STREAMS_TO_MUTE) {
439            getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream),
440                    0);
441        }
442    }
443
444    private Configuration getSystemUsersConfiguration() {
445        if (mSystemUserConfiguration == null) {
446            Settings.System.getConfiguration(getContext().getContentResolver(),
447                    mSystemUserConfiguration = new Configuration());
448        }
449        return mSystemUserConfiguration;
450    }
451
452    @Override
453    public void onStart() {
454        if (DEBUG) {
455            Slog.d(TAG, "Service starting up");
456        }
457        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
458                false);
459        mHandlerThread.start();
460        mHandler = new MainHandler(mHandlerThread.getLooper());
461        publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
462    }
463
464    @Override
465    public void onBootPhase(int bootPhase) {
466        if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) {
467            return;
468        }
469        mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
470        mAmi = LocalServices.getService(ActivityManagerInternal.class);
471        mWakeLock = mPm
472                .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
473        mNm = NotificationManager.from(getContext());
474        mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
475        mCameraIdsWithFlash = getCameraIdsWithFlash();
476
477        if (UserManager.isDeviceInDemoMode(getContext())) {
478            mDeviceInDemoMode = true;
479            SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
480            mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
481        }
482        SettingsObserver settingsObserver = new SettingsObserver(mHandler);
483        settingsObserver.register();
484        settingsObserver.refreshTimeoutConstants();
485        registerBroadcastReceiver();
486    }
487
488    @Override
489    public void onSwitchUser(int userId) {
490        if (!mDeviceInDemoMode) {
491            return;
492        }
493        if (DEBUG) {
494            Slog.d(TAG, "onSwitchUser: " + userId);
495        }
496        final UserInfo ui = getUserManager().getUserInfo(userId);
497        if (!ui.isDemo()) {
498            Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
499            return;
500        }
501        if (!mWakeLock.isHeld()) {
502            mWakeLock.acquire();
503        }
504        mCurrentUserId = userId;
505        mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId);
506        turnOffAllFlashLights();
507        muteVolumeStreams();
508        // Disable lock screen for demo users.
509        LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
510        lockPatternUtils.setLockScreenDisabled(true, userId);
511        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
512
513        synchronized (mActivityLock) {
514            mUserUntouched = true;
515        }
516        MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
517        mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
518        mHandler.post(new Runnable() {
519            @Override
520            public void run() {
521                mPreloadAppsInstaller.installApps(userId);
522            }
523        });
524    }
525
526    private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
527        private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
528
529        @Override
530        public void onUserActivity() {
531            if (!mDeviceInDemoMode) {
532                return;
533            }
534            long timeOfActivity = SystemClock.uptimeMillis();
535            synchronized (mActivityLock) {
536                if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
537                    return;
538                }
539                mLastUserActivityTime = timeOfActivity;
540                if (mUserUntouched && isDemoLauncherDisabled()) {
541                    Slog.d(TAG, "retail_demo first touch");
542                    mUserUntouched = false;
543                    mFirstUserActivityTime = timeOfActivity;
544                }
545            }
546            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
547            mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout);
548        }
549    };
550}
551