asks for storage permission when saving media

This commit is contained in:
Mariotaku Lee 2015-10-10 18:00:08 +08:00
parent 2c984d5a2c
commit 29ffac03ef
9 changed files with 106 additions and 25 deletions

View File

@ -309,4 +309,6 @@ public interface SharedPreferenceConstants {
String KEY_CACHE_SIZE_LIMIT = "cache_size_limit";
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = true)
String KEY_BUG_REPORTS = "bug_reports";
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = true)
String COMBINED_NOTIFICATIONS = "combined_notifications";
}

View File

@ -26,6 +26,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import com.squareup.otto.Bus;
@ -72,7 +73,7 @@ public class BaseAppCompatActivity extends ThemedAppCompatActivity implements Co
private boolean mInstanceStateSaved;
private boolean mIsVisible;
private Rect mSystemWindowsInsets;
private int mMetaState;
private int mKeyMetaState;
@Override
public boolean getSystemWindowsInsets(Rect insets) {
@ -110,23 +111,31 @@ public class BaseAppCompatActivity extends ThemedAppCompatActivity implements Co
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
if (KeyEvent.isModifierKey(keyCode)) {
mMetaState &= ~KeyboardShortcutsHandler.getMetaStateForKeyCode(keyCode);
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mKeyMetaState |= KeyboardShortcutsHandler.getMetaStateForKeyCode(keyCode);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mKeyMetaState &= ~KeyboardShortcutsHandler.getMetaStateForKeyCode(keyCode);
}
}
if (handleKeyboardShortcutSingle(mKeyboardShortcutsHandler, keyCode, event, mMetaState))
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (handleKeyboardShortcutSingle(mKeyboardShortcutsHandler, keyCode, event, mKeyMetaState))
return true;
return isKeyboardShortcutHandled(mKeyboardShortcutsHandler, keyCode, event, mMetaState) || super.onKeyUp(keyCode, event);
return isKeyboardShortcutHandled(mKeyboardShortcutsHandler, keyCode, event, mKeyMetaState) || super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.isModifierKey(keyCode)) {
mMetaState |= KeyboardShortcutsHandler.getMetaStateForKeyCode(keyCode);
}
if (handleKeyboardShortcutRepeat(mKeyboardShortcutsHandler, keyCode, event.getRepeatCount(), event, mMetaState))
if (handleKeyboardShortcutRepeat(mKeyboardShortcutsHandler, keyCode, event.getRepeatCount(), event, mKeyMetaState))
return true;
return isKeyboardShortcutHandled(mKeyboardShortcutsHandler, keyCode, event, mMetaState) || super.onKeyDown(keyCode, event);
return isKeyboardShortcutHandled(mKeyboardShortcutsHandler, keyCode, event, mKeyMetaState) || super.onKeyDown(keyCode, event);
}
@Override
@ -246,4 +255,8 @@ public class BaseAppCompatActivity extends ThemedAppCompatActivity implements Co
mControlBarOffsetListeners.remove(listener);
}
public int getKeyMetaState() {
return mKeyMetaState;
}
}

View File

