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