1ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistpackage com.android.hotspot2.osu.service;
2ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
3ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport android.util.Log;
4ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
5ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport com.android.hotspot2.osu.OSUManager;
6ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport com.android.hotspot2.osu.OSUOperationStatus;
7ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
8ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.BufferedReader;
9ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.BufferedWriter;
10ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.IOException;
11ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.InputStreamReader;
12ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.OutputStreamWriter;
13ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.net.InetAddress;
14ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.net.ServerSocket;
15ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.net.Socket;
16ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.net.URL;
17ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.nio.charset.StandardCharsets;
18ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.Random;
19ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
20ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistpublic class RedirectListener extends Thread {
21ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final long ThreadTimeout = 3000L;
22ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final long UserTimeout = 3600000L;
23ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final int MaxRetry = 5;
24ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final String TAG = "OSULSN";
25ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
26ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final String HTTPResponseHeader =
27ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            "HTTP/1.1 304 Not Modified\r\n" +
28ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "Server: dummy\r\n" +
29ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "Keep-Alive: timeout=500, max=5\r\n\r\n";
30ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
31ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private static final String GoodBye =
32ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            "<html>" +
33ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "<head><title>Goodbye</title></head>" +
34ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "<body>" +
35ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "<h3>Killing browser...</h3>" +
36ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "</body>" +
37ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    "</html>\r\n";
38ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
39ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final OSUManager mOSUManager;
40ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final String mSpName;
41ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final ServerSocket mServerSocket;
42ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final String mPath;
43ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final URL mURL;
44ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private final Object mLock = new Object();
45ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
46ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private boolean mListening;
47ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private OSUOperationStatus mUserStatus;
48ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private volatile boolean mAborted;
49ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
50ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public RedirectListener(OSUManager osuManager, String spName) throws IOException {
51ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        mOSUManager = osuManager;
52ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        mSpName = spName;
53ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        mServerSocket = new ServerSocket(0, 5, InetAddress.getLocalHost());
54ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        Random rnd = new Random(System.currentTimeMillis());
55ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        mPath = "rnd" + Integer.toString(Math.abs(rnd.nextInt()), Character.MAX_RADIX);
56ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        mURL = new URL("http", mServerSocket.getInetAddress().getHostAddress(),
57ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                mServerSocket.getLocalPort(), mPath);
58ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
59ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        Log.d(TAG, "Redirect URL: " + mURL);
60ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        setName("HS20-Redirect-Listener");
61ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        setDaemon(true);
62ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
63ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
64ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public void startService() throws IOException {
65ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        start();
66ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        synchronized (mLock) {
67ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            long bail = System.currentTimeMillis() + ThreadTimeout;
68ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            long remainder = ThreadTimeout;
69ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            while (remainder > 0 && !mListening) {
70ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                try {
71ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    mLock.wait(remainder);
72ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                } catch (InterruptedException ie) {
73ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    /**/
74ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
75ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                if (mListening) {
76ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    break;
77ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
78ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                remainder = bail - System.currentTimeMillis();
79ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            }
80ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            if (!mListening) {
81ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                throw new IOException("Failed to start listener");
82ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            } else {
83ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                Log.d(TAG, "OSU Redirect listener running");
84ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            }
85ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
86ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
87ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
88ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public boolean waitForUser() {
89ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        boolean success;
90ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        synchronized (mLock) {
91ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            long bail = System.currentTimeMillis() + UserTimeout;
92ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            long remainder = UserTimeout;
93ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            while (remainder > 0 && mUserStatus == null) {
94ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                try {
95ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    mLock.wait(remainder);
96ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                } catch (InterruptedException ie) {
97ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    /**/
98ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
99ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                if (mUserStatus != null) {
100ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    break;
101ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
102ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                remainder = bail - System.currentTimeMillis();
103ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            }
104ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            success = mUserStatus == OSUOperationStatus.UserInputComplete;
105ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
106ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        abort();
107ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        return success;
108ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
109ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
110ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public void abort() {
111ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        try {
112ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mAborted = true;
113ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mServerSocket.close();
114ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        } catch (IOException ioe) {
115ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            /**/
116ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
117ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
118ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
119ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public URL getURL() {
120ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        return mURL;
121ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
122ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
123ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    @Override
124ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    public void run() {
125ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        int count = 0;
126ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        synchronized (mLock) {
127ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mListening = true;
128ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mLock.notifyAll();
129ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
130ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
131ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        boolean terminate = false;
132ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
133ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        for (; ; ) {
134ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            count++;
135ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            try (Socket instance = mServerSocket.accept()) {
136ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                try (BufferedReader in = new BufferedReader(
137ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        new InputStreamReader(instance.getInputStream(), StandardCharsets.UTF_8))) {
138ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    boolean detected = false;
139ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    StringBuilder sb = new StringBuilder();
140ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    String s;
141ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    while ((s = in.readLine()) != null) {
142ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        sb.append(s).append('\n');
143ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        if (!detected && s.startsWith("GET")) {
144ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            String[] segments = s.split(" ");
145ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            if (segments.length == 3 &&
146ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                                    segments[2].startsWith("HTTP/") &&
147ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                                    segments[1].regionMatches(1, mPath, 0, mPath.length())) {
148ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                                detected = true;
149ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            }
150ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        }
151ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        if (s.length() == 0) {
152ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            break;
153ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        }
154ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    }
155ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    Log.d(TAG, "Redirect receive: " + sb);
156ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    String response = null;
157ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    if (detected) {
158ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        response = status(OSUOperationStatus.UserInputComplete);
159ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        if (response == null) {
160ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            response = GoodBye;
161ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            terminate = true;
162ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        }
163ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    }
164ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    try (BufferedWriter out = new BufferedWriter(
165ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            new OutputStreamWriter(instance.getOutputStream(),
166ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                                    StandardCharsets.UTF_8))) {
167ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
168ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        out.write(HTTPResponseHeader);
169ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        if (response != null) {
170ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                            out.write(response);
171ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        }
172ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    }
173ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    if (terminate) {
174ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        break;
175ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    } else if (count > MaxRetry) {
176ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        status(OSUOperationStatus.UserInputAborted);
177ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                        break;
178ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    }
179ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
180ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            } catch (IOException ioe) {
181ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                if (mAborted) {
182ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    return;
183ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                } else if (count > MaxRetry) {
184ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    status(OSUOperationStatus.UserInputAborted);
185ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                    break;
186ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                }
187ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            }
188ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
189ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
190ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
191ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    private String status(OSUOperationStatus status) {
192ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        Log.d(TAG, "User input status: " + status);
193ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        synchronized (mLock) {
194ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mUserStatus = status;
195ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist            mLock.notifyAll();
196ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        }
197ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        String message = (status == OSUOperationStatus.UserInputAborted) ?
198ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist                "Browser closed" : null;
199ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist
200ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist        return mOSUManager.notifyUser(status, message, mSpName);
201ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist    }
202ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist}
203