minutes and (z) seconds"
46 | */
47 | public static String millisToLongDHMS(long duration) {
48 | StringBuffer res = new StringBuffer();
49 | boolean isDay = false, isHr = false, isMin = false;
50 | long temp = 0;
51 | if (duration >= ONE_SECOND) {
52 | temp = duration / ONE_DAY;
53 | if (temp > 0) {
54 | isDay = true;
55 | duration -= temp * ONE_DAY;
56 | res.append(temp >= 10 ? temp : "0" + temp).append("d ");
57 | }
58 | temp = duration / ONE_HOUR;
59 | if (temp > 0) {
60 | isHr = true;
61 | duration -= temp * ONE_HOUR;
62 | res.append(temp >= 10 ? temp : "0" + temp).append("h ");
63 | }
64 | if (isDay)
65 | return res.toString() + ((temp > 0) ? "" : "00h");
66 | temp = duration / ONE_MINUTE;
67 | if (temp > 0) {
68 | isMin = true;
69 | duration -= temp * ONE_MINUTE;
70 | res.append(temp >= 10 ? temp : "0" + temp).append("m ");
71 | }
72 | if (isHr)
73 | return res.toString() + ((temp > 0) ? "" : "00m");
74 |
75 | temp = duration / ONE_SECOND;
76 | if (temp > 0) {
77 | res.append(temp >= 10 ? temp : "0" + temp).append("s");
78 | }
79 | return res.toString() + ((temp > 0) ? "" : "00s");
80 | } else {
81 | return "0s";
82 | }
83 | }
84 |
85 | public static String humanReadableByteCount(long bytes, boolean si) {
86 | int unit = !si ? 1000 : 1024;
87 | if (bytes < unit)
88 | return bytes + " B";
89 | int exp = (int) (Math.log(bytes) / Math.log(unit));
90 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1)
91 | + (si ? "" : "i");
92 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
93 | }
94 |
95 | public static int getRandomColor() {
96 | Random rnd = new Random();
97 | return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
98 | }
99 |
100 | public static String[] toStringArray(String jArray) {
101 | if (jArray == null)
102 | return null;
103 | try {
104 | JSONArray array = new JSONArray(jArray);
105 | String[] arr = new String[array.length()];
106 | for (int i = 0; i < arr.length; i++) {
107 | arr[i] = array.optString(i);
108 | }
109 | return arr;
110 | } catch (JSONException jse) {
111 | return null;
112 | }
113 | }
114 |
115 | public static int getTargetSDKVersion(Context context) {
116 | try {
117 | PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
118 | return packageInfo.applicationInfo.targetSdkVersion;
119 | } catch (PackageManager.NameNotFoundException nnf) {
120 | return -1;
121 | }
122 | }
123 |
124 | public static boolean isMobileDataEnabled(Context context) {
125 | return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 ?
126 | Settings.Global.getInt(context.getContentResolver(), "mobile_data", 1) == 1 :
127 | Settings.Secure.getInt(context.getContentResolver(), "mobile_data", 1) == 1;
128 | }
129 |
130 | public static byte[] macAddressToByteArray(String macString) {
131 | String[] mac = macString.split("[:\\s-]");
132 | byte[] macAddress = new byte[6];
133 | for (int i = 0; i < mac.length; i++) {
134 | macAddress[i] = Integer.decode("0x" + mac[i]).byteValue();
135 | }
136 | return macAddress;
137 | }
138 |
139 | public static boolean isOreoOrAbove() {
140 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SHAREthem
2 | 
3 |
4 | SHAREthem library facilitates P2P file sharing and transfers between devices using WiFi Hotspot.
5 | Also an attempt to simulate popular [SHAREit](https://play.google.com/store/apps/details?id=com.lenovo.anyshare.gps&hl=en) App functionality and improvise it by supporting multiple receivers at one go
6 |
7 | Library also supports App (Android) to Web/Mobile Browser transfers if Receiver has no App installed.
8 |
9 |
10 | Download the Working version from Play Store
11 |
12 | [![PlayStore][playstore-image]][playstore-url]
13 | [playstore-image]: screens/google-play-badge.png
14 | [playstore-url]: https://play.google.com/store/apps/details?id=com.tml.sharethem.demo
15 |
16 |
17 | ### Developed by
18 | [Srihari Yachamaneni](https://github.com/Sriharia) ([@srihari_y](https://twitter.com/srihari_y))
19 |
20 | ### Features
21 |
22 | * File transfers and sharing.
23 | * Supports downloads on Browser if Receiver has no App installed.
24 | * Easy to Implement.
25 | * No permissions required. (library handles them based on targetSdkVersion)
26 |
27 | ### Usage
28 |
29 | ## UPDATE
30 | Added Android-8, OREO support with default Android Hotspot functionality which comes with password protected Hotspot and so Receiver has to explicitly key-in password, inline with SHAREit functionality.
31 |
32 | ## SHARE mode
33 | To start SHARE mode, you need to pass an array of strings holding references of files you want to share along with port(optional) and sender name using an Intent to start SHAREthemActivity
34 |
35 | ```
36 | Intent intent = new Intent(getApplicationContext(), SHAREthemActivity.class);
37 | intent.putExtra(ShareService.EXTRA_FILE_PATHS, new String[]{[path to file1], [path to file2]}); // mandatory
38 | intent.putExtra(ShareService.EXTRA_PORT, 52287); //optional but preferred. PORT value is hardcoded for Oreo and above since it's not possible to set SSID with which port info can be extracted on Receiver side.
39 | intent.putExtra(ShareService.EXTRA_SENDER_NAME, "Sri"); //optional. Sender name can't be relayed to receiver for Oreo & above
40 | startActivity(intent);
41 | ```
42 |
43 | `ShareService.EXTRA_FILE_PATHS`: holds location references to local files on device.
44 |
45 | `ShareService.EXTRA_PORT` (optional): in case you want to start SHAREserver on a specific port. Passing 0 or skipping this lets system to assign its own. One of the downside of letting system to do this is SSID may be not the same for a subsequent Sharing session. (PORT number is used in algorithm to generate SSID)
46 |
47 | `ShareService.EXTRA_SENDER_NAME` (optional): used by Receiver to display connection information.
48 |
49 | ## Receiver mode
50 | Starting receiver mode is pretty simple as no intent extras needed. Receiver Activity starts scanning for senders automatically, you can turn off Receiver mode anytime though.
51 |
52 | ```
53 | startActivity(new Intent(getApplicationContext(), ReceiverActivity.class));
54 | ```
55 |
56 | End with an example of getting some data out of the system or using it for a little demo
57 |
58 | ### Read more about implementation on my blog [here](https://srihary.com/)
59 |
60 | ## DEMO Built With
61 |
62 | * [android-filepicker](https://github.com/Angads25/android-filepicker) - Android Library to select files/directories from Device Storage.
63 |
64 | ## IMPORTANT NOTE
65 | * increasing targetSdkVersion version might impact behaviour of this library
66 | 1. if targetSdkVersion >= 23
67 | * ShareActivity has to check for System Write permissions to proceed
68 | * Get Wifi Scan results method needs GPS to be ON and COARSE location permission.
69 | 2. library checks the targetSdkVersion to take care of above scenarios if targetSdkVersion > 20
70 | 3. If an application's targetSdkVersion is LOLLIPOP or newer, network communication may not use Wi-Fi even if Wi-Fi is connected with no interner.
71 | this might impact when Receiver connectivity to SHAREthem hotspot, library checks for this scenario and prompts user to disable data
72 | For more info: https://developer.android.com/reference/android/net/wifi/WifiManager.html#enableNetwork
73 |
74 | ## Troubleshooting
75 |
76 | #### Receiver Connected but not able to display Sender info:
77 | As mentioned above, on API level 21 & above, n/w communication may not use Wifi with no internet access if MOBILE DATA has internet access. Library can prompt to disable MOBILE DATA if this scenario is met, but not yet implemented.
78 | So turn-off mobile to make Receiver work.
79 | #### ReceiverActivity cannot connect to SHAREthem Wifi:
80 | Receiver tries to connect to SHAREthem Wifi in every way possible, but throws an error dialog if it fails. Dialog says to manually select SHAREthem Wifi on WIfi Settings.
81 | As a last resort, you can always manually connect to Sender Wifi.
82 |
83 | ## Known Issues
84 | * On some devices with API level 21 and above, ```connectToOpenHotspot()``` method on WifiUtils disables other Wifi networks in-order to connect to SHAREthem WIfi Hotspot. Ideally increasing priority should be enough for making WifiConfiguration but recent Android version are making it hard for Wifi n.ws with no internet connectivity.
85 | * Enabling SHARE mode when System Wifi Hotspot is already active (enabled by user via Settings) might not work.
86 |
87 | ## Screenshots
88 | 
89 | 
90 | 
91 | 
92 | 
93 | 
94 | 
95 | ## License
96 |
97 | Copyright 2017 Srihari Yachamaneni
98 |
99 | Licensed under the Apache License, Version 2.0 (the "License");
100 | you may not use this file except in compliance with the License.
101 | You may obtain a copy of the License at
102 |
103 | http://www.apache.org/licenses/LICENSE-2.0
104 |
105 | Unless required by applicable law or agreed to in writing, software
106 | distributed under the License is distributed on an "AS IS" BASIS,
107 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
108 | See the License for the specific language governing permissions and
109 | limitations under the License.
110 |
111 | see the [LICENSE.md](LICENSE.md) file for details
112 |
113 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/sender/SHAREthemServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Srihari Yachamaneni
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 | package com.tml.sharethem.sender;
17 |
18 | import android.content.Context;
19 | import android.content.Intent;
20 | import android.content.pm.ResolveInfo;
21 | import android.graphics.Bitmap;
22 | import android.graphics.BitmapFactory;
23 | import android.text.TextUtils;
24 | import android.util.Log;
25 |
26 | import com.tml.sharethem.R;
27 |
28 | import org.json.JSONArray;
29 |
30 | import java.io.BufferedReader;
31 | import java.io.ByteArrayInputStream;
32 | import java.io.ByteArrayOutputStream;
33 | import java.io.File;
34 | import java.io.IOException;
35 | import java.io.InputStreamReader;
36 | import java.util.Arrays;
37 |
38 | /**
39 | * A Tiny Http Server extended from {@link NanoHTTPD}. Once started, serves selected files from {@link SHAREthemActivity} on an assigned PORT.
40 | *
41 | * Created by Sri on 18/12/16.
42 | */
43 |
44 | class SHAREthemServer extends NanoHTTPD {
45 |
46 | private static final String TAG = "ShareServer";
47 |
48 | private static final String MIME_JSON = "application/json";
49 | private static final String MIME_FORCE_DOWNLOAD = "application/force-download";
50 | private static final String MIME_PNG = "image/png";
51 |
52 | private String[] m_filesTobeHosted;
53 | private FileTransferStatusListener m_clientsFileTransferListener;
54 | private Context m_context;
55 |
56 | public SHAREthemServer(String host_name, int port) {
57 | super(host_name, port);
58 | }
59 |
60 | public SHAREthemServer(Context context, FileTransferStatusListener statusListener, String[] filesToBeHosted) {
61 | this(null, 0);
62 | m_context = context;
63 | m_clientsFileTransferListener = statusListener;
64 | m_filesTobeHosted = filesToBeHosted;
65 | }
66 |
67 | public SHAREthemServer(Context context, FileTransferStatusListener statusListener, String[] filesToBeHosted, int port) {
68 | this(null, port);
69 | m_context = context;
70 | m_clientsFileTransferListener = statusListener;
71 | m_filesTobeHosted = filesToBeHosted;
72 | }
73 |
74 | @Override
75 | public void stop() {
76 | super.stop();
77 | }
78 |
79 | @Override
80 | public Response serve(IHTTPSession session) {
81 | Response res = null;
82 | try {
83 | String url = session.getUri();
84 | Log.d(TAG, "request uri: " + url);
85 | if (TextUtils.isEmpty(url) || url.equals("/") || url.contains("/open"))
86 | res = createHtmlResponse();
87 | else if (url.equals("/status"))
88 | res = new NanoHTTPD.Response(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, "Available");
89 | else if (url.equals("/apk"))
90 | res = createApkResponse(session.getHeaders().get("http-client-ip"));
91 | else if (url.equals("/logo") || url.equals("/favicon.ico"))
92 | res = createLogoResponse();
93 | else if (url.equals("/files"))
94 | res = createFilePathsResponse();
95 | else if (url.contains("/file/")) {
96 | int index = Integer.parseInt(url.replace("/file/", ""));
97 | if (index != -1)
98 | res = createFileResponse(m_filesTobeHosted[index], session.getHeaders().get("http-client-ip"));
99 | }
100 | } catch (Exception ioe) {
101 | ioe.printStackTrace();
102 | res = createErrorResponse(Response.Status.FORBIDDEN, ioe.getMessage());
103 | } finally {
104 | if (null == res)
105 | res = createForbiddenResponse();
106 | }
107 | res.addHeader("Accept-Ranges", "bytes");
108 | res.addHeader("Access-Control-Allow-Origin", "*");
109 | res.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
110 | return res;
111 | }
112 | // increasing targetSdkVersion version might impact behaviour of this library
113 | // if targetSdkVersion >= 23
114 | // 1. ShareActivity has to check for System Write permissions to proceed
115 | // 2. Get Wifi Scan results method needs GPS to be ON and COARSE location permission
116 | // library checks the targetSdkVersion to take care of above scenarios
117 | // if targetSdkVersion > 20
118 | // If an application's target SDK version is LOLLIPOP or newer, network communication may not use Wi-Fi even if Wi-Fi is connected;
119 | // this might impact when Receiver connectivity to SHAREthem hotspot, library checks for this scenario and prompts user to disable data
120 | // For more info: https://developer.android.com/reference/android/net/wifi/WifiManager.html#enableNetwork(int, boolean)
121 |
122 | /**
123 | * Creates an Error {@link com.tml.sharethem.sender.NanoHTTPD.Response} with
124 | *
125 | * @param status error Status like Response.Status.FORBIDDEN
126 | * @param message error message
127 | * @return {@link com.tml.sharethem.sender.NanoHTTPD.Response}
128 | */
129 | private Response createErrorResponse(Response.Status status, String message) {
130 | Log.e(TAG, "error while creating response: " + message);
131 | return new Response(status, NanoHTTPD.MIME_PLAINTEXT, message);
132 | }
133 |
134 | private Response createForbiddenResponse() {
135 | return createErrorResponse(Response.Status.FORBIDDEN,
136 | "FORBIDDEN: Reading file failed.");
137 | }
138 |
139 | /**
140 | * Creates a success {@link com.tml.sharethem.sender.NanoHTTPD.Response} with Shared Files URLS data in @{@link com.google.gson.JsonArray} format
141 | *
142 | * @return {@link com.tml.sharethem.sender.NanoHTTPD.Response}
143 | */
144 | private Response createFilePathsResponse() {
145 | return new NanoHTTPD.Response(Response.Status.OK, MIME_JSON, new JSONArray(Arrays.asList(m_filesTobeHosted)).toString());
146 | }
147 |
148 | /**
149 | * Creates a success Response with binary data of file in {@link SHAREthemServer#m_filesTobeHosted} with provided index.
150 | *
151 | * @param clientIp Receiver IP to which Response is intended for
152 | * @param fileUrl url of file among Shared files array
153 | * @return {@link com.tml.sharethem.sender.NanoHTTPD.Response}
154 | * @throws IOException
155 | */
156 | private Response createFileResponse(String fileUrl, String clientIp) throws IOException {
157 | final File file = new File(fileUrl);
158 | Log.d(TAG, "resolve info found, file location: " + file.getAbsolutePath() + ", file length: " + file.length() + ", file name: " + file.getName());
159 | Response res = new Response(Response.Status.OK, MIME_FORCE_DOWNLOAD, clientIp, file, m_clientsFileTransferListener);
160 | res.addHeader("Content-Length", "" + file.length());
161 | res.addHeader("Content-Disposition", "attachment; filename='" + file.getName() + "'");
162 | return res;
163 | }
164 |
165 | private Response createHtmlResponse() {
166 | String answer = "";
167 | try {
168 | BufferedReader reader = new BufferedReader(
169 | new InputStreamReader(m_context.getAssets().open("web_talk.html")));
170 | String line = "";
171 | while ((line = reader.readLine()) != null) {
172 | answer += line;
173 | }
174 | } catch (IOException ioe) {
175 | Log.e("NanoHTTPD", ioe.toString());
176 | }
177 | return new NanoHTTPD.Response(answer);
178 | }
179 |
180 | private Response createLogoResponse() {
181 | try {
182 | Bitmap bitmap = BitmapFactory.decodeResource(m_context.getResources(), R.mipmap.ic_launcher);
183 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
184 | bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
185 | byte[] bitmapdata = bos.toByteArray();
186 | ByteArrayInputStream bs = new ByteArrayInputStream(bitmapdata);
187 | Response res = new Response(Response.Status.OK, MIME_PNG, bs);
188 | res.addHeader("Accept-Ranges", "bytes");
189 | return res;
190 | } catch (Exception e) {
191 | e.printStackTrace();
192 | }
193 | return createForbiddenResponse();
194 | }
195 |
196 | private Response createApkResponse(String ip) throws IOException {
197 | Response res = null;
198 | final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
199 | mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
200 | mainIntent.setPackage(m_context.getPackageName());
201 | ResolveInfo info = m_context.getPackageManager().resolveActivity(mainIntent, 0);
202 | if (null != info) {
203 | res = createFileResponse(info.activityInfo.applicationInfo.publicSourceDir, ip);
204 | }
205 | return res;
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/utils/HotspotControl.java:
--------------------------------------------------------------------------------
1 | package com.tml.sharethem.utils;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiConfiguration;
5 | import android.net.wifi.WifiManager;
6 | import android.os.Build;
7 | import android.os.Handler;
8 | import android.support.annotation.RequiresApi;
9 | import android.util.Log;
10 |
11 | import com.tml.sharethem.sender.SHAREthemActivity;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.FileReader;
15 | import java.io.IOException;
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.lang.reflect.Method;
18 | import java.net.Inet4Address;
19 | import java.net.Inet6Address;
20 | import java.net.InetAddress;
21 | import java.net.NetworkInterface;
22 | import java.util.ArrayList;
23 | import java.util.Enumeration;
24 | import java.util.List;
25 | import java.util.concurrent.CountDownLatch;
26 | import java.util.concurrent.ExecutorService;
27 | import java.util.concurrent.Executors;
28 | import java.util.regex.Pattern;
29 |
30 | import static com.tml.sharethem.utils.Utils.isOreoOrAbove;
31 |
32 | public class HotspotControl {
33 |
34 | private static final String TAG = "HotspotControl";
35 |
36 | /**
37 | * Saves the existing configuration before creating SHAREthem hotspot. Same is used to restore the user Hotspot Config when SHAREthem hotspot is disabled
38 | */
39 | private WifiConfiguration m_original_config_backup;
40 |
41 | /**
42 | * Not necessary to {@link HotspotControl} functionality but added here to help {@link SHAREthemActivity} with port number to display connection URL
43 | */
44 | private int m_shareServerListeningPort;
45 |
46 | private static Method getWifiApConfiguration;
47 | private static Method getWifiApState;
48 | private static Method isWifiApEnabled;
49 | private static Method setWifiApEnabled;
50 | private static Method setWifiApConfiguration;
51 |
52 | private WifiManager wm;
53 | private String deviceName;
54 |
55 | private WifiManager.LocalOnlyHotspotReservation mReservation;
56 |
57 | private static HotspotControl instance = null;
58 | static final String DEFAULT_DEVICE_NAME = "Unknown";
59 |
60 | static {
61 | for (Method method : WifiManager.class.getDeclaredMethods()) {
62 | switch (method.getName()) {
63 | case "getWifiApConfiguration":
64 | getWifiApConfiguration = method;
65 | break;
66 | case "getWifiApState":
67 | getWifiApState = method;
68 | break;
69 | case "isWifiApEnabled":
70 | isWifiApEnabled = method;
71 | break;
72 | case "setWifiApEnabled":
73 | setWifiApEnabled = method;
74 | break;
75 | case "setWifiApConfiguration":
76 | setWifiApConfiguration = method;
77 | break;
78 | }
79 | }
80 | }
81 |
82 | private HotspotControl(Context context) {
83 | wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
84 | deviceName = WifiUtils.getDeviceName(wm);
85 | }
86 |
87 | public static HotspotControl getInstance(Context context) {
88 | if (instance == null) {
89 | instance = new HotspotControl(context);
90 | }
91 | return instance;
92 | }
93 |
94 | public boolean isEnabled() {
95 | if (isOreoOrAbove()) {
96 | return null != mReservation && mReservation.getWifiConfiguration() != null;
97 | }
98 | Object result = invokeSilently(isWifiApEnabled, wm);
99 | if (result == null) {
100 | return false;
101 | }
102 | return (Boolean) result;
103 | }
104 |
105 | private static Object invokeSilently(Method method, Object receiver, Object... args) {
106 | try {
107 | return method.invoke(receiver, args);
108 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
109 | Log.e(TAG, "exception in invoking methods: " + e.getMessage());
110 | }
111 | return null;
112 | }
113 |
114 | public static boolean isSupported() {
115 | return isOreoOrAbove() || (getWifiApState != null
116 | && isWifiApEnabled != null
117 | && setWifiApEnabled != null
118 | && getWifiApConfiguration != null);
119 | }
120 |
121 | public WifiConfiguration getConfiguration() {
122 | if (isOreoOrAbove()) {
123 | return mReservation.getWifiConfiguration();
124 | }
125 | Object result = invokeSilently(getWifiApConfiguration, wm);
126 | if (result == null) {
127 | return null;
128 | }
129 | return (WifiConfiguration) result;
130 | }
131 |
132 | private boolean setHotspotEnabled(WifiConfiguration config, boolean enabled) {
133 | Object result = invokeSilently(setWifiApEnabled, wm, config, enabled);
134 | if (result == null) {
135 | return false;
136 | }
137 | return (Boolean) result;
138 | }
139 |
140 | private boolean setHotspotConfig(WifiConfiguration config) {
141 | Object result = invokeSilently(setWifiApConfiguration, wm, config);
142 | if (result == null) {
143 | return false;
144 | }
145 | return (Boolean) result;
146 | }
147 |
148 | @RequiresApi(api = Build.VERSION_CODES.O)
149 | public void turnOnOreoHotspot(int port) {
150 | m_shareServerListeningPort = port;
151 | wm.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {
152 |
153 | @Override
154 | public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
155 | super.onStarted(reservation);
156 | Log.d(TAG, "Wifi Hotspot is on now");
157 | mReservation = reservation;
158 | }
159 |
160 | @Override
161 | public void onStopped() {
162 | super.onStopped();
163 | Log.d(TAG, "onStopped: ");
164 | mReservation = null;
165 | }
166 |
167 | @Override
168 | public void onFailed(int reason) {
169 | super.onFailed(reason);
170 | Log.d(TAG, "onFailed: ");
171 | mReservation = null;
172 | }
173 | }, new Handler());
174 | }
175 |
176 | /**
177 | * Creates a new OPEN {@link WifiConfiguration} and invokes {@link WifiManager}'s method via Reflection to enable Hotspot
178 | *
179 | * @param name Open HotSpot SSID
180 | * @param port Port number assigned to {@link com.tml.sharethem.sender.SHAREthemServer}, not used anywhere in {@link HotspotControl} but helps {@link SHAREthemActivity} to display port info
181 | * @return true if {@link WifiManager}'s method is successfully called
182 | */
183 | public boolean turnOnPreOreoHotspot(String name, int port) {
184 | wm.setWifiEnabled(false);
185 |
186 | m_shareServerListeningPort = port;
187 |
188 | m_original_config_backup = getConfiguration();
189 |
190 | //Create new Open Wifi Configuration
191 | WifiConfiguration wifiConf = new WifiConfiguration();
192 | wifiConf.SSID = "\"" + name + "\"";
193 | wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
194 | wm.addNetwork(wifiConf);
195 |
196 | //save it
197 | wm.saveConfiguration();
198 | return setHotspotEnabled(wifiConf, true);
199 | }
200 |
201 | // /**
202 | // * Calls {@link WifiManager}'s method via reflection to enabled Hotspot with existing configuration
203 | // *
204 | // * @return
205 | // */
206 | // public boolean enable() {
207 | // wm.setWifiEnabled(false);
208 | // return setHotspotEnabled(getConfiguration(), true);
209 | // }
210 |
211 | public boolean disable() {
212 | if (isOreoOrAbove()) {
213 | if (mReservation != null) {
214 | mReservation.close();
215 | mReservation = null;
216 | return true;
217 | }
218 | return false;
219 | } else {
220 | //restore original hotspot config if available
221 | if (null != m_original_config_backup)
222 | setHotspotConfig(m_original_config_backup);
223 | m_shareServerListeningPort = 0;
224 | return setHotspotEnabled(m_original_config_backup, false);
225 | }
226 | }
227 |
228 | public int getShareServerListeningPort() {
229 | return m_shareServerListeningPort;
230 | }
231 |
232 | public String getHostIpAddress() {
233 | if (!isEnabled()) {
234 | return null;
235 | }
236 | Inet4Address inet4 = getInetAddress(Inet4Address.class);
237 | if (null != inet4)
238 | return inet4.toString();
239 | Inet6Address inet6 = getInetAddress(Inet6Address.class);
240 | if (null != inet6)
241 | return inet6.toString();
242 | return null;
243 | }
244 |
245 | private T getInetAddress(Class addressType) {
246 | try {
247 | Enumeration ifaces = NetworkInterface.getNetworkInterfaces();
248 | while (ifaces.hasMoreElements()) {
249 | NetworkInterface iface = ifaces.nextElement();
250 |
251 | if (!iface.getName().equals(deviceName)) {
252 | continue;
253 | }
254 |
255 | Enumeration addrs = iface.getInetAddresses();
256 | while (addrs.hasMoreElements()) {
257 | InetAddress addr = addrs.nextElement();
258 |
259 | if (addressType.isInstance(addr)) {
260 | return addressType.cast(addr);
261 | }
262 | }
263 | }
264 | } catch (IOException e) {
265 | Log.e(TAG, "exception in fetching inet address: " + e.getMessage());
266 | }
267 | return null;
268 | }
269 |
270 | public static class WifiScanResult {
271 | public String ip;
272 |
273 | public WifiScanResult(String ipAddr) {
274 | this.ip = ipAddr;
275 | }
276 |
277 | @Override
278 | public boolean equals(Object o) {
279 | if (o == this)
280 | return true;
281 | if (o == null || o.getClass() != this.getClass())
282 | return false;
283 | WifiScanResult v = (WifiScanResult) o;
284 | return ip.equals(v.ip);
285 | }
286 | }
287 |
288 | public List getConnectedWifiClients(final int timeout,
289 | final WifiClientConnectionListener listener) {
290 | List wifiScanResults = getWifiClients();
291 | if (wifiScanResults == null) {
292 | listener.onWifiClientsScanComplete();
293 | return null;
294 | }
295 | final CountDownLatch latch = new CountDownLatch(wifiScanResults.size());
296 | ExecutorService es = Executors.newCachedThreadPool();
297 | for (final WifiScanResult c : wifiScanResults) {
298 | es.submit(new Runnable() {
299 | public void run() {
300 | try {
301 | InetAddress ip = InetAddress.getByName(c.ip);
302 | if (ip.isReachable(timeout)) {
303 | listener.onClientConnectionAlive(c);
304 | Thread.sleep(1000);
305 | } else listener.onClientConnectionDead(c);
306 | } catch (IOException e) {
307 | Log.e(TAG, "io exception while trying to reach client, ip: " + (c.ip));
308 | } catch (InterruptedException ire) {
309 | Log.e(TAG, "InterruptedException: " + ire.getMessage());
310 | }
311 | latch.countDown();
312 | }
313 | });
314 | }
315 | new Thread() {
316 | public void run() {
317 | try {
318 | latch.await();
319 | } catch (InterruptedException e) {
320 | Log.e(TAG, "listing clients countdown interrupted", e);
321 | }
322 | listener.onWifiClientsScanComplete();
323 | }
324 | }.start();
325 | return wifiScanResults;
326 | }
327 |
328 | public List getWifiClients() {
329 | if (!isEnabled()) {
330 | return null;
331 | }
332 | List result = new ArrayList<>();
333 |
334 | // Basic sanity checks
335 | Pattern macPattern = Pattern.compile("..:..:..:..:..:..");
336 |
337 | BufferedReader br = null;
338 | try {
339 | br = new BufferedReader(new FileReader("/proc/net/arp"));
340 | String line;
341 | while ((line = br.readLine()) != null) {
342 | String[] parts = line.split(" +");
343 | if (parts.length < 4 || !macPattern.matcher(parts[3]).find()) {
344 | continue;
345 | }
346 | String ipAddr = parts[0];
347 | result.add(new WifiScanResult(ipAddr));
348 | }
349 | } catch (IOException e) {
350 | Log.e(TAG, "exception in getting clients", e);
351 | } finally {
352 | try {
353 | if (br != null) {
354 | br.close();
355 | }
356 | } catch (IOException e) {
357 | Log.e(TAG, "", e);
358 | }
359 | }
360 |
361 | return result;
362 | }
363 |
364 | public interface WifiClientConnectionListener {
365 | void onClientConnectionAlive(WifiScanResult c);
366 |
367 | void onClientConnectionDead(WifiScanResult c);
368 |
369 | void onWifiClientsScanComplete();
370 | }
371 |
372 | }
373 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/receiver/FilesListingFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Srihari Yachamaneni
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 | package com.tml.sharethem.receiver;
17 |
18 | import android.app.DownloadManager;
19 | import android.content.Context;
20 | import android.net.Uri;
21 | import android.os.AsyncTask;
22 | import android.os.Bundle;
23 | import android.os.Environment;
24 | import android.os.Handler;
25 | import android.os.Message;
26 | import android.support.annotation.Nullable;
27 | import android.support.v7.widget.LinearLayoutManager;
28 | import android.support.v7.widget.RecyclerView;
29 | import android.util.Log;
30 | import android.view.LayoutInflater;
31 | import android.view.View;
32 | import android.view.ViewGroup;
33 | import android.widget.ImageButton;
34 | import android.widget.ProgressBar;
35 | import android.widget.TextView;
36 | import android.widget.Toast;
37 |
38 | import com.google.gson.Gson;
39 | import com.google.gson.reflect.TypeToken;
40 | import com.tml.sharethem.R;
41 | import com.tml.sharethem.utils.DividerItemDecoration;
42 | import com.tml.sharethem.utils.RecyclerViewArrayAdapter;
43 |
44 | import java.io.BufferedReader;
45 | import java.io.IOException;
46 | import java.io.InputStream;
47 | import java.io.InputStreamReader;
48 | import java.io.Reader;
49 | import java.io.StringWriter;
50 | import java.io.Writer;
51 | import java.lang.ref.WeakReference;
52 | import java.lang.reflect.Type;
53 | import java.net.HttpURLConnection;
54 | import java.net.URL;
55 | import java.util.ArrayList;
56 | import java.util.List;
57 |
58 | import static android.content.Context.DOWNLOAD_SERVICE;
59 |
60 | /**
61 | * Lists all files available to download by making network calls using {@link ContactSenderAPITask}
62 | *
63 | * Functionalities include:
64 | *
65 | * - Adds file downloads to {@link DownloadManager}'s Queue
66 | * * - Checks Sender API availability and throws error after certain retry limit
67 | *
68 | *
69 | * Created by Sri on 21/12/16.
70 | */
71 |
72 | public class FilesListingFragment extends android.support.v4.app.Fragment {
73 | private static final String TAG = "FilesListingFragment";
74 |
75 | public static final String PATH_FILES = "http://%s:%s/files";
76 | public static final String PATH_STATUS = "http://%s:%s/status";
77 | public static final String PATH_FILE_DOWNLOAD = "http://%s:%s/file/%s";
78 |
79 | private String mSenderIp = null, mSenderSSID;
80 | private ContactSenderAPITask mUrlsTask;
81 | private ContactSenderAPITask mStatusCheckTask;
82 |
83 | private String mPort, mSenderName;
84 |
85 | static final int CHECK_SENDER_STATUS = 100;
86 | static final int SENDER_DATA_FETCH = 101;
87 |
88 | RecyclerView mFilesListing;
89 | ProgressBar mLoading;
90 | TextView mEmptyListText;
91 |
92 | private SenderFilesListingAdapter mFilesAdapter;
93 |
94 | private UiUpdateHandler uiUpdateHandler;
95 |
96 | private static final int SENDER_DATA_FETCH_RETRY_LIMIT = 3;
97 | private int senderDownloadsFetchRetry = SENDER_DATA_FETCH_RETRY_LIMIT, senderStatusCheckRetryLimit = SENDER_DATA_FETCH_RETRY_LIMIT;
98 |
99 | @Nullable
100 | @Override
101 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
102 | View v = inflater.inflate(R.layout.fragment_files_listing, null);
103 | mFilesListing = (RecyclerView) v.findViewById(R.id.files_list);
104 | mLoading = (ProgressBar) v.findViewById(R.id.loading);
105 | mEmptyListText = (TextView) v.findViewById(R.id.empty_listing_text);
106 | mEmptyListText.setOnClickListener(new View.OnClickListener() {
107 | @Override
108 | public void onClick(View view) {
109 | mEmptyListText.setVisibility(View.GONE);
110 | mLoading.setVisibility(View.VISIBLE);
111 | fetchSenderFiles();
112 | }
113 | });
114 | mFilesListing.setLayoutManager(new LinearLayoutManager(getActivity()));
115 | mFilesListing.addItemDecoration(new DividerItemDecoration(getResources().getDrawable(R.drawable.list_divider)));
116 | mFilesAdapter = new SenderFilesListingAdapter(new ArrayList());
117 | mFilesListing.setAdapter(mFilesAdapter);
118 | uiUpdateHandler = new UiUpdateHandler(this);
119 | return v;
120 | }
121 |
122 | public static FilesListingFragment getInstance(String senderIp, String ssid, String senderName, String port) {
123 | FilesListingFragment fragment = new FilesListingFragment();
124 | Bundle data = new Bundle();
125 | data.putString("senderIp", senderIp);
126 | data.putString("ssid", ssid);
127 | data.putString("name", senderName);
128 | data.putString("port", port);
129 | fragment.setArguments(data);
130 | return fragment;
131 | }
132 |
133 | @Override
134 | public void onAttach(Context activity) {
135 | super.onAttach(activity);
136 | if (null != getArguments()) {
137 | mSenderIp = getArguments().getString("senderIp");
138 | mSenderSSID = getArguments().getString("ssid");
139 | mPort = getArguments().getString("port");
140 | mSenderName = getArguments().getString("name");
141 | Log.d(TAG, "sender ip: " + mSenderIp);
142 | }
143 | }
144 |
145 | @Override
146 | public void onResume() {
147 | super.onResume();
148 | fetchSenderFiles();
149 | checkSenderAPIAvailablity();
150 | }
151 |
152 | private void fetchSenderFiles() {
153 | mLoading.setVisibility(View.VISIBLE);
154 | if (null != mUrlsTask)
155 | mUrlsTask.cancel(true);
156 | mUrlsTask = new ContactSenderAPITask(SENDER_DATA_FETCH);
157 | mUrlsTask.execute(String.format(PATH_FILES, mSenderIp, mPort));
158 | }
159 |
160 | private void checkSenderAPIAvailablity() {
161 | if (null != mStatusCheckTask)
162 | mStatusCheckTask.cancel(true);
163 | mStatusCheckTask = new ContactSenderAPITask(CHECK_SENDER_STATUS);
164 | mStatusCheckTask.execute(String.format(PATH_STATUS, mSenderIp, mPort));
165 | }
166 |
167 | @Override
168 | public void onPause() {
169 | super.onPause();
170 | if (null != mUrlsTask)
171 | mUrlsTask.cancel(true);
172 | }
173 |
174 | @Override
175 | public void onDestroy() {
176 | super.onDestroy();
177 | if (null != uiUpdateHandler)
178 | uiUpdateHandler.removeCallbacksAndMessages(null);
179 | if (null != mStatusCheckTask)
180 | mStatusCheckTask.cancel(true);
181 | }
182 |
183 | public String getSenderSSID() {
184 | return mSenderSSID;
185 | }
186 |
187 | public String getSenderIp() {
188 | return mSenderIp;
189 | }
190 |
191 | private void loadListing(String contentAsString) {
192 | Type collectionType = new TypeToken>() {
193 | }.getType();
194 | ArrayList files = new Gson().fromJson(contentAsString, collectionType);
195 | mLoading.setVisibility(View.GONE);
196 | if (null == files || files.size() == 0) {
197 | mEmptyListText.setText("No Downloads found.\n Tap to Retry");
198 | mEmptyListText.setVisibility(View.VISIBLE);
199 | } else {
200 | mEmptyListText.setVisibility(View.GONE);
201 | mFilesAdapter.updateData(files);
202 | }
203 | }
204 |
205 | private void onDataFetchError() {
206 | mLoading.setVisibility(View.GONE);
207 | mEmptyListText.setVisibility(View.VISIBLE);
208 | mEmptyListText.setText("Error occurred while fetching data.\n Tap to Retry");
209 | }
210 |
211 | private long postDownloadRequestToDM(Uri uri, String fileName) {
212 |
213 | // Create request for android download manager
214 | DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE);
215 | DownloadManager.Request request = new DownloadManager.Request(uri);
216 |
217 | //Setting title of request
218 | request.setTitle(fileName);
219 |
220 | //Setting description of request
221 | request.setDescription("ShareThem");
222 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
223 |
224 | //Set the local destination for the downloaded file to a path
225 | //within the application's external files directory
226 | request.setDestinationInExternalFilesDir(getActivity(),
227 | Environment.DIRECTORY_DOWNLOADS, fileName);
228 |
229 | //Enqueue download and save into referenceId
230 | return downloadManager.enqueue(request);
231 | }
232 |
233 | private class SenderFilesListingAdapter extends RecyclerViewArrayAdapter {
234 | SenderFilesListingAdapter(List objects) {
235 | super(objects);
236 | }
237 |
238 | void updateData(List objects) {
239 | clear();
240 | mObjects = objects;
241 | notifyDataSetChanged();
242 | }
243 |
244 | @Override
245 | public SenderFilesListItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
246 | View itemView = LayoutInflater.from(parent.getContext()).
247 | inflate(R.layout.listitem_file, parent, false);
248 | return new SenderFilesListItemHolder(itemView);
249 | }
250 |
251 | @Override
252 | public void onBindViewHolder(SenderFilesListItemHolder holder, int position) {
253 | final String senderFile = mObjects.get(position);
254 | holder.itemView.setTag(senderFile);
255 | final String fileName = senderFile.substring(senderFile.lastIndexOf('/') + 1, senderFile.length());
256 | holder.title.setText(fileName);
257 | holder.download.setOnClickListener(new View.OnClickListener() {
258 | @Override
259 | public void onClick(View view) {
260 | postDownloadRequestToDM(Uri.parse(String.format(PATH_FILE_DOWNLOAD, mSenderIp, mPort, mObjects.indexOf(senderFile))), fileName);
261 | Toast.makeText(getActivity(), "Downloading " + fileName + "...", Toast.LENGTH_SHORT).show();
262 | }
263 | });
264 | }
265 | }
266 |
267 | static class SenderFilesListItemHolder extends RecyclerView.ViewHolder {
268 | TextView title;
269 | ImageButton download;
270 |
271 | SenderFilesListItemHolder(View itemView) {
272 | super(itemView);
273 | title = (TextView) itemView.findViewById(R.id.sender_list_item_name);
274 | download = (ImageButton) itemView.findViewById(R.id.sender_list_start_download);
275 | }
276 | }
277 |
278 |
279 | /**
280 | * Performs network calls to fetch data/status from Sender.
281 | * Retries on error for times bases on values of {@link FilesListingFragment#senderDownloadsFetchRetry}
282 | */
283 | private class ContactSenderAPITask extends AsyncTask {
284 |
285 | int mode;
286 | boolean error;
287 |
288 | ContactSenderAPITask(int mode) {
289 | this.mode = mode;
290 | }
291 |
292 | @Override
293 | protected String doInBackground(String... urls) {
294 | error = false;
295 | try {
296 | return downloadDataFromSender(urls[0]);
297 | } catch (IOException e) {
298 | e.printStackTrace();
299 | error = true;
300 | Log.e(TAG, "Exception: " + e.getMessage());
301 | return null;
302 | }
303 | }
304 |
305 | // onPostExecute displays the results of the AsyncTask.
306 | @Override
307 | protected void onPostExecute(String result) {
308 | switch (mode) {
309 | case SENDER_DATA_FETCH:
310 | if (error) {
311 | if (senderDownloadsFetchRetry >= 0) {
312 | --senderDownloadsFetchRetry;
313 | if (getView() == null || getActivity() == null || null == uiUpdateHandler)
314 | return;
315 | uiUpdateHandler.removeMessages(SENDER_DATA_FETCH);
316 | uiUpdateHandler.sendMessageDelayed(uiUpdateHandler.obtainMessage(mode), 800);
317 | return;
318 | } else senderDownloadsFetchRetry = SENDER_DATA_FETCH_RETRY_LIMIT;
319 | if (null != getView())
320 | onDataFetchError();
321 | } else if (null != getView())
322 | loadListing(result);
323 | else Log.e(TAG, "fragment may have been removed, File fetch");
324 | break;
325 | case CHECK_SENDER_STATUS:
326 | if (error) {
327 | if (senderStatusCheckRetryLimit > 1) {
328 | --senderStatusCheckRetryLimit;
329 | uiUpdateHandler.removeMessages(CHECK_SENDER_STATUS);
330 | uiUpdateHandler.sendMessageDelayed(uiUpdateHandler.obtainMessage(CHECK_SENDER_STATUS), 800);
331 | } else if (getActivity() instanceof ReceiverActivity) {
332 | senderStatusCheckRetryLimit = SENDER_DATA_FETCH_RETRY_LIMIT;
333 | ((ReceiverActivity) getActivity()).resetSenderSearch();
334 | Toast.makeText(getActivity(), getString(R.string.p2p_receiver_error_sender_disconnected), Toast.LENGTH_SHORT).show();
335 | } else
336 | Log.e(TAG, "Activity is not instance of ReceiverActivity");
337 | } else if (null != getView()) {
338 | senderStatusCheckRetryLimit = SENDER_DATA_FETCH_RETRY_LIMIT;
339 | uiUpdateHandler.removeMessages(CHECK_SENDER_STATUS);
340 | uiUpdateHandler.sendMessageDelayed(uiUpdateHandler.obtainMessage(CHECK_SENDER_STATUS), 1000);
341 | } else
342 | Log.e(TAG, "fragment may have been removed: Sender api check");
343 | break;
344 | }
345 |
346 | }
347 |
348 | private String downloadDataFromSender(String apiUrl) throws IOException {
349 | InputStream is = null;
350 | try {
351 | URL url = new URL(apiUrl);
352 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
353 | conn.setReadTimeout(10000 /* milliseconds */);
354 | conn.setConnectTimeout(15000 /* milliseconds */);
355 | conn.setRequestMethod("GET");
356 | conn.setDoInput(true);
357 | // Starts the query
358 | conn.connect();
359 | // int response =
360 | conn.getResponseCode();
361 | // Log.d(TAG, "The response is: " + response);
362 | is = conn.getInputStream();
363 | // Convert the InputStream into a string
364 | return readIt(is);
365 | } finally {
366 | if (is != null) {
367 | is.close();
368 | }
369 | }
370 | }
371 |
372 | private String readIt(InputStream stream) throws IOException {
373 | Writer writer = new StringWriter();
374 |
375 | char[] buffer = new char[1024];
376 | try {
377 | Reader reader = new BufferedReader(
378 | new InputStreamReader(stream, "UTF-8"));
379 | int n;
380 | while ((n = reader.read(buffer)) != -1) {
381 | writer.write(buffer, 0, n);
382 | }
383 | } finally {
384 | stream.close();
385 | }
386 | return writer.toString();
387 | }
388 | }
389 |
390 | private static class UiUpdateHandler extends Handler {
391 | WeakReference mFragment;
392 |
393 | UiUpdateHandler(FilesListingFragment fragment) {
394 | mFragment = new WeakReference<>(fragment);
395 | }
396 |
397 | @Override
398 | public void handleMessage(Message msg) {
399 | FilesListingFragment fragment = mFragment.get();
400 | if (null == mFragment)
401 | return;
402 | switch (msg.what) {
403 | case CHECK_SENDER_STATUS:
404 | fragment.checkSenderAPIAvailablity();
405 | break;
406 | case SENDER_DATA_FETCH:
407 | fragment.fetchSenderFiles();
408 | break;
409 | }
410 | }
411 | }
412 |
413 | }
414 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/sender/SHAREthemService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Srihari Yachamaneni
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 | package com.tml.sharethem.sender;
17 |
18 | import android.app.Notification;
19 | import android.app.NotificationManager;
20 | import android.app.PendingIntent;
21 | import android.app.Service;
22 | import android.content.BroadcastReceiver;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.IntentFilter;
26 | import android.net.wifi.WifiManager;
27 | import android.os.Handler;
28 | import android.os.IBinder;
29 | import android.os.Message;
30 | import android.provider.Settings;
31 | import android.support.v4.app.NotificationCompat;
32 | import android.text.TextUtils;
33 | import android.util.Base64;
34 | import android.util.Log;
35 |
36 | import com.tml.sharethem.R;
37 | import com.tml.sharethem.utils.HotspotControl;
38 | import com.tml.sharethem.utils.WifiUtils;
39 |
40 | import java.lang.ref.WeakReference;
41 |
42 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.SHARE_CLIENT_IP;
43 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.SHARE_SERVER_UPDATES_INTENT_ACTION;
44 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.SHARE_SERVER_UPDATE_FILE_NAME;
45 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.SHARE_SERVER_UPDATE_TEXT;
46 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.SHARE_TRANSFER_PROGRESS;
47 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.TYPE;
48 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.Types.AP_DISABLED_ACKNOWLEDGEMENT;
49 | import static com.tml.sharethem.sender.SHAREthemService.ShareIntents.Types.FILE_TRANSFER_STATUS;
50 | import static com.tml.sharethem.utils.Utils.isOreoOrAbove;
51 |
52 |
53 | /**
54 | * Manages Hotspot configuration using an instance of {@link HotspotControl} and also deals lifecycle of {@link SHAREthemServer}
55 | * Created by Sri on 18/12/16.
56 | */
57 | public class SHAREthemService extends Service {
58 |
59 | private static final String TAG = "ShareService";
60 |
61 | private WifiManager wifiManager;
62 | private HotspotControl hotspotControl;
63 | private SHAREthemServer m_fileServer;
64 | private BroadcastReceiver m_notificationStopActionReceiver;
65 | private HotspotChecker hotspotCheckHandler;
66 |
67 | private static final int SHARE_SERVICE_NOTIFICATION_ID = 100001;
68 | static final String WIFI_AP_ACTION_START = "wifi_ap_start";
69 | static final String WIFI_AP_ACTION_STOP = "wifi_ap_stop";
70 | static final String WIFI_AP_ACTION_START_CHECK = "wifi_ap_check";
71 |
72 | public static final String EXTRA_FILE_PATHS = "file_paths";
73 | public static final String EXTRA_PORT = "server_port";
74 | public static final String EXTRA_SENDER_NAME = "sender_name";
75 |
76 | private static final int AP_ALIVE_CHECK = 100;
77 | private static final int AP_START_CHECK = 101;
78 |
79 | static class ShareIntents {
80 | static final String TYPE = "type";
81 | static final String SHARE_SERVER_UPDATES_INTENT_ACTION = "share_server_updates_intent_action";
82 | static final String SHARE_SERVER_UPDATE_TEXT = "share_server_update_text";
83 | static final String SHARE_SERVER_UPDATE_FILE_NAME = "share_server_file_name";
84 | static final String SHARE_CLIENT_IP = "share_client_ip";
85 | static final String SHARE_TRANSFER_PROGRESS = "share_transfer_progress";
86 |
87 | class Types {
88 | static final int FILE_TRANSFER_STATUS = 1000;
89 | static final int AP_ENABLED_ACKNOWLEDGEMENT = 1001;
90 | static final int AP_DISABLED_ACKNOWLEDGEMENT = 1002;
91 | }
92 | }
93 |
94 | public SHAREthemService() {
95 | }
96 |
97 | @Override
98 | public void onCreate() {
99 | super.onCreate();
100 | wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
101 | hotspotControl = HotspotControl.getInstance(getApplicationContext());
102 | m_notificationStopActionReceiver = new BroadcastReceiver() {
103 | @Override
104 | public void onReceive(Context context, Intent intent) {
105 | if (null != intent && WIFI_AP_ACTION_STOP.equals(intent.getAction()))
106 | disableHotspotAndStop();
107 | }
108 | };
109 | registerReceiver(m_notificationStopActionReceiver, new IntentFilter(WIFI_AP_ACTION_STOP));
110 | //Start a foreground with message saying 'Initiating Hotspot'. Message is later updated using SHARE_SERVICE_NOTIFICATION_ID
111 | startForeground(SHARE_SERVICE_NOTIFICATION_ID, getShareThemNotification(getString(R.string.p2p_sender_service_init_notification_header), false));
112 | hotspotCheckHandler = new HotspotChecker(this);
113 | }
114 |
115 | protected android.support.v7.app.NotificationCompat.Action getStopAction() {
116 | Intent intent = new Intent(WIFI_AP_ACTION_STOP);
117 | PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
118 | return new android.support.v7.app.NotificationCompat.Action.Builder(android.R.drawable.ic_menu_close_clear_cancel, "Stop", pendingIntent).build();
119 | }
120 |
121 | @Override
122 | public int onStartCommand(Intent intent, int flags, int startId) {
123 | if (null != intent) {
124 | String action = intent.getAction();
125 | switch (action) {
126 | case WIFI_AP_ACTION_START:
127 | if (!hotspotControl.isEnabled()) {
128 | startFileHostingServer(intent.getStringArrayExtra(EXTRA_FILE_PATHS), intent.getIntExtra(EXTRA_PORT, 0), intent.getStringExtra(EXTRA_SENDER_NAME));
129 | }
130 | // sendAcknowledgementBroadcast(AP_ENABLED_ACKNOWLEDGEMENT);
131 | break;
132 | case WIFI_AP_ACTION_STOP:
133 | disableHotspotAndStop();
134 | break;
135 | case WIFI_AP_ACTION_START_CHECK:
136 | if (null != hotspotControl && hotspotControl.isEnabled()) {
137 | //starts a handler in loop to check Hotspot check. Service kills itself when Hotspot is no more alive
138 | if (null == hotspotCheckHandler)
139 | hotspotCheckHandler = new HotspotChecker(this);
140 | else hotspotCheckHandler.removeMessages(AP_ALIVE_CHECK);
141 | hotspotCheckHandler.sendEmptyMessageDelayed(100, 3000);
142 | }
143 | break;
144 | }
145 | }
146 | return START_NOT_STICKY;
147 | }
148 |
149 | private void disableHotspotAndStop() {
150 | Log.d(TAG, "p2p service stop action received..");
151 | if (null != hotspotControl)
152 | hotspotControl.disable();
153 | wifiManager.setWifiEnabled(true);
154 | if (null != hotspotCheckHandler)
155 | hotspotCheckHandler.removeCallbacksAndMessages(null);
156 | stopFileTasks();
157 | stopForeground(true);
158 | sendAcknowledgementBroadcast(AP_DISABLED_ACKNOWLEDGEMENT);
159 | stopSelf();
160 | }
161 |
162 | /**
163 | * Creates a Notification for {@link Service#startForeground(int, Notification)} method.
164 | * Adds text passed in text param as content and title
165 | *
166 | * @param text message and title
167 | * @param addStopAction if true STOP action is added to stop {@link SHAREthemService}
168 | * @return Notification
169 | */
170 | private Notification getShareThemNotification(String text, boolean addStopAction) {
171 | Intent notificationIntent = new Intent(getApplicationContext(),
172 | SHAREthemActivity.class);
173 | notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
174 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
175 | notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
176 | NotificationCompat.Builder builder = new NotificationCompat.Builder(
177 | this);
178 | builder.setContentIntent(pendingIntent)
179 | .setSmallIcon(R.mipmap.ic_launcher).setWhen(System.currentTimeMillis())
180 | .setContentTitle(getString(R.string.app_name))
181 | .setTicker(text)
182 | .setContentText(text);
183 | if (addStopAction)
184 | builder.addAction(getStopAction());
185 | return builder.build();
186 | }
187 |
188 | /**
189 | * Starts {@link SHAREthemServer} and then enables Hotspot with assigned port being part of encoded SSID.
190 | *
191 | * @param filePathsTobeServed
192 | */
193 | private void startFileHostingServer(String[] filePathsTobeServed, int port, final String sender_name) {
194 | m_fileServer = new SHAREthemServer(getApplicationContext()
195 | , new FileTransferStatusListener() {
196 | @Override
197 | public synchronized void onBytesTransferProgress(final String ip, final String fileName, long totalSize, final String speed, long currentSize, final int percentage) {
198 | sendTransferStatusBroadcast(ip, percentage, "Transferring " + fileName + " file(" + percentage + "%)\nSpeed: " + speed, fileName);
199 | }
200 |
201 | @Override
202 | public void onBytesTransferCompleted(final String ip, final String fileName) {
203 | sendTransferStatusBroadcast(ip, 100, fileName + " file transfer completed", fileName);
204 | }
205 |
206 | @Override
207 | public synchronized void onBytesTransferStarted(final String ip, final String fileName) {
208 | sendTransferStatusBroadcast(ip, 0, "Transferring " + fileName + " file", fileName);
209 | }
210 |
211 | @Override
212 | public void onBytesTransferCancelled(final String ip, final String error, String fileName) {
213 | Log.e(TAG, " transfer cancelled for ip: " + ip + ", file name: " + fileName);
214 | sendTransferStatusBroadcast(ip, 0, "Error in file transfer: " + error, fileName);
215 | }
216 | }, filePathsTobeServed, port);
217 | // new Thread(new Runnable() {
218 | // @Override
219 | // public void run() {
220 | // try {
221 | // m_fileServer.start();
222 | //
223 | // } catch (BindException e) {
224 | // e.printStackTrace();
225 | // Log.e(TAG, "exception in starting file server: " + e.getMessage());
226 | // } catch (Exception e) {
227 | // Log.e(TAG, "exception in starting file server: " + e.getMessage());
228 | // e.printStackTrace();
229 | // }
230 | // }
231 | // }).start();
232 | try {
233 | m_fileServer.start();
234 | Log.d(TAG, "**** Server started success****port: " + m_fileServer.getListeningPort() + ", " + m_fileServer.getHostAddress());
235 | if (isOreoOrAbove()) {
236 | hotspotControl.turnOnOreoHotspot(m_fileServer.getListeningPort());
237 | } else {
238 | /*
239 | * We need create a Open Hotspot with an SSID which can be intercepted by Receiver.
240 | * Here is the combination logic followed to create SSID for open Hotspot and same is followed by Receiver while decoding SSID, Sender HostName & port to connect
241 | * Reason for doing this is to keep SSID unique, constant(unless port is assigned by system) and interpretable by Receiver
242 | * {last 4 digits of android id} + {-} + Base64 of [{sender name} + {|} + SENDER_WIFI_NAMING_SALT + {|} + {port}]
243 | */
244 | String androidId = Settings.Secure.ANDROID_ID;
245 | androidId = androidId.replaceAll("[^A-Za-z0-9]", "");
246 | String name = (androidId.length() > 4 ? androidId.substring(androidId.length() - 4) : androidId) + "-" + Base64.encodeToString((TextUtils.isEmpty(sender_name) ? generateP2PSpuulName() : sender_name + "|" + WifiUtils.SENDER_WIFI_NAMING_SALT + "|" + m_fileServer.getListeningPort()).getBytes(), Base64.DEFAULT);
247 |
248 | hotspotControl.turnOnPreOreoHotspot(name, m_fileServer.getListeningPort());
249 | hotspotCheckHandler.sendEmptyMessage(AP_START_CHECK);
250 | }
251 | } catch (Exception e) {
252 | Log.e(TAG, "exception in hotspot init: " + e.getMessage());
253 | e.printStackTrace();
254 | }
255 | }
256 |
257 | public static String DEFAULT_SENDER_NAME = "Sender.";
258 |
259 | public static String generateP2PSpuulName() {
260 | String androidId = Settings.Secure.ANDROID_ID;
261 | if (TextUtils.isEmpty(androidId))
262 | androidId = "";
263 | else
264 | androidId = androidId.length() <= 3 ? androidId : androidId.substring(androidId.length() - 3, androidId.length());
265 | return DEFAULT_SENDER_NAME + androidId;
266 | }
267 |
268 | private void stopFileTasks() {
269 | try {
270 | if (null != m_fileServer && m_fileServer.isAlive()) {
271 | Log.d(TAG, "stopping server..");
272 | m_fileServer.stop();
273 | }
274 | } catch (Exception e) {
275 | e.printStackTrace();
276 | Log.e(TAG, "exception in stopping file server: " + e.getMessage());
277 | }
278 | }
279 |
280 | private void sendAcknowledgementBroadcast(int type) {
281 | Intent updateIntent = new Intent(SHARE_SERVER_UPDATES_INTENT_ACTION);
282 | updateIntent.putExtra(TYPE, type);
283 | sendBroadcast(updateIntent);
284 | }
285 |
286 | private void sendTransferStatusBroadcast(String ip, int progress, String updateText, String fileName) {
287 | Intent updateIntent = new Intent(SHARE_SERVER_UPDATES_INTENT_ACTION);
288 | updateIntent.putExtra(TYPE, FILE_TRANSFER_STATUS);
289 | updateIntent.putExtra(SHARE_SERVER_UPDATE_TEXT, updateText);
290 | updateIntent.putExtra(SHARE_SERVER_UPDATE_FILE_NAME, fileName);
291 | updateIntent.putExtra(SHARE_CLIENT_IP, ip);
292 | updateIntent.putExtra(SHARE_TRANSFER_PROGRESS, progress);
293 | sendBroadcast(updateIntent);
294 | }
295 |
296 | @Override
297 | public void onDestroy() {
298 | super.onDestroy();
299 | if (null != hotspotControl && hotspotControl.isEnabled()) {
300 | hotspotControl.disable();
301 | wifiManager.setWifiEnabled(true);
302 | stopFileTasks();
303 | }
304 | if (null != hotspotCheckHandler)
305 | hotspotCheckHandler.removeCallbacksAndMessages(null);
306 | if (null != m_notificationStopActionReceiver)
307 | unregisterReceiver(m_notificationStopActionReceiver);
308 | }
309 |
310 | @Override
311 | public IBinder onBind(Intent intent) {
312 | return null;
313 | }
314 |
315 | /**
316 | * Updates notification text saying 'Hotspot Enabled' along with STOP action.
317 | * Since there isn't an easy way to update Foreground notification, notifying a new notification with same ID as earlier would do the trick.
318 | *
Ref: http://stackoverflow.com/a/20142620/862882
319 | */
320 | private void updateForegroundNotification() {
321 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
322 | mNotificationManager.notify(SHARE_SERVICE_NOTIFICATION_ID, getShareThemNotification(getString(R.string.p2p_sender_service_notification_header), true));
323 | }
324 |
325 | private static class HotspotChecker extends Handler {
326 | WeakReference service;
327 |
328 | HotspotChecker(SHAREthemService senderService) {
329 | this.service = new WeakReference<>(senderService);
330 | }
331 |
332 | @Override
333 | public void handleMessage(Message msg) {
334 | SHAREthemService senderService = service.get();
335 | if (null == senderService)
336 | return;
337 | if (msg.what == AP_ALIVE_CHECK) {
338 | if (null == senderService.hotspotControl || !senderService.hotspotControl.isEnabled()) {
339 | Log.e(TAG, "hotspot isnt active, close this service");
340 | senderService.disableHotspotAndStop();
341 | } else sendEmptyMessageDelayed(AP_ALIVE_CHECK, 3000);
342 | } else if (msg.what == AP_START_CHECK && null != senderService.hotspotControl) {
343 | if (senderService.hotspotControl.isEnabled())
344 | senderService.updateForegroundNotification();
345 | else {
346 | removeMessages(AP_START_CHECK);
347 | sendEmptyMessageDelayed(AP_START_CHECK, 800);
348 | }
349 | }
350 | }
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/utils/WifiUtils.java:
--------------------------------------------------------------------------------
1 | package com.tml.sharethem.utils;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.DhcpInfo;
6 | import android.net.Network;
7 | import android.net.NetworkInfo;
8 | import android.net.wifi.ScanResult;
9 | import android.net.wifi.WifiConfiguration;
10 | import android.net.wifi.WifiInfo;
11 | import android.net.wifi.WifiManager;
12 | import android.os.Build;
13 | import android.util.Base64;
14 | import android.util.Log;
15 |
16 | import java.io.IOException;
17 | import java.net.Inet4Address;
18 | import java.net.InetAddress;
19 | import java.net.NetworkInterface;
20 | import java.net.UnknownHostException;
21 | import java.util.Arrays;
22 | import java.util.Collections;
23 | import java.util.Comparator;
24 | import java.util.Enumeration;
25 | import java.util.Iterator;
26 | import java.util.List;
27 |
28 | import static com.tml.sharethem.utils.Utils.DEFAULT_PORT_OREO;
29 | import static com.tml.sharethem.utils.Utils.isOreoOrAbove;
30 |
31 |
32 | /**
33 | * Created by Sri on 18/12/16.
34 | */
35 |
36 | public class WifiUtils {
37 |
38 | public static final String SENDER_WIFI_NAMING_SALT = "stl";
39 | private static final String TAG = "WifiUtils";
40 |
41 | public static boolean isConnectToHotSpotRunning = false;
42 |
43 | /*
44 | * Max priority of network to be associated.
45 | */
46 | private static final int MAX_PRIORITY = 999999;
47 |
48 | /**
49 | * Method for Connecting to WiFi Network (hotspot)
50 | *
51 | * @param netSSID of WiFi Network (hotspot)
52 | * @param disableOthers
53 | */
54 | public static boolean connectToOpenWifi(WifiManager mWifiManager, String netSSID, boolean disableOthers) {
55 |
56 | isConnectToHotSpotRunning = true;
57 | WifiConfiguration wifiConf = new WifiConfiguration();
58 | if (mWifiManager.isWifiEnabled()) {
59 | wifiConf.SSID = "\"" + netSSID + "\"";
60 | wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
61 | int res = mWifiManager.addNetwork(wifiConf);
62 | Log.d(TAG, "added network id: " + res);
63 | mWifiManager.disconnect();
64 | if (disableOthers) {
65 | enableNetworkAndDisableOthers(mWifiManager, netSSID);
66 | } else {
67 | enableShareThemNetwork(mWifiManager, netSSID);
68 | }
69 | isConnectToHotSpotRunning = false;
70 | return mWifiManager.setWifiEnabled(true);
71 | }
72 | isConnectToHotSpotRunning = false;
73 | return false;
74 | }
75 |
76 | public static boolean enableNetworkAndDisableOthers(WifiManager wifiManager, String ssid) {
77 | boolean state = false;
78 | List networks = wifiManager.getConfiguredNetworks();
79 | Iterator iterator = networks.iterator();
80 | while (iterator.hasNext()) {
81 | WifiConfiguration wifiConfig = iterator.next();
82 | if (wifiConfig.SSID.equals("\"" + ssid + "\""))
83 | state = wifiManager.enableNetwork(wifiConfig.networkId, true);
84 | else
85 | wifiManager.disableNetwork(wifiConfig.networkId);
86 | }
87 | Log.d(TAG, "turnOnPreOreoHotspot wifi result: " + state);
88 | wifiManager.reconnect();
89 | return state;
90 | }
91 |
92 | public static boolean removeSTWifiAndEnableOthers(WifiManager wifiManager, String ssid) {
93 | boolean state = false;
94 | List networks = wifiManager.getConfiguredNetworks();
95 | Iterator iterator = networks.iterator();
96 | while (iterator.hasNext()) {
97 | WifiConfiguration wifiConfig = iterator.next();
98 | if (wifiConfig.SSID.equals("\"" + ssid + "\"")) {
99 | wifiManager.removeNetwork(wifiConfig.networkId);
100 | wifiManager.disableNetwork(wifiConfig.networkId);
101 | } else
102 | // if targetSdkVersion > 20
103 | // If an application's target SDK version is LOLLIPOP or newer, network communication may not use Wi-Fi even if Wi-Fi is connected;
104 | // For more info: https://developer.android.com/reference/android/net/wifi/WifiManager.html#enableNetwork(int, boolean)
105 | state = wifiManager.enableNetwork(wifiConfig.networkId, true);
106 | }
107 | wifiManager.saveConfiguration();
108 | wifiManager.reconnect();
109 | return state;
110 | }
111 |
112 | public static boolean enableShareThemNetwork(WifiManager wifiManager, String ssid) {
113 | boolean state = false;
114 | List list = wifiManager.getConfiguredNetworks();
115 |
116 | if (list != null && list.size() > 0) {
117 | for (WifiConfiguration i : list) {
118 | if (i.SSID != null && i.SSID.equals("\"" + ssid + "\"")) {
119 | int newPri = getMaxPriority(wifiManager) + 1;
120 | if (newPri >= MAX_PRIORITY) {
121 | // We have reached a rare situation.
122 | newPri = shiftPriorityAndSave(wifiManager);
123 | }
124 | i.priority = newPri;
125 | wifiManager.updateNetwork(i);
126 | wifiManager.saveConfiguration();
127 | // if targetSdkVersion > 20
128 | // If an application's target SDK version is LOLLIPOP or newer, network communication may not use Wi-Fi even if Wi-Fi is connected;
129 | // For more info: https://developer.android.com/reference/android/net/wifi/WifiManager.html#enableNetwork(int, boolean)
130 | state = wifiManager.enableNetwork(i.networkId, true);
131 | wifiManager.reconnect();
132 | break;
133 | }
134 | }
135 | }
136 |
137 | return state;
138 | }
139 |
140 | /**
141 | * removes Configured wifi Network By SSID
142 | *
143 | * @param ssid of wifi Network
144 | */
145 | public static void removeWifiNetwork(WifiManager wifiManager, String ssid) {
146 | List configs = wifiManager.getConfiguredNetworks();
147 | if (configs != null) {
148 | for (WifiConfiguration config : configs) {
149 | if (config.SSID.contains(ssid)) {
150 | wifiManager.disableNetwork(config.networkId);
151 | wifiManager.removeNetwork(config.networkId);
152 | Log.d(TAG, "removed wifi network with ssid: " + ssid);
153 | }
154 | }
155 | }
156 | wifiManager.saveConfiguration();
157 | }
158 |
159 | private static int getMaxPriority(WifiManager wifiManager) {
160 | final List configurations = wifiManager.getConfiguredNetworks();
161 | int pri = 0;
162 | for (final WifiConfiguration config : configurations) {
163 | if (config.priority > pri) {
164 | pri = config.priority;
165 | }
166 | }
167 | return pri;
168 | }
169 |
170 | private static void sortByPriority(final List configurations) {
171 | Collections.sort(configurations,
172 | new Comparator() {
173 | @Override
174 | public int compare(WifiConfiguration object1, WifiConfiguration object2) {
175 | return object1.priority - object2.priority;
176 | }
177 | });
178 | }
179 |
180 | private static int shiftPriorityAndSave(WifiManager wifiManager) {
181 | final List configurations = wifiManager.getConfiguredNetworks();
182 | sortByPriority(configurations);
183 | final int size = configurations.size();
184 | for (int i = 0; i < size; i++) {
185 | final WifiConfiguration config = configurations.get(i);
186 | config.priority = i;
187 | wifiManager.updateNetwork(config);
188 | }
189 | wifiManager.saveConfiguration();
190 | return size;
191 | }
192 |
193 | /**
194 | * Method to Get Network Security Mode
195 | *
196 | * @param scanResult
197 | * @return OPEN PSK EAP OR WEP
198 | */
199 | public static String getSecurityMode(ScanResult scanResult) {
200 | final String cap = scanResult.capabilities;
201 | final String[] modes = {"WPA", "EAP", "WEP"};
202 | for (int i = modes.length - 1; i >= 0; i--) {
203 | if (cap.contains(modes[i])) {
204 | return modes[i];
205 | }
206 | }
207 | return "OPEN";
208 | }
209 |
210 | public static boolean isWifiConnectedToSTAccessPoint(Context context) {
211 | return isConnectedOnWifi(context, true) && isShareThemSSID(((WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getConnectionInfo().getSSID());
212 | }
213 |
214 | public static boolean isAndroidAP(String ssid) {
215 | Log.d(TAG, "is this ssid mathcing to ST hotspot: " + ssid);
216 | String[] splits = ssid.split("-");
217 | if (splits.length != 2)
218 | return false;
219 | String[] names = new String(Base64.decode(splits[1], Base64.DEFAULT)).split("\\|");
220 | return names.length == 3 && names[1].equals(SENDER_WIFI_NAMING_SALT);
221 | }
222 |
223 | public static boolean isShareThemSSID(String ssid) {
224 | if (isOreoOrAbove()) {
225 | return null != ssid && ssid.contains("AndroidShare");
226 | }
227 | Log.d(TAG, "is this ssid mathcing to ST hotspot: " + ssid);
228 | String[] splits = ssid.split("-");
229 | if (splits.length != 2)
230 | return false;
231 | try {
232 | String[] names = new String(Base64.decode(splits[1], Base64.DEFAULT)).split("\\|");
233 | return names.length == 3 && names[1].equals(SENDER_WIFI_NAMING_SALT);
234 | } catch(Exception e){
235 | return false;
236 | }
237 | }
238 |
239 | public static int getSenderSocketPortFromSSID(String ssid) {
240 | String[] splits = ssid.split("-");
241 | if (splits.length != 2)
242 | return -1;
243 | String[] names = new String(Base64.decode(splits[1], Base64.DEFAULT)).split("\\|");
244 | if (names.length == 3 && names[1].equals(SENDER_WIFI_NAMING_SALT))
245 | return Integer.parseInt(names[2]);
246 | return -1;
247 | }
248 |
249 | public static String[] getSenderInfoFromSSID(String ssid) {
250 | if (isOreoOrAbove()) {
251 | return new String[]{ssid, String.valueOf(DEFAULT_PORT_OREO)};
252 | }
253 | String[] splits = ssid.split("-");
254 | if (splits.length != 2)
255 | return null;
256 | String[] names = new String(Base64.decode(splits[1], Base64.DEFAULT)).split("\\|");
257 | if (names.length == 3 && names[1].equals(SENDER_WIFI_NAMING_SALT))
258 | return new String[]{names[0], names[2]};
259 | return null;
260 | }
261 |
262 | public static String getThisDeviceIp(Context context) {
263 | WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
264 | WifiInfo wifiInf = wifiMan.getConnectionInfo();
265 | int ipAddress = wifiInf.getIpAddress();
266 | return String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
267 |
268 | }
269 |
270 | /**
271 | * Check if there is any connectivity to a Wifi network
272 | *
273 | * @param context
274 | * @param includeConnectingStatus
275 | * @return
276 | */
277 | public static boolean isConnectedOnWifi(Context context, boolean includeConnectingStatus) {
278 | if (Build.VERSION.SDK_INT >= 21) {
279 | boolean isWifiConnected = false;
280 | ConnectivityManager connectivityManager = ((ConnectivityManager) context.getSystemService
281 | (Context.CONNECTIVITY_SERVICE));
282 | Network[] networks = connectivityManager.getAllNetworks();
283 | if (networks == null) {
284 | isWifiConnected = false;
285 | } else {
286 | for (Network network : networks) {
287 | NetworkInfo info = connectivityManager.getNetworkInfo(network);
288 | if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI && info.isAvailable() && (!includeConnectingStatus || info.isConnectedOrConnecting())) {
289 | isWifiConnected = true;
290 | break;
291 | }
292 | }
293 | }
294 | return isWifiConnected;
295 | } else {
296 | NetworkInfo info = getNetworkInfo(context);
297 | return (info != null && info.isConnectedOrConnecting() && info.getType() == ConnectivityManager.TYPE_WIFI);
298 | }
299 | }
300 |
301 | /**
302 | * Get the network info
303 | *
304 | * @param context
305 | * @return
306 | */
307 | public static NetworkInfo getNetworkInfo(Context context) {
308 | ConnectivityManager cm = (ConnectivityManager) context
309 | .getSystemService(Context.CONNECTIVITY_SERVICE);
310 | return cm.getActiveNetworkInfo();
311 | }
312 |
313 | public static String getAccessPointIpAddress(Context context) {
314 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
315 | DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
316 | byte[] ipAddress = convert2Bytes(dhcpInfo.serverAddress);
317 | try {
318 | String ip = InetAddress.getByAddress(ipAddress).getHostAddress();
319 | return ip.replace("/", "");
320 | } catch (UnknownHostException e) {
321 | e.printStackTrace();
322 | }
323 | return null;
324 | }
325 |
326 | private static byte[] convert2Bytes(int hostAddress) {
327 | byte[] addressBytes = {(byte) (0xff & hostAddress),
328 | (byte) (0xff & (hostAddress >> 8)),
329 | (byte) (0xff & (hostAddress >> 16)),
330 | (byte) (0xff & (hostAddress >> 24))};
331 | return addressBytes;
332 | }
333 |
334 | public static String getHostIpAddress() {
335 | try {
336 | for (final Enumeration enumerationNetworkInterface = NetworkInterface.getNetworkInterfaces(); enumerationNetworkInterface.hasMoreElements(); ) {
337 | final NetworkInterface networkInterface = enumerationNetworkInterface.nextElement();
338 | for (Enumeration enumerationInetAddress = networkInterface.getInetAddresses(); enumerationInetAddress.hasMoreElements(); ) {
339 | final InetAddress inetAddress = enumerationInetAddress.nextElement();
340 | final String ipAddress = inetAddress.getHostAddress();
341 | if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
342 | return ipAddress;
343 | }
344 | }
345 | }
346 | return null;
347 | } catch (final Exception e) {
348 | Log.e("WifiUtils", "exception in fetching inet address: " + e.getMessage());
349 | return null;
350 | }
351 | }
352 |
353 | public static int inetAddressToInt(InetAddress inetAddr)
354 | throws IllegalArgumentException {
355 | byte[] addr = inetAddr.getAddress();
356 | if (addr.length != 4) {
357 | throw new IllegalArgumentException("Not an IPv4 address");
358 | }
359 | return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
360 | ((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
361 | }
362 |
363 | public static boolean isOpenWifi(ScanResult result) {
364 | return !result.capabilities.contains("WEP") && !result.capabilities.contains("PSK") && !result.capabilities.contains("EAP");
365 | }
366 |
367 | static String getDeviceName(WifiManager wifiManager) {
368 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
369 | Log.d(TAG, "For version >= MM inaccessible mac - falling back to the default device name: " + HotspotControl.DEFAULT_DEVICE_NAME);
370 | return HotspotControl.DEFAULT_DEVICE_NAME;
371 | }
372 | String macString = wifiManager.getConnectionInfo().getMacAddress();
373 | if (macString == null) {
374 | Log.d(TAG, "MAC Address not found - Wi-Fi disabled? Falling back to the default device name: " + HotspotControl.DEFAULT_DEVICE_NAME);
375 | return HotspotControl.DEFAULT_DEVICE_NAME;
376 | }
377 | byte[] macBytes = Utils.macAddressToByteArray(macString);
378 |
379 | try {
380 | Enumeration ifaces = NetworkInterface.getNetworkInterfaces();
381 | while (ifaces.hasMoreElements()) {
382 | NetworkInterface iface = ifaces.nextElement();
383 |
384 | byte[] hardwareAddress = iface.getHardwareAddress();
385 | if (hardwareAddress != null && Arrays.equals(macBytes, hardwareAddress)) {
386 | return iface.getName();
387 | }
388 | }
389 | } catch (IOException e) {
390 | Log.e(TAG, "exception in retrieving device name: " + e.getMessage());
391 | }
392 |
393 | Log.w(TAG, "None found - falling back to the default device name: " + HotspotControl.DEFAULT_DEVICE_NAME);
394 | return HotspotControl.DEFAULT_DEVICE_NAME;
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/library/src/main/java/com/tml/sharethem/receiver/ReceiverActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Srihari Yachamaneni
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 | package com.tml.sharethem.receiver;
17 |
18 | import android.Manifest;
19 | import android.annotation.TargetApi;
20 | import android.content.ActivityNotFoundException;
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.DialogInterface;
24 | import android.content.Intent;
25 | import android.content.IntentFilter;
26 | import android.content.SharedPreferences;
27 | import android.content.pm.PackageManager;
28 | import android.location.LocationManager;
29 | import android.net.ConnectivityManager;
30 | import android.net.NetworkInfo;
31 | import android.net.Uri;
32 | import android.net.wifi.ScanResult;
33 | import android.net.wifi.SupplicantState;
34 | import android.net.wifi.WifiInfo;
35 | import android.net.wifi.WifiManager;
36 | import android.os.Build;
37 | import android.os.Bundle;
38 | import android.os.Handler;
39 | import android.os.Message;
40 | import android.provider.Settings;
41 | import android.support.annotation.NonNull;
42 | import android.support.v4.app.ActivityCompat;
43 | import android.support.v4.app.Fragment;
44 | import android.support.v4.app.FragmentTransaction;
45 | import android.support.v7.app.AlertDialog;
46 | import android.support.v7.app.AppCompatActivity;
47 | import android.support.v7.widget.SwitchCompat;
48 | import android.support.v7.widget.Toolbar;
49 | import android.text.Html;
50 | import android.text.TextUtils;
51 | import android.util.Log;
52 | import android.view.MenuItem;
53 | import android.view.View;
54 | import android.widget.CompoundButton;
55 | import android.widget.TextView;
56 | import android.widget.Toast;
57 |
58 | import com.tml.sharethem.R;
59 | import com.tml.sharethem.utils.Utils;
60 | import com.tml.sharethem.utils.WifiUtils;
61 |
62 | import java.lang.ref.WeakReference;
63 | import java.util.List;
64 |
65 | import static com.tml.sharethem.receiver.ReceiverActivity.WifiTasksHandler.SCAN_FOR_WIFI_RESULTS;
66 | import static com.tml.sharethem.receiver.ReceiverActivity.WifiTasksHandler.WAIT_FOR_CONNECT_ACTION_TIMEOUT;
67 | import static com.tml.sharethem.receiver.ReceiverActivity.WifiTasksHandler.WAIT_FOR_RECONNECT_ACTION_TIMEOUT;
68 | import static com.tml.sharethem.utils.Utils.isOreoOrAbove;
69 | import static com.tml.sharethem.utils.WifiUtils.connectToOpenWifi;
70 |
71 | /**
72 | * Controls
73 | */
74 | public class ReceiverActivity extends AppCompatActivity {
75 |
76 | public static final String TAG = "ReceiverActivity";
77 |
78 | TextView m_p2p_connection_status;
79 | SwitchCompat m_receiver_control;
80 | TextView m_goto_wifi_settings;
81 | TextView m_sender_files_header;
82 |
83 | private WifiManager wifiManager;
84 | private SharedPreferences preferences;
85 |
86 | private CompoundButton.OnCheckedChangeListener m_receiver_control_switch_listener;
87 |
88 | private WifiScanner mWifiScanReceiver;
89 | private WifiScanner mNwChangesReceiver;
90 | private WifiTasksHandler m_wifiScanHandler;
91 | private static final int PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION = 100;
92 | private String mConnectedSSID;
93 |
94 | private boolean m_areOtherNWsDisabled = false;
95 | Toolbar m_toolbar;
96 |
97 | private static String TAG_SENDER_FILES_LISTING = "sender_files_listing";
98 |
99 | private static final Long SYNCTIME = 800L;
100 | private static final String LASTCONNECTEDTIME = "LASTCONNECTEDTIME";
101 | private static final String LASTDISCONNECTEDTIME = "LASTDISCONNECTEDTIME";
102 |
103 | @Override
104 | protected void onCreate(Bundle savedInstanceState) {
105 | super.onCreate(savedInstanceState);
106 | setContentView(R.layout.activity_receiver);
107 | if (Utils.isShareServiceRunning(getApplication())) {
108 | Toast.makeText(this, "Share mode is active, stop Share service to proceed with Receiving files", Toast.LENGTH_SHORT).show();
109 | return;
110 | }
111 |
112 | m_p2p_connection_status = (TextView) findViewById(R.id.p2p_receiver_wifi_info);
113 | m_goto_wifi_settings = (TextView) findViewById(R.id.p2p_receiver_wifi_switch);
114 | m_sender_files_header = (TextView) findViewById(R.id.p2p_sender_files_header);
115 | m_receiver_control = (SwitchCompat) findViewById(R.id.p2p_receiver_ap_switch);
116 |
117 | m_goto_wifi_settings.setOnClickListener(new View.OnClickListener() {
118 | @Override
119 | public void onClick(View view) {
120 | gotoWifiSettings();
121 | }
122 | });
123 |
124 | m_toolbar = (Toolbar) findViewById(R.id.toolbar);
125 | m_toolbar.setTitle(getString(R.string.send_title));
126 | setSupportActionBar(m_toolbar);
127 | getSupportActionBar().setDisplayShowTitleEnabled(true);
128 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
129 |
130 | wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
131 | m_wifiScanHandler = new WifiTasksHandler(this);
132 | m_receiver_control_switch_listener = new CompoundButton.OnCheckedChangeListener() {
133 | @Override
134 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
135 | if (isChecked) {
136 | if (!startSenderScan())
137 | changeReceiverControlCheckedStatus(false);
138 | } else {
139 | changeReceiverControlCheckedStatus(true);
140 | showOptionsDialogWithListners(getString(R.string.p2p_receiver_close_warning), new DialogInterface.OnClickListener() {
141 | @Override
142 | public void onClick(DialogInterface dialog, int which) {
143 | changeReceiverControlCheckedStatus(false);
144 | disableReceiverMode();
145 | }
146 | }, new DialogInterface.OnClickListener() {
147 | @Override
148 | public void onClick(DialogInterface dialog, int which) {
149 | }
150 | }, getString(R.string.Action_Ok), getString(R.string.Action_cancel));
151 | }
152 | }
153 | };
154 | m_receiver_control.setOnCheckedChangeListener(m_receiver_control_switch_listener);
155 | if (!wifiManager.isWifiEnabled())
156 | wifiManager.setWifiEnabled(true);
157 | //start search by default
158 | m_receiver_control.setChecked(true);
159 | }
160 |
161 | @TargetApi(23)
162 | private boolean checkLocationPermission() {
163 | if (Build.VERSION.SDK_INT >= 23 && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
164 | ActivityCompat.requestPermissions(
165 | this,
166 | new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
167 | PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION
168 | );
169 | return false;
170 | }
171 | return true;
172 | }
173 |
174 | @Override
175 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
176 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
177 | switch (requestCode) {
178 | case PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION:
179 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
180 | if (checkLocationAccess())
181 | startSenderScan();
182 | } else {
183 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
184 | showOptionsDialogWithListners(getString(R.string.p2p_receiver_gps_permission_warning), new DialogInterface.OnClickListener() {
185 | @Override
186 | public void onClick(DialogInterface dialog, int which) {
187 | checkLocationPermission();
188 | }
189 | }, new DialogInterface.OnClickListener() {
190 | @Override
191 | public void onClick(DialogInterface dialog, int which) {
192 | dialog.dismiss();
193 | finish();
194 | }
195 | }, "Re-Try", "Yes, Im Sure");
196 | } else {
197 | showOptionsDialogWithListners(getString(R.string.p2p_receiver_gps_no_permission_prompt), new DialogInterface.OnClickListener() {
198 | @Override
199 | public void onClick(DialogInterface dialog, int which) {
200 | try {
201 | Intent intent = new Intent();
202 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
203 | Uri uri = Uri.fromParts("package", getPackageName(), null);
204 | intent.setData(uri);
205 | startActivity(intent);
206 | } catch (ActivityNotFoundException anf) {
207 | Toast.makeText(getApplicationContext(), "Settings activity not found", Toast.LENGTH_SHORT).show();
208 | }
209 | }
210 | }, new DialogInterface.OnClickListener() {
211 | @Override
212 | public void onClick(DialogInterface dialog, int which) {
213 | finish();
214 | }
215 | }, getString(R.string.label_settings), getString(R.string.Action_cancel));
216 | }
217 | }
218 | }
219 | }
220 |
221 | @Override
222 | public void onResume() {
223 | super.onResume();
224 | boolean isConnectedToShareThemAp = WifiUtils.isWifiConnectedToSTAccessPoint(getApplicationContext());
225 | if (isConnectedToShareThemAp) {
226 | unRegisterForScanResults();
227 | if (!m_receiver_control.isChecked())
228 | changeReceiverControlCheckedStatus(true);
229 | String ssid = wifiManager.getConnectionInfo().getSSID();
230 | Log.d(TAG, "wifi is connected/connecting to ShareThem ap, ssid: " + ssid);
231 | mConnectedSSID = ssid;
232 | addSenderFilesListingFragment(WifiUtils.getAccessPointIpAddress(this), ssid);
233 | } else if (m_receiver_control.isChecked()) {
234 | Log.d(TAG, "wifi isn't connected to ShareThem ap, initiating sender search..");
235 | resetSenderSearch();
236 | }
237 | }
238 |
239 | @Override
240 | protected void onPause() {
241 | super.onPause();
242 | unRegisterForScanResults();
243 | }
244 |
245 | @Override
246 | protected void onDestroy() {
247 | super.onDestroy();
248 | unRegisterForNwChanges();
249 | }
250 |
251 | @Override
252 | public boolean onOptionsItemSelected(MenuItem item) {
253 | if (item.getItemId() == android.R.id.home) {
254 | onBackPressed();
255 | return true;
256 | }
257 | return super.onOptionsItemSelected(item);
258 | }
259 |
260 | /**
261 | * Entry point to start receiver mode. Makes calls to register necessary broadcast receivers to start scanning for SHAREthem Wifi Hotspot.
262 | *
263 | * @return
264 | */
265 | private boolean startSenderScan() {
266 | if (Utils.getTargetSDKVersion(getApplicationContext()) >= 23
267 | &&
268 | // if targetSdkVersion >= 23
269 | // Get Wifi Scan results method needs GPS to be ON and COARSE location permission
270 | !checkLocationPermission()) {
271 | return false;
272 | }
273 | changeReceiverControlCheckedStatus(true);
274 | registerAndScanForWifiResults();
275 | registerForNwChanges();
276 | return true;
277 | }
278 |
279 | /**
280 | * Disables and removes SHAREthem wifi configuration from Wifi Settings. Also does cleanup work to remove handlers, un-register receivers etc..
281 | */
282 | private void disableReceiverMode() {
283 | if (!TextUtils.isEmpty(mConnectedSSID)) {
284 | if (m_areOtherNWsDisabled)
285 | WifiUtils.removeSTWifiAndEnableOthers(wifiManager, mConnectedSSID);
286 | else
287 | WifiUtils.removeWifiNetwork(wifiManager, mConnectedSSID);
288 | }
289 | m_wifiScanHandler.removeMessages(WAIT_FOR_CONNECT_ACTION_TIMEOUT);
290 | m_wifiScanHandler.removeMessages(WAIT_FOR_RECONNECT_ACTION_TIMEOUT);
291 | unRegisterForScanResults();
292 | unRegisterForNwChanges();
293 | removeSenderFilesListingFragmentIfExists();
294 | }
295 |
296 | public void showOptionsDialogWithListners(String message,
297 | DialogInterface.OnClickListener pListner,
298 | DialogInterface.OnClickListener nListener, String pButtonText,
299 | String nButtonText) {
300 | AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
301 | builder.setCancelable(false);
302 | builder.setMessage(Html.fromHtml(message));
303 | builder.setPositiveButton(pButtonText, pListner);
304 | builder.setNegativeButton(nButtonText, null != nListener ? nListener
305 | : new DialogInterface.OnClickListener() {
306 |
307 | @Override
308 | public void onClick(DialogInterface dialog, int which) {
309 | dialog.cancel();
310 | return;
311 | }
312 | });
313 | builder.show();
314 | }
315 |
316 | /**
317 | * Changes checked status without invoking listener. Removes @{@link android.widget.CompoundButton.OnCheckedChangeListener} on @{@link SwitchCompat} button before changing checked status
318 | *
319 | * @param checked if true, sets @{@link SwitchCompat} checked.
320 | */
321 | private void changeReceiverControlCheckedStatus(boolean checked) {
322 | m_receiver_control.setOnCheckedChangeListener(null);
323 | m_receiver_control.setChecked(checked);
324 | m_receiver_control.setOnCheckedChangeListener(m_receiver_control_switch_listener);
325 | }
326 |
327 | /**
328 | * Registers for {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} action and also calls a method to start Wifi Scan action.
329 | */
330 | private void registerAndScanForWifiResults() {
331 | if (null == mWifiScanReceiver)
332 | mWifiScanReceiver = new WifiScanner();
333 | IntentFilter intentFilter = new IntentFilter();
334 | intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
335 | registerReceiver(mWifiScanReceiver, intentFilter);
336 | m_p2p_connection_status.setText(getString(R.string.p2p_receiver_scanning_hint));
337 | startWifiScan();
338 | }
339 |
340 | /**
341 | * Registers for {@link WifiManager#NETWORK_STATE_CHANGED_ACTION} action
342 | */
343 | private void registerForNwChanges() {
344 | if (null == mNwChangesReceiver)
345 | mNwChangesReceiver = new WifiScanner();
346 | IntentFilter intentFilter = new IntentFilter();
347 | intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
348 | registerReceiver(mNwChangesReceiver, intentFilter);
349 | }
350 |
351 | private void unRegisterForScanResults() {
352 | stopWifiScan();
353 | try {
354 | if (null != mWifiScanReceiver)
355 | unregisterReceiver(mWifiScanReceiver);
356 | } catch (Exception e) {
357 | Log.e(TAG, "exception while un-registering wifi changes.." + e.getMessage());
358 | }
359 | }
360 |
361 | private void unRegisterForNwChanges() {
362 | try {
363 | if (null != mNwChangesReceiver)
364 | unregisterReceiver(mNwChangesReceiver);
365 | } catch (Exception e) {
366 | Log.e(TAG, "exception while un-registering NW changes.." + e.getMessage());
367 | }
368 | }
369 |
370 | private void startWifiScan() {
371 | m_wifiScanHandler.removeMessages(SCAN_FOR_WIFI_RESULTS);
372 | m_wifiScanHandler.sendMessageDelayed(m_wifiScanHandler.obtainMessage(SCAN_FOR_WIFI_RESULTS), 500);
373 | }
374 |
375 | private void stopWifiScan() {
376 | if (null != m_wifiScanHandler)
377 | m_wifiScanHandler.removeMessages(SCAN_FOR_WIFI_RESULTS);
378 | }
379 |
380 | private boolean checkLocationAccess() {
381 | final LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
382 | if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
383 | Log.e(TAG, "GPS not enabled..");
384 | buildAlertMessageNoGps();
385 | return false;
386 | }
387 | return true;
388 | }
389 |
390 | private void buildAlertMessageNoGps() {
391 | final AlertDialog.Builder builder = new AlertDialog.Builder(this);
392 | builder.setMessage("Your GPS seems to be disabled, Please enabled it to proceed with p2p movie sharing")
393 | .setCancelable(false)
394 | .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
395 | public void onClick(final DialogInterface dialog, final int id) {
396 | startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
397 | dialog.dismiss();
398 | }
399 | })
400 | .setNegativeButton("No", new DialogInterface.OnClickListener() {
401 | public void onClick(final DialogInterface dialog, final int id) {
402 | dialog.cancel();
403 | finish();
404 | }
405 | });
406 | final AlertDialog alert = builder.create();
407 | alert.show();
408 |
409 | }
410 |
411 | public void resetSenderSearch() {
412 | removeSenderFilesListingFragmentIfExists();
413 | startSenderScan();
414 | }
415 |
416 | private void connectToWifi(String ssid) {
417 | WifiInfo info = wifiManager.getConnectionInfo();
418 | unRegisterForScanResults();
419 | if (isOreoOrAbove()) {
420 | promptToConnectManually(ssid);
421 | return;
422 | }
423 | boolean resetWifiScan;
424 | if (info.getSSID().equals(ssid)) {
425 | Log.d(TAG, "Already connected to ShareThem, add sender Files listing fragment");
426 | resetWifiScan = false;
427 | addSenderFilesListingFragment(WifiUtils.getAccessPointIpAddress(getApplicationContext()), ssid);
428 | } else {
429 | m_p2p_connection_status.setText(getString(R.string.p2p_receiver_connecting_hint, ssid));
430 | resetWifiScan = !connectToOpenWifi(wifiManager, ssid, false);
431 | Log.e(TAG, "connection attempt to ShareThem wifi is " + (!resetWifiScan ? "success!!!" : "FAILED..!!!"));
432 | }
433 | //if wap isnt successful, start wifi scan
434 | if (resetWifiScan) {
435 | Toast.makeText(this, getString(R.string.p2p_receiver_error_in_connecting, ssid), Toast.LENGTH_SHORT).show();
436 | m_p2p_connection_status.setText(getString(R.string.p2p_receiver_scanning_hint));
437 | startSenderScan();
438 | } else {
439 | Message message = m_wifiScanHandler.obtainMessage(WAIT_FOR_CONNECT_ACTION_TIMEOUT);
440 | message.obj = ssid;
441 | m_wifiScanHandler.sendMessageDelayed(message, 7000);
442 | }
443 | }
444 |
445 | private void promptToConnectManually(final String ssid) {
446 | showOptionsDialogWithListners(getString(R.string.p2p_receiver_oreo_msg, ssid), new DialogInterface.OnClickListener() {
447 | @Override
448 | public void onClick(DialogInterface dialog, int which) {
449 | try {
450 | Intent intent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
451 | startActivity(intent);
452 | } catch (Exception e) {
453 | Toast.makeText(ReceiverActivity.this, "Wifi listings not found on your device!! :(", Toast.LENGTH_SHORT).show();
454 | }
455 | }
456 | }, new DialogInterface.OnClickListener() {
457 | @Override
458 | public void onClick(DialogInterface dialog, int which) {
459 | Toast.makeText(getApplicationContext(), getString(R.string.p2p_receiver_error_in_connecting, ssid), Toast.LENGTH_SHORT).show();
460 | m_p2p_connection_status.setText(getString(R.string.p2p_receiver_scanning_hint));
461 | startSenderScan();
462 | }
463 | }, "Settings", "Cancel");
464 | }
465 |
466 | private class WifiScanner extends BroadcastReceiver {
467 | @Override
468 | public void onReceive(Context context, Intent intent) {
469 | if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) && !WifiUtils.isWifiConnectedToSTAccessPoint(getApplicationContext())) {
470 | List mScanResults = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getScanResults();
471 | boolean foundSTWifi = false;
472 | for (ScanResult result : mScanResults)
473 | if (WifiUtils.isShareThemSSID(result.SSID) && (isOreoOrAbove() || WifiUtils.isOpenWifi(result))) {
474 | Log.d(TAG, "signal level: " + result.level);
475 | connectToWifi(result.SSID);
476 | foundSTWifi = true;
477 | break;
478 | }
479 | if (!foundSTWifi) {
480 | Log.e(TAG, "no ST wifi found, starting scan again!!");
481 | startWifiScan();
482 | }
483 | } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
484 | NetworkInfo netInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
485 | if (ConnectivityManager.TYPE_WIFI == netInfo.getType()) {
486 | WifiInfo info = wifiManager.getConnectionInfo();
487 | SupplicantState supState = info.getSupplicantState();
488 | Log.d(TAG, "NETWORK_STATE_CHANGED_ACTION, ssid: " + info.getSSID() + ", ap ip: " + WifiUtils.getAccessPointIpAddress(getApplicationContext()) + ", sup state: " + supState);
489 | if (null == preferences)
490 | preferences = getSharedPreferences(
491 | getPackageName(), Context.MODE_PRIVATE);
492 | if (WifiUtils.isShareThemSSID(info.getSSID())) {
493 | if (System.currentTimeMillis() - preferences.getLong(LASTCONNECTEDTIME, 0) >= SYNCTIME && supState.equals(SupplicantState.COMPLETED)) {
494 | mConnectedSSID = info.getSSID();
495 | m_wifiScanHandler.removeMessages(WAIT_FOR_CONNECT_ACTION_TIMEOUT);
496 | m_wifiScanHandler.removeMessages(WAIT_FOR_RECONNECT_ACTION_TIMEOUT);
497 | final String ip = WifiUtils.getAccessPointIpAddress(getApplicationContext());
498 | preferences.edit().putLong(LASTCONNECTEDTIME, System.currentTimeMillis()).commit();
499 | Log.d(TAG, "client connected to ShareThem hot spot. AP ip address: " + ip);
500 | addSenderFilesListingFragment(ip, info.getSSID());
501 | }
502 | // else if (!netInfo.isConnectedOrConnecting() && System.currentTimeMillis() - Prefs.getInstance().loadLong(LASTDISCONNECTEDTIME, 0) >= SYNCTIME) {
503 | // Prefs.getInstance().saveLong(LASTDISCONNECTEDTIME, System.currentTimeMillis());
504 | // if (LogUtil.LOG)
505 | // LogUtil.e(TAG, "AP disconnedted..");
506 | // Toast.makeText(context, "Sender Wifi Hotspot disconnected. Retrying to connect..", Toast.LENGTH_SHORT).show();
507 | // resetSenderSearch();
508 | // }
509 | }
510 | }
511 | }
512 | }
513 | }
514 |
515 |
516 | private void addSenderFilesListingFragment(String ip, String ssid) {
517 | String[] senderInfo = setConnectedUi(ssid);
518 | if (null == senderInfo) {
519 | Log.e(TAG, "Cant retrieve port and name info from SSID");
520 | return;
521 | }
522 | Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_SENDER_FILES_LISTING);
523 | if (null != fragment) {
524 | if (ip.equals(((FilesListingFragment) fragment).getSenderIp()) && ssid.equals(((FilesListingFragment) fragment).getSenderSSID())) {
525 | Log.e(TAG, "files fragment exists already!!");
526 | return;
527 | } else {
528 | Log.e(TAG, "fragment with diff tag is found, removing to add a fresh one..");
529 | removeSenderFilesListingFragmentIfExists();
530 | }
531 | }
532 | Log.d(TAG, "adding files fragment with ip: " + ip);
533 | FragmentTransaction ft = getSupportFragmentManager()
534 | .beginTransaction();
535 | FilesListingFragment files_listing_fragment = FilesListingFragment.getInstance(ip, ssid, senderInfo[0], senderInfo[1]);
536 | // ft.setCustomAnimations(R.anim.push_left_in, R.anim.push_left_out,
537 | // R.anim.push_right_in, R.anim.push_right_out);
538 | ft.add(R.id.sender_files_list_fragment_holder, files_listing_fragment, TAG_SENDER_FILES_LISTING).commitAllowingStateLoss();
539 | }
540 |
541 | private String[] setConnectedUi(String ssid) {
542 | String[] senderInfo = WifiUtils.getSenderInfoFromSSID(ssid);
543 | if (null == senderInfo || senderInfo.length != 2)
544 | return null;
545 | String ip = WifiUtils.getThisDeviceIp(getApplicationContext());
546 | m_p2p_connection_status.setText(getString(R.string.p2p_receiver_connected_hint, ssid, ip));
547 | m_goto_wifi_settings.setVisibility(View.VISIBLE);
548 | if (!m_receiver_control.isChecked())
549 | changeReceiverControlCheckedStatus(true);
550 | m_sender_files_header.setVisibility(View.VISIBLE);
551 | return senderInfo;
552 | }
553 |
554 | protected void gotoWifiSettings() {
555 | try {
556 | startActivity(new Intent(
557 | Settings.ACTION_WIFI_SETTINGS));
558 | } catch (ActivityNotFoundException anf) {
559 | Toast.makeText(this, "No Wifi listings feature found on this device", Toast.LENGTH_SHORT).show();
560 | }
561 | }
562 |
563 | private void removeSenderFilesListingFragmentIfExists() {
564 | m_p2p_connection_status.setText(getString(m_receiver_control.isChecked() ? R.string.p2p_receiver_scanning_hint : R.string.p2p_receiver_hint_text));
565 | m_goto_wifi_settings.setVisibility(View.GONE);
566 | m_sender_files_header.setVisibility(View.GONE);
567 | Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_SENDER_FILES_LISTING);
568 | if (null != fragment)
569 | getSupportFragmentManager()
570 | .beginTransaction()
571 | .remove(fragment).commitAllowingStateLoss();
572 | }
573 |
574 | static class WifiTasksHandler extends Handler {
575 | static final int SCAN_FOR_WIFI_RESULTS = 100;
576 | static final int WAIT_FOR_CONNECT_ACTION_TIMEOUT = 101;
577 | static final int WAIT_FOR_RECONNECT_ACTION_TIMEOUT = 102;
578 | private WeakReference mActivity;
579 |
580 | WifiTasksHandler(ReceiverActivity activity) {
581 | mActivity = new WeakReference<>(activity);
582 | }
583 |
584 | @Override
585 | public void handleMessage(Message msg) {
586 | final ReceiverActivity activity = mActivity.get();
587 | if (null == activity)
588 | return;
589 | switch (msg.what) {
590 | case SCAN_FOR_WIFI_RESULTS:
591 | if (null != activity.wifiManager)
592 | activity.wifiManager.startScan();
593 | break;
594 | case WAIT_FOR_CONNECT_ACTION_TIMEOUT:
595 | Log.e(TAG, "cant connect to sender's hotspot by increasing priority, try the dirty way..");
596 | activity.m_areOtherNWsDisabled = WifiUtils.connectToOpenWifi(activity.wifiManager, (String) msg.obj, true);
597 | Message m = obtainMessage(WAIT_FOR_RECONNECT_ACTION_TIMEOUT);
598 | m.obj = msg.obj;
599 | sendMessageDelayed(m, 6000);
600 | break;
601 | case WAIT_FOR_RECONNECT_ACTION_TIMEOUT:
602 | if (WifiUtils.isWifiConnectedToSTAccessPoint(activity) || activity.isFinishing())
603 | return;
604 | Log.e(TAG, "Even the dirty hack couldn't do it, prompt user to chose it fromWIFI settings..");
605 | activity.disableReceiverMode();
606 | activity.showOptionsDialogWithListners(activity.getString(R.string.p2p_receiver_connect_timeout_error, msg.obj), new DialogInterface.OnClickListener() {
607 | @Override
608 | public void onClick(DialogInterface dialog, int which) {
609 | dialog.dismiss();
610 | activity.gotoWifiSettings();
611 | }
612 | }, new DialogInterface.OnClickListener() {
613 | @Override
614 | public void onClick(DialogInterface dialogInterface, int i) {
615 | activity.finish();
616 | }
617 | }, "Settings", "Cancel");
618 | break;
619 | }
620 | }
621 | }
622 | }
623 |
--------------------------------------------------------------------------------