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