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