1/*
2 * Copyright (C) 2010 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 android.net.wifi;
18
19import android.content.Context;
20import android.net.wifi.WifiConfiguration.KeyMgmt;
21import android.os.Environment;
22import android.os.Handler;
23import android.os.Message;
24import android.os.Messenger;
25import android.util.Log;
26
27import com.android.internal.util.AsyncChannel;
28import com.android.internal.R;
29import com.android.internal.util.State;
30import com.android.internal.util.StateMachine;
31
32import java.io.BufferedInputStream;
33import java.io.BufferedOutputStream;
34import java.io.DataInputStream;
35import java.io.DataOutputStream;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.net.InetAddress;
40import java.util.UUID;
41
42/**
43 * Provides API to the WifiStateMachine for doing read/write access
44 * to soft access point configuration
45 */
46class WifiApConfigStore extends StateMachine {
47
48    private Context mContext;
49    private static final String TAG = "WifiApConfigStore";
50
51    private static final String AP_CONFIG_FILE = Environment.getDataDirectory() +
52        "/misc/wifi/softap.conf";
53
54    private static final int AP_CONFIG_FILE_VERSION = 1;
55
56    private State mDefaultState = new DefaultState();
57    private State mInactiveState = new InactiveState();
58    private State mActiveState = new ActiveState();
59
60    private WifiConfiguration mWifiApConfig = null;
61    private AsyncChannel mReplyChannel = new AsyncChannel();
62
63    WifiApConfigStore(Context context, Handler target) {
64        super(TAG, target.getLooper());
65
66        mContext = context;
67        addState(mDefaultState);
68            addState(mInactiveState, mDefaultState);
69            addState(mActiveState, mDefaultState);
70
71        setInitialState(mInactiveState);
72    }
73
74    public static WifiApConfigStore makeWifiApConfigStore(Context context, Handler target) {
75        WifiApConfigStore s = new WifiApConfigStore(context, target);
76        s.start();
77        return s;
78    }
79
80    class DefaultState extends State {
81        public boolean processMessage(Message message) {
82            switch (message.what) {
83                case WifiStateMachine.CMD_SET_AP_CONFIG:
84                case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
85                    Log.e(TAG, "Unexpected message: " + message);
86                    break;
87                case WifiStateMachine.CMD_REQUEST_AP_CONFIG:
88                    mReplyChannel.replyToMessage(message,
89                            WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);
90                    break;
91                default:
92                    Log.e(TAG, "Failed to handle " + message);
93                    break;
94            }
95            return HANDLED;
96        }
97    }
98
99    class InactiveState extends State {
100        public boolean processMessage(Message message) {
101            switch (message.what) {
102                case WifiStateMachine.CMD_SET_AP_CONFIG:
103                    mWifiApConfig = (WifiConfiguration) message.obj;
104                    transitionTo(mActiveState);
105                    break;
106                default:
107                    return NOT_HANDLED;
108            }
109            return HANDLED;
110        }
111    }
112
113    class ActiveState extends State {
114        public void enter() {
115            new Thread(new Runnable() {
116                public void run() {
117                    writeApConfiguration(mWifiApConfig);
118                    sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED);
119                }
120            }).start();
121        }
122
123        public boolean processMessage(Message message) {
124            switch (message.what) {
125                //TODO: have feedback to the user when we do this
126                //to indicate the write is currently in progress
127                case WifiStateMachine.CMD_SET_AP_CONFIG:
128                    deferMessage(message);
129                    break;
130                case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
131                    transitionTo(mInactiveState);
132                    break;
133                default:
134                    return NOT_HANDLED;
135            }
136            return HANDLED;
137        }
138    }
139
140    void loadApConfiguration() {
141        DataInputStream in = null;
142        try {
143            WifiConfiguration config = new WifiConfiguration();
144            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
145                            AP_CONFIG_FILE)));
146
147            int version = in.readInt();
148            if (version != 1) {
149                Log.e(TAG, "Bad version on hotspot configuration file, set defaults");
150                setDefaultApConfiguration();
151                return;
152            }
153            config.SSID = in.readUTF();
154            int authType = in.readInt();
155            config.allowedKeyManagement.set(authType);
156            if (authType != KeyMgmt.NONE) {
157                config.preSharedKey = in.readUTF();
158            }
159            mWifiApConfig = config;
160        } catch (IOException ignore) {
161            setDefaultApConfiguration();
162        } finally {
163            if (in != null) {
164                try {
165                    in.close();
166                } catch (IOException e) {}
167            }
168        }
169    }
170
171    Messenger getMessenger() {
172        return new Messenger(getHandler());
173    }
174
175    private void writeApConfiguration(final WifiConfiguration config) {
176        DataOutputStream out = null;
177        try {
178            out = new DataOutputStream(new BufferedOutputStream(
179                        new FileOutputStream(AP_CONFIG_FILE)));
180
181            out.writeInt(AP_CONFIG_FILE_VERSION);
182            out.writeUTF(config.SSID);
183            int authType = config.getAuthType();
184            out.writeInt(authType);
185            if(authType != KeyMgmt.NONE) {
186                out.writeUTF(config.preSharedKey);
187            }
188        } catch (IOException e) {
189            Log.e(TAG, "Error writing hotspot configuration" + e);
190        } finally {
191            if (out != null) {
192                try {
193                    out.close();
194                } catch (IOException e) {}
195            }
196        }
197    }
198
199    /* Generate a default WPA2 based configuration with a random password.
200       We are changing the Wifi Ap configuration storage from secure settings to a
201       flat file accessible only by the system. A WPA2 based default configuration
202       will keep the device secure after the update */
203    private void setDefaultApConfiguration() {
204        WifiConfiguration config = new WifiConfiguration();
205        config.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
206        config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
207        String randomUUID = UUID.randomUUID().toString();
208        //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
209        config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9,13);
210        sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG, config);
211    }
212}
213