@ -16,6 +16,7 @@
package org.mariotaku.twidere.activity.support;
import android.Manifest;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
@ -53,6 +54,7 @@ import android.widget.ImageButton;
import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
@ -76,6 +78,7 @@ import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.PermissionUtils;
import org.mariotaku.twidere.util.SaveFileTask;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
@ -252,7 +255,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
setTitle(String.format("%d / %d", mViewPager.getCurrentItem() + 1, mPagerAdapter.getCount()));
}
public static class BaseImagePageFragment extends BaseSupportFragment
public static class BaseImagePageFragment extends AbsMediaPageFragment
implements DownloadListener, LoaderCallbacks<Result>, OnClickListener {
private SubsamplingScaleImageView mImageView;
@ -385,7 +388,8 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
startActivity(intent);
}
private void saveToGallery() {
@Override
protected void saveToGallery() {
if (mSaveFileTask != null && mSaveFileTask.getStatus() == Status.RUNNING) return;
final File file = mImageFile;
final boolean hasImage = file != null && file.exists();
@ -451,7 +455,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
return true;
}
case R.id.save: {
saveToGallery();
requestAndSaveToGallery();
return true;
}
case R.id.refresh: {
@ -568,7 +572,40 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
}
}
public static final class VideoPageFragment extends BaseSupportFragment
private static abstract class AbsMediaPageFragment extends BaseSupportFragment {
protected void requestAndSaveToGallery() {
if (PermissionUtils.hasPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
saveToGallery();
} else {
final String[] permissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};
} else {
permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
requestPermissions(permissions, REQUEST_REQUEST_PERMISSIONS);
}
}
protected abstract void saveToGallery();
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_REQUEST_PERMISSIONS: {
if (PermissionUtils.hasPermission(permissions, grantResults, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
saveToGallery();
} else {
Toast.makeText(getContext(), R.string.save_media_no_storage_permission_message, Toast.LENGTH_LONG).show();
}
return;
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public static final class VideoPageFragment extends AbsMediaPageFragment
implements VideoLoadingListener, OnPreparedListener, OnErrorListener, OnCompletionListener, OnClickListener {
private static final String[] SUPPORTED_VIDEO_TYPES;
@ -783,7 +820,8 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
return args.getParcelable(EXTRA_MEDIA);
}
private void saveToGallery() {
@Override
protected void saveToGallery() {
if (mSaveFileTask != null && mSaveFileTask.getStatus() == Status.RUNNING) return;
final File file = mVideoFile;
final Pair<String, String> urlAndType = mVideoUrlAndType;
@ -914,7 +952,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.save: {
saveToGallery();
requestAndSaveToGallery();
return true;
}
case R.id.refresh: {

View File

@ -120,7 +120,6 @@ import java.util.Locale;
import me.uucky.colorpicker.internal.EffectViewHelper;
import static org.mariotaku.twidere.util.Utils.buildDirectMessageConversationUri;
import static org.mariotaku.twidere.util.Utils.showOkMessage;
public class MessagesConversationFragment extends BaseSupportFragment implements
LoaderCallbacks<Cursor>, OnClickListener, OnItemSelectedListener, MenuButtonClickListener,
@ -503,7 +502,7 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
}
case R.id.copy: {
if (ClipboardUtils.setText(getActivity(), mSelectedDirectMessage.text_plain)) {
showOkMessage(getActivity(), R.string.text_copied, false);
Utils.showOkMessage(getActivity(), R.string.text_copied, false);
}
break;
}
@ -653,11 +652,16 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
final EditTextEnterHandler queryEnterHandler = EditTextEnterHandler.attach(mEditUserQuery, new EnterListener() {
@Override
public boolean shouldCallListener() {
return true;
final FragmentActivity activity = getActivity();
if (!(activity instanceof BaseAppCompatActivity)) return false;
return ((BaseAppCompatActivity) activity).getKeyMetaState() == 0;
}
@Override
public boolean onHitEnter() {
final FragmentActivity activity = getActivity();
if (!(activity instanceof BaseAppCompatActivity)) return false;
if (((BaseAppCompatActivity) activity).getKeyMetaState() != 0) return false;
final ParcelableCredentials account = (ParcelableCredentials) mAccountSpinner.getSelectedItem();
if (account == null) return false;
mEditText.setAccountId(account.account_id);

View File

@ -30,6 +30,7 @@ import android.util.Log;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.dagger.ApplicationModule;
import org.mariotaku.twidere.util.net.NetworkUsageUtils;
import edu.tsinghua.hotmobi.HotMobiLogger;
@ -49,6 +50,7 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons
Log.d(RECEIVER_LOGTAG, String.format("Received Broadcast %s", intent));
}
if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) return;
ApplicationModule.get(context).reloadConnectivitySettings();
startRefreshServiceIfNeeded(context);
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);

View File

@ -19,6 +19,10 @@
package org.mariotaku.twidere.util;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.v4.content.ContextCompat;
import org.apache.commons.lang3.ArrayUtils;
/**
@ -30,4 +34,12 @@ public class PermissionUtils {
if (idx != -1) return grantResults[idx];
return 0;
}
public static boolean hasPermission(Context context, String permission) {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
public static boolean hasPermission(String[] permissions, int[] grantResults, String permission) {
return getPermission(permissions, grantResults, permission) == PackageManager.PERMISSION_GRANTED;
}
}

View File

@ -139,7 +139,7 @@ public class TwitterAPIFactory implements TwidereConstants {
if (enableProxy) {
client.setProxy(getProxy(prefs));
} else {
client.setProxy(Proxy.NO_PROXY);
client.setProxy(null);
}
}

View File

@ -787,4 +787,8 @@
<string name="invalid_consumer_secret">Invalid consumer secret</string>
<string name="page_up">Page up</string>
<string name="page_down">Page down</string>
<string name="combined_notifications">Combined notifications</string>
<string name="combined_notifications_summary_on">Notifications will be grouped</string>
<string name="combined_notifications_summary_off">Notifications will be displayed separately</string>
<string name="save_media_no_storage_permission_message">Storage permission is needed to save media.</string>
</resources>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/notifications">
@ -8,7 +7,7 @@
android:key="cat_accounts"
android:title="@string/accounts"
app:switchDefault="true"
app:switchKey="notification"/>
app:switchKey="notification" />
<PreferenceCategory
android:key="cat_general"
@ -16,13 +15,20 @@
<org.mariotaku.twidere.preference.SilentNotificationsPreference
android:key="silent_notifications"
android:summary="@string/silent_notifications_summary"
android:title="@string/silent_notifications"/>
android:title="@string/silent_notifications" />
<org.mariotaku.twidere.preference.AutoFixCheckBoxPreference
android:defaultValue="false"
android:key="combined_notifications"
android:summaryOff="@string/combined_notifications_summary_off"
android:summaryOn="@string/combined_notifications_summary_on"
android:title="@string/combined_notifications" />
<org.mariotaku.twidere.preference.AutoFixCheckBoxPreference
android:defaultValue="false"
android:key="pebble_notifications"
android:summary="@string/pebble_notifications_summary"
android:title="@string/pebble_notifications"/>
android:title="@string/pebble_notifications" />
</PreferenceCategory>
</PreferenceScreen>