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