1package com.android;
2
3import android.app.Activity;
4import android.app.AlertDialog;
5import android.app.IntentService;
6import android.app.Notification;
7import android.app.NotificationManager;
8import android.app.PendingIntent;
9import android.app.TaskStackBuilder;
10import android.content.BroadcastReceiver;
11import android.content.Context;
12import android.content.DialogInterface;
13import android.content.Intent;
14import android.graphics.BitmapFactory;
15import android.graphics.drawable.BitmapDrawable;
16import android.net.wifi.WifiConfiguration;
17import android.net.wifi.WifiInfo;
18import android.net.wifi.WifiManager;
19import android.os.Binder;
20import android.os.Bundle;
21import android.os.IBinder;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.AdapterView;
27import android.widget.ArrayAdapter;
28import android.widget.ImageView;
29import android.widget.ListView;
30import android.widget.TextView;
31
32import com.android.anqp.OSUProvider;
33import com.android.hotspot2.AppBridge;
34import com.android.hotspot2.PasspointMatch;
35import com.android.hotspot2.osu.OSUInfo;
36import com.android.hotspot2.osu.OSUManager;
37
38import org.xml.sax.SAXException;
39
40import java.io.IOException;
41import java.util.Collections;
42import java.util.List;
43import java.util.Locale;
44import java.util.concurrent.TimeUnit;
45
46//import com.android.Osu.R;
47
48/**
49 * Main activity.
50 */
51public class MainActivity extends Activity {
52    private static final int NOTIFICATION_ID = 0; // Used for OSU count
53    private static final int NOTIFICATION_MESSAGE_ID = 1; // Used for other messages
54    private static final Locale LOCALE = java.util.Locale.getDefault();
55
56    private static volatile OSUService sOsuService;
57
58    private ListView osuListView;
59    private OsuListAdapter2 osuListAdapter;
60    private String message;
61
62    public MainActivity() {
63
64    }
65
66    @Override
67    protected void onResume() {
68        super.onResume();
69        if (message != null) {
70            showDialog(message);
71            message = null;
72        }
73    }
74
75    @Override
76    public void onCreate(Bundle savedInstanceState) {
77        super.onCreate(savedInstanceState);
78
79        Intent intent = getIntent();
80        Bundle bundle = intent.getExtras();
81
82        if (bundle == null) {   // User interaction
83            if (sOsuService == null) {
84                Intent serviceIntent = new Intent(this, OSUService.class);
85                serviceIntent.putExtra(ACTION_KEY, "dummy-key");
86                startService(serviceIntent);
87                return;
88            }
89
90            List<OSUInfo> osuInfos = sOsuService.getOsuInfos();
91
92            setContentView(R.layout.activity_main);
93            Log.d("osu", "osu count:" + osuInfos.size());
94            View noOsuView = findViewById(R.id.no_osu);
95            if (osuInfos.size() > 0) {
96                noOsuView.setVisibility(View.GONE);
97                osuListAdapter = new OsuListAdapter2(this, osuInfos);
98                osuListView = (ListView) findViewById(R.id.profile_list);
99                osuListView.setAdapter(osuListAdapter);
100                osuListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
101                    @Override
102                    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
103                        OSUInfo osuData = (OSUInfo) adapterView.getAdapter().getItem(position);
104                        Log.d("osu", "launch osu:" + osuData.getName(LOCALE)
105                                + " id:" + osuData.getOsuID());
106                        sOsuService.selectOsu(osuData.getOsuID());
107                        finish();
108                    }
109                });
110            } else {
111                noOsuView.setVisibility(View.VISIBLE);
112            }
113        } else if (intent.getAction().equals(AppBridge.ACTION_OSU_NOTIFICATION)) {
114            if (bundle.containsKey(AppBridge.OSU_COUNT)) {
115                showOsuCount(bundle.getInt("osu-count", 0), Collections.<OSUInfo>emptyList());
116            } else if (bundle.containsKey(AppBridge.PROV_SUCCESS)) {
117                showStatus(bundle.getBoolean(AppBridge.PROV_SUCCESS),
118                        bundle.getString(AppBridge.SP_NAME),
119                        bundle.getString(AppBridge.PROV_MESSAGE),
120                        null);
121            } else if (bundle.containsKey(AppBridge.DEAUTH)) {
122                showDeauth(bundle.getString(AppBridge.SP_NAME),
123                        bundle.getBoolean(AppBridge.DEAUTH),
124                        bundle.getInt(AppBridge.DEAUTH_DELAY),
125                        bundle.getString(AppBridge.DEAUTH_URL));
126            }
127            /*
128            else if (bundle.containsKey(AppBridge.OSU_INFO)) {
129                List<OsuData> osus = printOsuDataList(bundle.getParcelableArray(AppBridge.OSU_INFO));
130                showOsuList(osus);
131            }
132            */
133        }
134    }
135
136    private void showOsuCount(int osuCount, List<OSUInfo> osus) {
137        if (osuCount > 0) {
138            printOsuDataList(osus);
139            sendNotification(osuCount);
140        } else {
141            cancelNotification();
142        }
143        finish();
144    }
145
146    private void showStatus(boolean provSuccess, String spName, String provMessage,
147                            String remoteStatus) {
148        if (provSuccess) {
149            sendDialogMessage(
150                    String.format("Credentials for %s was successfully installed", spName));
151        } else {
152            if (spName != null) {
153                if (remoteStatus != null) {
154                    sendDialogMessage(
155                            String.format("Failed to install credentials for %s: %s: %s",
156                                    spName, provMessage, remoteStatus));
157                } else {
158                    sendDialogMessage(
159                            String.format("Failed to install credentials for %s: %s",
160                                    spName, provMessage));
161                }
162            } else {
163                sendDialogMessage(
164                        String.format("Failed to contact OSU: %s", provMessage));
165            }
166        }
167    }
168
169    private void showDeauth(String spName, boolean ess, int delay, String url) {
170        String delayReadable = getReadableTimeInSeconds(delay);
171        if (ess) {
172            if (delay > 60) {
173                sendDialogMessage(
174                        String.format("There is an issue connecting to %s [for the next %s]. " +
175                                "Please visit %s for details", spName, delayReadable, url));
176            } else {
177                sendDialogMessage(
178                        String.format("There is an issue connecting to %s. " +
179                                "Please visit %s for details", spName, url));
180            }
181        } else {
182            sendDialogMessage(
183                    String.format("There is an issue with the closest Access Point for %s. " +
184                                    "You may wait %s or move to another Access Point to " +
185                                    "regain access. Please visit %s for details.",
186                            spName, delayReadable, url));
187        }
188    }
189
190    private static final String ACTION_KEY = "action";
191
192    public static class WifiReceiver extends BroadcastReceiver {
193        @Override
194        public void onReceive(Context c, Intent intent) {
195            Log.d(OSUManager.TAG, "OSU App got intent: " + intent.getAction());
196            Intent serviceIntent;
197            serviceIntent = new Intent(c, OSUService.class);
198            serviceIntent.putExtra(ACTION_KEY, intent.getAction());
199            serviceIntent.putExtras(intent);
200            c.startService(serviceIntent);
201        }
202    }
203
204    public static class OSUService extends IntentService {
205        private OSUManager mOsuManager;
206        private final IBinder mBinder = new Binder();
207
208        public OSUService() {
209            super("OSUService");
210        }
211
212        @Override
213        public int onStartCommand(Intent intent, int flags, int startId) {
214            onHandleIntent(intent);
215            return START_STICKY;
216        }
217
218        @Override
219        public void onCreate() {
220            super.onCreate();
221            Log.d("YYY", String.format("Service %x running, OSU %x",
222                    System.identityHashCode(this), System.identityHashCode(mOsuManager)));
223            if (mOsuManager == null) {
224                mOsuManager = new OSUManager(this);
225            }
226            sOsuService = this;
227        }
228
229        @Override
230        public void onDestroy() {
231            super.onDestroy();
232            Log.d("YYY", String.format("Service %x killed", System.identityHashCode(this)));
233        }
234
235        @Override
236        public IBinder onBind(Intent intent) {
237            return mBinder;
238        }
239
240        @Override
241        protected void onHandleIntent(Intent intent) {
242            Bundle bundle = intent.getExtras();
243            WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
244            Log.d(OSUManager.TAG, "OSU Service got intent: " + intent.getStringExtra(ACTION_KEY));
245            switch (intent.getStringExtra(ACTION_KEY)) {
246                case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
247                    mOsuManager.pushScanResults(wifiManager.getScanResults());
248                    break;
249                case WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION:
250                    long bssid = bundle.getLong(WifiManager.EXTRA_PASSPOINT_WNM_BSSID);
251                    String url = bundle.getString(WifiManager.EXTRA_PASSPOINT_WNM_URL);
252
253                    try {
254                        if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_METHOD)) {
255                            int method = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_METHOD);
256                            if (method != OSUProvider.OSUMethod.SoapXml.ordinal()) {
257                                Log.w(OSUManager.TAG, "Unsupported remediation method: " + method);
258                            }
259                            PasspointMatch match = null;
260                            if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH)) {
261                                int ordinal =
262                                        bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH);
263                                if (ordinal >= 0 && ordinal < PasspointMatch.values().length) {
264                                    match = PasspointMatch.values()[ordinal];
265                                }
266                            }
267                            mOsuManager.wnmRemediate(bssid, url, match);
268                        } else if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_ESS)) {
269                            boolean ess = bundle.getBoolean(WifiManager.EXTRA_PASSPOINT_WNM_ESS);
270                            int delay = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_DELAY);
271                            mOsuManager.deauth(bssid, ess, delay, url);
272                        } else {
273                            Log.w(OSUManager.TAG, "Unknown WNM event");
274                        }
275                    } catch (IOException | SAXException e) {
276                        Log.w(OSUManager.TAG, "Remediation event failed to parse: " + e);
277                    }
278                    break;
279                case WifiManager.PASSPOINT_ICON_RECEIVED_ACTION:
280                    mOsuManager.notifyIconReceived(
281                            bundle.getLong(WifiManager.EXTRA_PASSPOINT_ICON_BSSID),
282                            bundle.getString(WifiManager.EXTRA_PASSPOINT_ICON_FILE),
283                            bundle.getByteArray(WifiManager.EXTRA_PASSPOINT_ICON_DATA));
284                    break;
285                case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
286                    mOsuManager.networkConfigChange((WifiConfiguration)
287                            intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION));
288                    break;
289                case WifiManager.WIFI_STATE_CHANGED_ACTION:
290                    int state = bundle.getInt(WifiManager.EXTRA_WIFI_STATE);
291                    if (state == WifiManager.WIFI_STATE_DISABLED) {
292                        mOsuManager.wifiStateChange(false);
293                    } else if (state == WifiManager.WIFI_STATE_ENABLED) {
294                        mOsuManager.wifiStateChange(true);
295                    }
296                    break;
297                case WifiManager.NETWORK_STATE_CHANGED_ACTION:
298                    mOsuManager.networkConnectEvent((WifiInfo)
299                            intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
300                    break;
301            }
302        }
303
304        public List<OSUInfo> getOsuInfos() {
305            return mOsuManager.getAvailableOSUs();
306        }
307
308        public void selectOsu(int id) {
309            mOsuManager.setOSUSelection(id);
310        }
311    }
312
313    private String getReadableTimeInSeconds(int timeSeconds) {
314        long hours = TimeUnit.SECONDS.toHours(timeSeconds);
315        long minutes = TimeUnit.SECONDS.toMinutes(timeSeconds) - TimeUnit.HOURS.toMinutes(hours);
316        long seconds =
317                timeSeconds - TimeUnit.HOURS.toSeconds(hours) - TimeUnit.MINUTES.toSeconds(minutes);
318        if (hours > 0) {
319            return String.format("%02d:%02d:%02d", hours, minutes, seconds);
320        } else {
321            return String.format("%ds", seconds);
322        }
323    }
324
325    private void sendNotification(int count) {
326        Notification.Builder builder =
327                new Notification.Builder(this)
328                        .setContentTitle(String.format("%s OSU available", count))
329                        .setContentText("Choose one to connect")
330                        .setSmallIcon(android.R.drawable.ic_dialog_info)
331                        .setAutoCancel(false);
332        Intent resultIntent = new Intent(this, MainActivity.class);
333
334        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
335        stackBuilder.addParentStack(MainActivity.class);
336        stackBuilder.addNextIntent(resultIntent);
337        PendingIntent resultPendingIntent =
338                stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
339        builder.setContentIntent(resultPendingIntent);
340        NotificationManager notificationManager =
341                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
342        notificationManager.notify(NOTIFICATION_ID, builder.build());
343    }
344
345    private void cancelNotification() {
346        NotificationManager notificationManager =
347                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
348        notificationManager.cancel(NOTIFICATION_ID);
349    }
350
351    private void sendDialogMessage(String message) {
352//        sendNotificationMessage(message);
353        this.message = message;
354    }
355
356    private void showDialog(String message) {
357        AlertDialog.Builder builder = new AlertDialog.Builder(this);
358        builder.setMessage(message)
359                .setTitle("OSU");
360        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
361            @Override
362            public void onCancel(DialogInterface dialogInterface) {
363                dialogInterface.cancel();
364                finish();
365            }
366        });
367        AlertDialog dialog = builder.create();
368        dialog.show();
369    }
370
371    private void sendNotificationMessage(String title) {
372        Notification.Builder builder =
373                new Notification.Builder(this)
374                        .setContentTitle(title)
375                        .setContentText("Click to dismiss.")
376                        .setSmallIcon(android.R.drawable.ic_dialog_info)
377                        .setAutoCancel(true);
378        NotificationManager notificationManager =
379                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
380        notificationManager.notify(NOTIFICATION_MESSAGE_ID, builder.build());
381    }
382
383    private static class OsuListAdapter2 extends ArrayAdapter<OSUInfo> {
384        private Activity activity;
385
386        public OsuListAdapter2(Activity activity, List<OSUInfo> osuDataList) {
387            super(activity, R.layout.list_item, osuDataList);
388            this.activity = activity;
389        }
390
391        @Override
392        public View getView(int position, View convertView, ViewGroup parent) {
393            View view = convertView;
394            if (view == null) {
395                view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
396            }
397            OSUInfo osuData = getItem(position);
398            TextView osuName = (TextView) view.findViewById(R.id.profile_name);
399            osuName.setText(osuData.getName(LOCALE));
400            TextView osuDetail = (TextView) view.findViewById(R.id.profile_detail);
401            osuDetail.setText(osuData.getServiceDescription(LOCALE));
402            ImageView osuIcon = (ImageView) view.findViewById(R.id.profile_logo);
403            byte[] iconData = osuData.getIconFileElement().getIconData();
404            osuIcon.setImageDrawable(
405                    new BitmapDrawable(activity.getResources(),
406                            BitmapFactory.decodeByteArray(iconData, 0, iconData.length)));
407            return view;
408        }
409    }
410
411    private void printOsuDataList(List<OSUInfo> osuDataList) {
412        for (OSUInfo osuData : osuDataList) {
413            Log.d("osu", String.format("OSUData:[%s][%s][%d]",
414                    osuData.getName(LOCALE), osuData.getServiceDescription(LOCALE),
415                    osuData.getOsuID()));
416        }
417    }
418
419}
420