Compare commits

..

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] dd0cbcee6a Change from AlertDialog to contextual submenu for call options
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-04 17:30:01 +00:00
copilot-swe-agent[bot] ca14e0dcc6 Add call-specific permission explanation strings
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-04 17:23:03 +00:00
copilot-swe-agent[bot] 04ad02f56d Optimize permissions for audio-only calls
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-04 17:21:14 +00:00
copilot-swe-agent[bot] e578853a62 Add audio/video call selection dialog
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-04 17:19:34 +00:00
copilot-swe-agent[bot] fc25f2ed9e Initial plan 2026-02-04 17:16:12 +00:00
7 changed files with 39 additions and 88 deletions
-1
View File
@@ -360,7 +360,6 @@
<activity android:name=".WebxdcActivity"
android:label=""
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="adjustPan"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
</activity>
@@ -569,8 +569,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else if (itemId == R.id.menu_show_map) {
WebxdcActivity.openMaps(this, chatId);
return true;
} else if (itemId == R.id.menu_start_call) {
CallUtil.startCall(this, chatId);
} else if (itemId == R.id.menu_audio_call) {
CallUtil.startCall(this, chatId, true);
return true;
} else if (itemId == R.id.menu_video_call) {
CallUtil.startCall(this, chatId, false);
return true;
} else if (itemId == R.id.menu_all_media) {
handleAllMedia();
@@ -77,8 +77,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
findViewById(R.id.status_bar_background).setBackgroundResource(R.drawable.search_toolbar_shadow);
} else {
// add padding to avoid content hidden behind system bars
// Use applyWindowInsetsExcludingIme to respect navigation bars but not keyboard
ViewUtil.applyWindowInsetsExcludingIme(findViewById(R.id.content_container));
ViewUtil.applyWindowInsets(findViewById(R.id.content_container));
}
webView.setWebViewClient(new WebViewClient() {
@@ -19,23 +19,40 @@ public class CallUtil {
private static final String TAG = CallUtil.class.getSimpleName();
public static void startCall(Activity activity, int chatId) {
startCall(activity, chatId, false);
}
public static void startCall(Activity activity, int chatId, boolean audioOnly) {
String[] permissions = audioOnly
? new String[] { Manifest.permission.RECORD_AUDIO }
: new String[] { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };
String permissionExplanation = audioOnly
? activity.getString(R.string.perm_explain_access_to_mic_for_calls_denied)
: activity.getString(R.string.perm_explain_access_to_camera_and_mic_denied);
Permissions.with(activity)
.request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
.request(permissions)
.ifNecessary()
.withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_camera_denied))
.withPermanentDenialDialog(permissionExplanation)
.onAllGranted(() -> {
int accId = DcHelper.getContext(activity).getAccountId();
startCall(activity, accId, chatId);
startCall(activity, accId, chatId, audioOnly);
})
.execute();
}
public static void startCall(Context context, int accId, int chatId) {
startCall(context, accId, chatId, false);
}
public static void startCall(Context context, int accId, int chatId, boolean audioOnly) {
Intent intent = new Intent(context, CallActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, accId);
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatId);
intent.putExtra(CallActivity.EXTRA_HASH, "#startCall");
String hash = audioOnly ? "?disableVideoCompletely#startCall" : "#startCall";
intent.putExtra(CallActivity.EXTRA_HASH, hash);
context.startActivity(intent);
}
@@ -316,23 +316,6 @@ public class ViewUtil {
return Insets.max(systemBars, displayCutout);
}
/**
* Get combined insets from status bar, navigation bar and display cutout areas,
* excluding the IME (soft keyboard).
*
* @param windowInsets The window insets to extract from
* @return Combined insets excluding IME
*/
private static Insets getCombinedInsetsExcludingIme(@NonNull WindowInsetsCompat windowInsets) {
Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
Insets displayCutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
// Combine systemBars (which excludes IME) with displayCutout using max to handle notches/cutouts
Insets combined = Insets.max(systemBars, displayCutout);
return combined;
}
/**
* Apply window insets to a view by adding margin to avoid drawing it behind system bars.
* Convenience method that applies insets to all sides.
@@ -468,67 +451,6 @@ public class ViewUtil {
}
}
/**
* Apply window insets to a view by adding padding to avoid drawing elements behind system bars,
* excluding IME (soft keyboard) insets. This is useful for views that should respect navigation
* bars but handle keyboard resizing through the window's soft input mode.
*
* This method stores the original padding values in view tags to ensure that
* padding doesn't accumulate on multiple inset applications.
*
* Note: This feature is only enabled on API 30+ (Android 11+) where WindowInsets APIs
* work correctly. On older API levels, the method returns early and the view will use
* default system bar handling (content may be drawn behind system bars).
*
* @param view The view to apply insets to
*/
public static void applyWindowInsetsExcludingIme(@NonNull View view) {
// Only enable on API 30+ where WindowInsets APIs work correctly
if (!isEdgeToEdgeSupported()) return;
// Store the original padding as a tag only if not already stored
// This prevents losing the true original padding on subsequent calls
if (view.getTag(R.id.tag_window_insets_padding_left) == null) {
view.setTag(R.id.tag_window_insets_padding_left, view.getPaddingLeft());
view.setTag(R.id.tag_window_insets_padding_top, view.getPaddingTop());
view.setTag(R.id.tag_window_insets_padding_right, view.getPaddingRight());
view.setTag(R.id.tag_window_insets_padding_bottom, view.getPaddingBottom());
}
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
Insets insets = getCombinedInsetsExcludingIme(windowInsets);
// Retrieve the original padding values from tags with null checks
Integer leftTag = (Integer) v.getTag(R.id.tag_window_insets_padding_left);
Integer topTag = (Integer) v.getTag(R.id.tag_window_insets_padding_top);
Integer rightTag = (Integer) v.getTag(R.id.tag_window_insets_padding_right);
Integer bottomTag = (Integer) v.getTag(R.id.tag_window_insets_padding_bottom);
int basePaddingLeft = leftTag != null ? leftTag : 0;
int basePaddingTop = topTag != null ? topTag : 0;
int basePaddingRight = rightTag != null ? rightTag : 0;
int basePaddingBottom = bottomTag != null ? bottomTag : 0;
v.setPadding(
basePaddingLeft + insets.left,
basePaddingTop + insets.top,
basePaddingRight + insets.right,
basePaddingBottom + insets.bottom
);
// Consume IME insets to prevent them from affecting child views (like WebView)
// This stops the double-padding issue where both window resize and inset padding occur
WindowInsetsCompat withoutIme = new WindowInsetsCompat.Builder(windowInsets)
.setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE)
.build();
return withoutIme;
});
// Request the initial insets to be dispatched if the view is attached
if (view.isAttachedToWindow()) {
ViewCompat.requestApplyInsets(view);
}
}
/**
* Apply the top status bar inset as the height of a view.
*/
+8 -1
View File
@@ -28,7 +28,14 @@
<item android:id="@+id/menu_start_call"
android:title="@string/start_call"
android:icon="@drawable/ic_videocam_white_24dp"
app:showAsAction="always" />
app:showAsAction="always">
<menu>
<item android:id="@+id/menu_audio_call"
android:title="@string/start_audio_call" />
<item android:id="@+id/menu_video_call"
android:title="@string/start_video_call" />
</menu>
</item>
<item android:id="@+id/menu_all_media"
android:title="@string/apps_and_media"
+4
View File
@@ -380,6 +380,8 @@
<!-- the action "to call someone", used as a tooltip for the "phone" icon. not: "the call" -->
<string name="start_call">Call</string>
<string name="start_audio_call">Audio Call</string>
<string name="start_video_call">Video Call</string>
<!-- the action "to answer" or to "accept" or to "pick up" a call. not: "the answer" -->
<string name="answer_call">Answer</string>
<!-- the action "to decline" a call, not: "the decline" -->
@@ -1037,6 +1039,8 @@
<string name="perm_explain_access_to_camera_denied">To take photos or capture videos, go to the app settings, select \"Permissions\", and enable \"Camera\".</string>
<!-- give the user an idea where to find the "Microphone" option. the hint is only shown if access was initially denied by the user. pick up wordings really used on the systems, but do not be overly precise: things shift around often and there are too many system we support to track every detail and click path -->
<string name="perm_explain_access_to_mic_denied">To send audio messages, go to system or app settings, select "Permissions" or "Privacy &amp; Security", and enable \"Microphone\".</string>
<string name="perm_explain_access_to_camera_and_mic_denied">To make video calls, go to the app settings, select \"Permissions\", and enable \"Camera\" and \"Microphone\".</string>
<string name="perm_explain_access_to_mic_for_calls_denied">To make audio calls, go to system or app settings, select "Permissions" or "Privacy &amp; Security", and enable \"Microphone\".</string>
<string name="perm_explain_access_to_storage_denied">To receive or send files, go to the app settings, select \"Permissions\", and enable \"Storage\".</string>
<string name="perm_explain_access_to_location_denied">To attach a location, go to the app settings, select \"Permissions\", and enable \"Location\".</string>
<string name="perm_explain_access_to_notifications_denied">To receive notifications, go to \"System Settings / Apps / Delta Chat\" and enable \"Notifications\".</string>