From 271b6e70a25337db7c0a897d87e96e7c993b959a Mon Sep 17 00:00:00 2001 From: kateshevchenko Date: Tue, 5 Nov 2024 08:27:15 -0800 Subject: [PATCH] Add share button and share functionality. --- .../apps/walkietalkie/MainActivity.java | 86 +++++++++++++++++++ .../app/src/main/AndroidManifest.xml | 1 + .../walkietalkie/ConnectionsActivity.java | 73 ++++++++++++++++ .../app/src/main/res/layout/activity_main.xml | 14 +++ .../app/src/main/res/values/strings.xml | 2 + 5 files changed, 176 insertions(+) diff --git a/NearbyConnectionsWalkieTalkie/app/src/automatic/java/com/google/location/nearby/apps/walkietalkie/MainActivity.java b/NearbyConnectionsWalkieTalkie/app/src/automatic/java/com/google/location/nearby/apps/walkietalkie/MainActivity.java index 43cb7090..0f948144 100644 --- a/NearbyConnectionsWalkieTalkie/app/src/automatic/java/com/google/location/nearby/apps/walkietalkie/MainActivity.java +++ b/NearbyConnectionsWalkieTalkie/app/src/automatic/java/com/google/location/nearby/apps/walkietalkie/MainActivity.java @@ -3,11 +3,17 @@ import android.Manifest; import android.animation.Animator; import android.animation.ObjectAnimator; +import android.app.Activity; import android.content.Context; +import android.content.Intent; +import android.database.Cursor; import android.media.AudioManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -15,6 +21,8 @@ import androidx.annotation.WorkerThread; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; + +import android.provider.OpenableColumns; import android.text.SpannableString; import android.text.format.DateFormat; import android.text.method.ScrollingMovementMethod; @@ -22,12 +30,17 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewAnimationUtils; +import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.nearby.connection.ConnectionInfo; import com.google.android.gms.nearby.connection.Payload; import com.google.android.gms.nearby.connection.Strategy; +import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; import java.util.Random; /** @@ -43,6 +56,8 @@ * down the volume keys and speaking into the phone. Advertising and discovery have both stopped. */ public class MainActivity extends ConnectionsActivity { + + private final Collection payloads = new ArrayList<>(); /** If true, debug logs are shown on the device. */ private static final boolean DEBUG = true; @@ -149,8 +164,72 @@ protected void onCreate(Bundle savedInstanceState) { mName = generateRandomName(); ((TextView) findViewById(R.id.name)).setText(mName); + + Button shareButton = findViewById(R.id.button_share); + shareButton.setOnClickListener(view -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + activityResultLaunch.launch(intent); + } + ); + } + + ActivityResultLauncher activityResultLaunch = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + int resultCode = result.getResultCode(); + Intent resultData = result.getData(); + if (resultCode == Activity.RESULT_OK && resultData != null) { + Uri uri = resultData.getData(); + Payload filePayload; + try { + ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); + filePayload = Payload.fromFile(pfd); + logV("Payload created successfully"); + } catch (FileNotFoundException e) { + logV("File not found"); + return; + } + + String filenameMessage = filePayload.getId() + ":" + getFileName(uri); + + logV("FileNameMessage : " + filenameMessage); + Payload filenameBytesPayload; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + filenameBytesPayload = + Payload.fromBytes(filenameMessage.getBytes(StandardCharsets.UTF_8)); + payloads.add(filenameBytesPayload); + } + + payloads.add(filePayload); + } + }); + + private String getFileName(Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = getContentResolver().query(uri, null, null, null, null); + try { + int idx = 0; + if (cursor != null && cursor.moveToFirst() && (idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) >= 0) { + result = cursor.getString(idx); + } + } finally { + cursor.close(); + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; } + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (mState == State.CONNECTED && mGestureDetector.onKeyEvent(event)) { @@ -231,6 +310,11 @@ protected void onEndpointConnected(Endpoint endpoint) { this, getString(R.string.toast_connected, endpoint.getName()), Toast.LENGTH_SHORT) .show(); setState(State.CONNECTED); + for (Payload payload : payloads) { + send(payload); + } + payloads.clear(); + findViewById(R.id.button_share).setEnabled(true); } @Override @@ -239,6 +323,7 @@ protected void onEndpointDisconnected(Endpoint endpoint) { this, getString(R.string.toast_disconnected, endpoint.getName()), Toast.LENGTH_SHORT) .show(); setState(State.SEARCHING); + findViewById(R.id.button_share).setEnabled(false); } @Override @@ -247,6 +332,7 @@ protected void onConnectionFailed(Endpoint endpoint) { if (getState() == State.SEARCHING) { startDiscovering(); } + findViewById(R.id.button_share).setEnabled(false); } /** diff --git a/NearbyConnectionsWalkieTalkie/app/src/main/AndroidManifest.xml b/NearbyConnectionsWalkieTalkie/app/src/main/AndroidManifest.xml index ac6e7223..068d0932 100755 --- a/NearbyConnectionsWalkieTalkie/app/src/main/AndroidManifest.xml +++ b/NearbyConnectionsWalkieTalkie/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + diff --git a/NearbyConnectionsWalkieTalkie/app/src/main/java/com/google/location/nearby/apps/walkietalkie/ConnectionsActivity.java b/NearbyConnectionsWalkieTalkie/app/src/main/java/com/google/location/nearby/apps/walkietalkie/ConnectionsActivity.java index 2324270e..bf9972f5 100644 --- a/NearbyConnectionsWalkieTalkie/app/src/main/java/com/google/location/nearby/apps/walkietalkie/ConnectionsActivity.java +++ b/NearbyConnectionsWalkieTalkie/app/src/main/java/com/google/location/nearby/apps/walkietalkie/ConnectionsActivity.java @@ -1,16 +1,20 @@ package com.google.location.nearby.apps.walkietalkie; import android.Manifest; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.appcompat.app.AppCompatActivity; +import android.os.Environment; import android.util.Log; import android.widget.Toast; @@ -32,6 +36,12 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -169,10 +179,22 @@ public void onDisconnected(String endpointId) { /** Callbacks for payloads (bytes of data) sent from another device to us. */ private final PayloadCallback mPayloadCallback = new PayloadCallback() { + private final Map filePayloads = new HashMap<>(); + private final Map filePayloadFilenames = new HashMap<>(); + @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onPayloadReceived(String endpointId, Payload payload) { logD(String.format("onPayloadReceived(endpointId=%s, payload=%s)", endpointId, payload)); onReceive(mEstablishedConnections.get(endpointId), payload); + + if (payload.getType() == Payload.Type.BYTES) { + String payloadFilenameMessage = new String(payload.asBytes(), StandardCharsets.UTF_8); + long payloadId = addPayloadFilename(payloadFilenameMessage); + processPayload(payloadId); + } else if (payload.getType() == Payload.Type.FILE) { + // Add this to our tracking map, so that we can retrieve the payload later. + filePayloads.put(payload.getId(), payload); + } } @Override @@ -180,7 +202,58 @@ public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate upd logD( String.format( "onPayloadTransferUpdate(endpointId=%s, update=%s)", endpointId, update)); + if (update.getStatus() == PayloadTransferUpdate.Status.SUCCESS) { + processPayload(update.getPayloadId()); + } + } + + private void processPayload(long payloadId) { + Payload filePayload = filePayloads.get(payloadId); + String filename = filePayloadFilenames.get(payloadId); + if (filePayload != null && filename != null) { + filePayloads.remove(payloadId); + filePayloadFilenames.remove(payloadId); + + // Get the received file (which will be in the Downloads folder) + Uri uri = filePayload.asFile().asUri(); + ContentResolver contentResolver = getApplicationContext().getContentResolver(); + try { + File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + logV("Saving file to " + path.toString() + " with name " + filename); + InputStream in = contentResolver.openInputStream(uri); + copyStream(in, new FileOutputStream(new File(path, filename))); + } catch (IOException e) { + logE("Cannot save file", e); + } finally { + contentResolver.delete(uri, null, null); + } + } } + + /** Copies a stream from one location to another. */ + private void copyStream(InputStream in, OutputStream out) throws IOException { + try (in; out) { + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + out.flush(); + } + } + + /** + * Extracts the payloadId and filename from the message and stores it in the + * filePayloadFilenames map. The format is payloadId:filename. + */ + private long addPayloadFilename(String payloadFilenameMessage) { + String[] parts = payloadFilenameMessage.split(":"); + long payloadId = Long.parseLong(parts[0]); + String filename = parts[1]; + filePayloadFilenames.put(payloadId, filename); + return payloadId; + } + }; /** Called when our Activity is first created. */ diff --git a/NearbyConnectionsWalkieTalkie/app/src/main/res/layout/activity_main.xml b/NearbyConnectionsWalkieTalkie/app/src/main/res/layout/activity_main.xml index f7b70f4c..b41b5f61 100644 --- a/NearbyConnectionsWalkieTalkie/app/src/main/res/layout/activity_main.xml +++ b/NearbyConnectionsWalkieTalkie/app/src/main/res/layout/activity_main.xml @@ -44,4 +44,18 @@ android:textSize="20sp" android:textColor="@color/textColor" /> +