blob: 8364a8e1bba3e5cd88d379f86475d25be70cb80a [file] [log] [blame]
Jessica Hummelb5f001d2014-01-10 16:39:07 +00001/*
2 * Copyright 2014, 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 */
Jessica Hummel8119e122014-01-10 12:13:49 +000016
17package com.android.managedprovisioning;
18
19import android.app.Activity;
Jessica Hummelb5f001d2014-01-10 16:39:07 +000020import android.app.AlarmManager;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.net.wifi.WifiManager;
26import android.nfc.NdefMessage;
27import android.nfc.NdefRecord;
28import android.nfc.NfcAdapter;
Jessica Hummel8119e122014-01-10 12:13:49 +000029import android.os.Bundle;
Jessica Hummelb5f001d2014-01-10 16:39:07 +000030import android.os.Handler;
31import android.os.Parcelable;
32
33import com.android.internal.app.LocalePicker;
34import com.google.common.collect.Sets;
35
36import java.io.IOException;
37import java.io.StringReader;
38import java.util.Locale;
39import java.util.Properties;
40import java.util.Set;
Jessica Hummel8119e122014-01-10 12:13:49 +000041
42public class ManagedProvisioningActivity extends Activity {
43
Jessica Hummelb5f001d2014-01-10 16:39:07 +000044 private static final String NFC_MIME_TYPE = "application/com.android.managedprovisioning";
45 private static final String BUMP_LAUNCHER_ACTIVITY = ".ManagedProvisioningActivity";
46
47 // Five minute timeout by default.
48 private static final String TIMEOUT_IN_MS = "300000";
49
50 // The packet that we receive over NFC is this serialized properties object.
51 private Properties mProps;
52
53 // Abstraction above settings for testing.
54 private SettingsAdapter mSettingsAdapter;
55
56 // TODO Make this a boolean if it turns out that we only have one or two states to be
57 // tracked here. If we don't remove it, rename to make the difference between this state and the
58 // task state used in the task manager more clear.
59 enum State {
60 BUMP_DETECTED, // We've received a NFC packet.
61 MAX_VALUES
62 }
63
64 private State mState = State.BUMP_DETECTED;
65
66 // Used to determine which state to enter.
67 private static Set<State> mCompletedStates = Sets.newHashSet();
68
Jessica Hummel8119e122014-01-10 12:13:49 +000069 @Override
70 public void onCreate(Bundle savedInstanceState) {
71 super.onCreate(savedInstanceState);
Jessica Hummelb5f001d2014-01-10 16:39:07 +000072
73 PackageManager pkgMgr = getPackageManager();
74 pkgMgr.setComponentEnabledSetting(getComponentName(this),
75 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
76
77 mSettingsAdapter = new SettingsAdapter(getContentResolver());
78
79 // TODO Register status receiver for error handling only if needed.
80 // (Might be possible to simplify this)
81
82 // Sometimes ManagedProvisioning gets killed and restarted, and needs to resume
83 // provisioning already in progress. Provisioning doesn't need to resume if
84 // provisioning has already succeeded, in which case prefs.doesntNeedResume() returns true.
85
86 Preferences prefs = new Preferences(this);
87
88 // TODO Check if we need the settingsAdapter for tests.
89 if (prefs.doesntNeedResume() && (mSettingsAdapter.isDeviceProvisioned())) {
90
91 Intent intent = getIntent();
92 if (intent != null) {
93 processNfcPayload(intent);
94 sendProvisioningError("Device bumped twice.");
95 }
96
97 ProvisionLogger.logd("Device already provisioned or running in sandbox, exiting.");
98 if (savedInstanceState == null) {
99 cleanupAndFinish();
100 } else {
101 ProvisionLogger.logd("onCreate() called, provisioning in progress. Exiting.");
102 finish();
103 }
104 return;
105 }
106
Jessica Hummel8119e122014-01-10 12:13:49 +0000107 setContentView(R.layout.show_progress);
108 }
Jessica Hummelb5f001d2014-01-10 16:39:07 +0000109
110 @Override
111 public void onResume() {
112 super.onResume();
113
114 Intent intent = getIntent();
115 // Check to see that the Activity started due to an Android Beam.
116 // Don't restart an provisioning in progress
117
118 // TODO also react to bluetooth
119 if ((mState == State.BUMP_DETECTED) &&
120 NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
121 processNfcPayload(intent);
122 if (mProps != null) {
123 initialize();
124 completeState(State.BUMP_DETECTED);
125 }
126 }
127 }
128
129 private void sendProvisioningError(final String message) {
130 // TODO implement error message for Programmer app.
131 ProvisionLogger.logd("send provision error code: " + " " + message);
132 }
133
134 @Override
135 public void onNewIntent(Intent intent) {
136 setIntent(intent);
137 }
138
139 /**
140 * Parses the NDEF Message from the intent and prints to the TextView.
141 */
142 void processNfcPayload(Intent intent) {
143
144 // TODO internationalization for messages.
145 ProvisionLogger.logi("Processing NFC Payload.");
146
147 if (intent.hasExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
148 for (Parcelable rawMsg : intent
149 .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
150 NdefMessage msg = (NdefMessage) rawMsg;
151 NdefRecord firstRecord = msg.getRecords()[0];
152 String mimeType = new String(firstRecord.getType());
153
154 if (NFC_MIME_TYPE.equals(mimeType)) {
155 loadProps(new String(firstRecord.getPayload()));
156 }
157 }
158 }
159 // TODO Add bluetooth alternative to NFC.
160 }
161
162 private void loadProps(String data) {
163 try {
164 mProps = new Properties();
165 mProps.load(new StringReader(data));
166 } catch (IOException e) {
167 error("Couldn't load payload", e);
168 }
169 }
170
171 // TODO This initialisation should be different for BYOD (e.g don't set time zone).
172 private void initialize() {
173 registerErrorTimeout();
174 setTimeAndTimezone();
175
176 enableWifi();
177 setLanguage();
178 }
179
180 private void registerErrorTimeout() {
181 Runnable checkForDoneness = new Runnable() {
182 @Override
183 public void run() {
184 if (mState == State.MAX_VALUES) {
185 return;
186 }
187 error("timeout");
188 }
189 };
190
191 final Handler handler = new Handler();
192 long timeout = Long.parseLong(
193 mProps.getProperty(Preferences.TIMEOUT_KEY, TIMEOUT_IN_MS));
194 handler.postDelayed(checkForDoneness, timeout);
195 }
196
197 private void setTimeAndTimezone() {
198 String timeZoneId = mProps.getProperty(Preferences.TIME_ZONE_KEY);
199 String localTimeString = mProps.getProperty(Preferences.LOCAL_TIME_KEY);
200 Long localTime = localTimeString != null ? Long.valueOf(localTimeString) : null;
201
202 try {
203 final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
204 if (timeZoneId != null) am.setTimeZone(timeZoneId);
205 if (localTime != null) am.setTime(localTime);
206 } catch (Exception e) {
207 ProvisionLogger.loge("Failed to get alarm manager to set the system time/timezone.");
208 }
209 }
210
211 private void enableWifi() {
212 WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
213 if (wm != null) {
214 int wifiState = wm.getWifiState();
215 boolean wifiOn = wifiState == WifiManager.WIFI_STATE_ENABLED;
216 if (!wifiOn) {
217 if (!wm.setWifiEnabled(true)) {
218 error("Unable to enable WiFi");
219 }
220 }
221 }
222 }
223
224 private void setLanguage() {
225 String locale = mProps.getProperty(Preferences.LOCALE_KEY);
226 ProvisionLogger.logd("About to set locale: " + locale);
227 if (locale == null || locale.equals(Locale.getDefault().toString())) {
228 return;
229 }
230
231 try {
232 if (locale.length() == 5) {
233 Locale l = new Locale(locale.substring(0, 2), locale.substring(3, 5));
234 LocalePicker.updateLocale(l);
235 }
236 } catch (Exception e) {
237 ProvisionLogger.loge("failed to set the system locale");
238 }
239 }
240
241 @Override
242 public void onDestroy() {
243 super.onDestroy();
244 // TODO unregister status receiver if added
245 }
246
247 public void completeState(State completed) {
248 completeState(completed, true);
249 }
250
251 private synchronized void completeState(State completed, boolean startNext) {
252 mCompletedStates.add(completed);
253 ProvisionLogger.logv("Completed state:" + completed);
254
255 // Find the first state that isn't done.
256 for (State state : State.values()) {
257 if (!mCompletedStates.contains(state)) {
258 if (mState == state) {
259 ProvisionLogger.logv("No state change");
260 return; // No state change.
261 } else {
262 ProvisionLogger.logv("New State: " + state);
263 mState = state;
264 break;
265 }
266 }
267 }
268
269 // TODO Start the task manager to start the setup.
270 // Ensure that the execute() method is called on the UI thread.
271 // Message taskMsg = mHandler.obtainMessage(RUN_TASK_MSG);
272 // skMsg.sendToTarget();
273 }
274
275 private void cleanupAndFinish() {
276 ProvisionLogger.logd("Finishing NfcBumpActivity");
277 finish();
278 }
279
280 private void error(String logMsg) {
281 error(logMsg, null);
282 }
283
284 private void error(String logMsg, Throwable t) {
285 Preferences prefs = new Preferences(this);
286 prefs.setError(logMsg.toString());
287 ProvisionLogger.loge("Error: " + logMsg, t);
288 // TODO add error Dialog.
289 // ErrorDialog.showError(NfcBumpActivity.this);
290 }
291
292 private ComponentName getComponentName(Context context) {
293 String ourPackage = context.getPackageName();
294 String bumpLauncher = ourPackage + BUMP_LAUNCHER_ACTIVITY;
295 return new ComponentName(ourPackage, bumpLauncher);
296 }
Jessica Hummel8119e122014-01-10 12:13:49 +0000297}