RetailDemoModeService.java revision 0ce03e77bc58cccb083ec0d98bb1516db6a1523e
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        LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
325        lockPatternUtils.setLockScreenDisabled(true, userInfo.id);
326        um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
327        um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
328        um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
329        um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
330        um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
331        um.setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, true, user);
332        Settings.Secure.putIntForUser(getContext().getContentResolver(),
333                Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
334        Settings.Secure.putIntForUser(getContext().getContentResolver(),
335                Settings.Global.PACKAGE_VERIFIER_ENABLE, 0, userInfo.id);
336
337        grantRuntimePermissionToCamera(userInfo.getUserHandle());
338    }
339
340    private void grantRuntimePermissionToCamera(UserHandle user) {
341        final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
342        final PackageManager pm = getContext().getPackageManager();
343        final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent,
344                PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
345                user.getIdentifier());
346        if (handler == null || handler.activityInfo == null) {
347            return;
348        }
349        try {
350            pm.grantRuntimePermission(handler.activityInfo.packageName,
351                    Manifest.permission.ACCESS_FINE_LOCATION, user);
352        } catch (Exception e) {
353            // Ignore
354        }
355
356    }
357
358    void logSessionDuration() {
359        final int sessionDuration;
360        synchronized (mActivityLock) {
361            sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
362        }
363        MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration);
364    }
365
366    private ActivityManagerService getActivityManager() {
367        if (mAms == null) {
368            mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
369        }
370        return mAms;
371    }
372
373    private UserManager getUserManager() {
374        if (mUm == null) {
375            mUm = getContext().getSystemService(UserManager.class);
376        }
377        return mUm;
378    }
379
380    private AudioManager getAudioManager() {
381        if (mAudioManager == null) {
382            mAudioManager = getContext().getSystemService(AudioManager.class);
383        }
384        return mAudioManager;
385    }
386
387    private boolean isDeviceProvisioned() {
388        return Settings.Global.getInt(
389                getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
390    }
391
392    private boolean deletePreloadsFolderContents() {
393        final File dir = Environment.getDataPreloadsDirectory();
394        Slog.i(TAG, "Deleting contents of " + dir);
395        return FileUtils.deleteContents(dir);
396    }
397
398    private void registerBroadcastReceiver() {
399        final IntentFilter filter = new IntentFilter();
400        filter.addAction(Intent.ACTION_SCREEN_OFF);
401        filter.addAction(ACTION_RESET_DEMO);
402        getContext().registerReceiver(mBroadcastReceiver, filter);
403    }
404
405    private String[] getCameraIdsWithFlash() {
406        ArrayList<String> cameraIdsList = new ArrayList<String>();
407        try {
408            for (String cameraId : mCameraManager.getCameraIdList()) {
409                CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
410                if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
411                    cameraIdsList.add(cameraId);
412                }
413            }
414        } catch (CameraAccessException e) {
415            Slog.e(TAG, "Unable to access camera while getting camera id list", e);
416        }
417        return cameraIdsList.toArray(new String[cameraIdsList.size()]);
418    }
419
420    private void turnOffAllFlashLights() {
421        for (String cameraId : mCameraIdsWithFlash) {
422            try {
423                mCameraManager.setTorchMode(cameraId, false);
424            } catch (CameraAccessException e) {
425                Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e);
426            }
427        }
428    }
429
430    private void muteVolumeStreams() {
431        for (int stream : VOLUME_STREAMS_TO_MUTE) {
432            getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream),
433                    0);
434        }
435    }
436
437    private Configuration getSystemUsersConfiguration() {
438        if (mSystemUserConfiguration == null) {
439            Settings.System.getConfiguration(getContext().getContentResolver(),
440                    mSystemUserConfiguration = new Configuration());
441        }
442        return mSystemUserConfiguration;
443    }
444
445    @Override
446    public void onStart() {
447        if (DEBUG) {
448            Slog.d(TAG, "Service starting up");
449        }
450        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
451                false);
452        mHandlerThread.start();
453        mHandler = new MainHandler(mHandlerThread.getLooper());
454        publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
455    }
456
457    @Override
458    public void onBootPhase(int bootPhase) {
459        if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) {
460            return;
461        }
462        mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
463        mAmi = LocalServices.getService(ActivityManagerInternal.class);
464        mWakeLock = mPm
465                .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
466        mNm = NotificationManager.from(getContext());
467        mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
468        mCameraIdsWithFlash = getCameraIdsWithFlash();
469
470        if (UserManager.isDeviceInDemoMode(getContext())) {
471            mDeviceInDemoMode = true;
472            mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
473        }
474        SettingsObserver settingsObserver = new SettingsObserver(mHandler);
475        settingsObserver.register();
476        settingsObserver.refreshTimeoutConstants();
477        registerBroadcastReceiver();
478    }
479
480    @Override
481    public void onSwitchUser(int userId) {
482        if (!mDeviceInDemoMode) {
483            return;
484        }
485        if (DEBUG) {
486            Slog.d(TAG, "onSwitchUser: " + userId);
487        }
488        final UserInfo ui = getUserManager().getUserInfo(userId);
489        if (!ui.isDemo()) {
490            Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
491            return;
492        }
493        if (!mWakeLock.isHeld()) {
494            mWakeLock.acquire();
495        }
496        mCurrentUserId = userId;
497        mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId);
498        turnOffAllFlashLights();
499        muteVolumeStreams();
500        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
501
502        synchronized (mActivityLock) {
503            mUserUntouched = true;
504        }
505        MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
506        mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
507        mHandler.post(new Runnable() {
508            @Override
509            public void run() {
510                mPreloadAppsInstaller.installApps(userId);
511            }
512        });
513    }
514
515    private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
516        private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
517
518        @Override
519        public void onUserActivity() {
520            if (!mDeviceInDemoMode) {
521                return;
522            }
523            long timeOfActivity = SystemClock.uptimeMillis();
524            synchronized (mActivityLock) {
525                if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
526                    return;
527                }
528                mLastUserActivityTime = timeOfActivity;
529                if (mUserUntouched && isDemoLauncherDisabled()) {
530                    Slog.d(TAG, "retail_demo first touch");
531                    mUserUntouched = false;
532                    mFirstUserActivityTime = timeOfActivity;
533                }
534            }
535            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
536            mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout);
537        }
538    };
539}
540