mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +01:00 
			
		
		
		
	Merge branch 'master' into 1130(j)
This commit is contained in:
		
						commit
						14686138df
					
				
					 64 changed files with 844 additions and 156 deletions
				
			
		|  | @ -3,6 +3,8 @@ package fr.free.nrw.commons; | |||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.customtabs.CustomTabsIntent; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
|  | @ -47,22 +49,29 @@ public class AboutActivity extends NavigationBaseActivity { | |||
|             intent.setPackage("com.facebook.katana"); | ||||
|             startActivity(intent); | ||||
|         } catch (Exception e) { | ||||
|             startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.facebook.com/" + "1921335171459985"))); | ||||
|             Utils.handleWebUrl(this,Uri.parse("https://www.facebook.com/" + "1921335171459985")); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.github_launch_icon) | ||||
|     public void launchGithub(View view) { | ||||
| 
 | ||||
|         Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/commons-app/apps-android-commons\\")); | ||||
|         startActivity(browserIntent); | ||||
|         Utils.handleWebUrl(this,Uri.parse("https://commons-app.github.io/\\")); | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.website_launch_icon) | ||||
|     public void launchWebsite(View view) { | ||||
| 
 | ||||
|         Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://commons-app.github.io/\\")); | ||||
|         startActivity(browserIntent); | ||||
|         Utils.handleWebUrl(this,Uri.parse("https://commons-app.github.io/\\")); | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.about_credits) | ||||
|     public void launchCredits(View view) { | ||||
|         Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/blob/master/CREDITS/\\")); | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.about_privacy_policy) | ||||
|     public void launchPrivacyPolicy(View view) { | ||||
|         Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\")); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,8 +1,12 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.customtabs.CustomTabsIntent; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| 
 | ||||
| import org.apache.commons.codec.binary.Hex; | ||||
| import org.apache.commons.codec.digest.DigestUtils; | ||||
|  | @ -11,6 +15,7 @@ import java.io.BufferedReader; | |||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.net.URL; | ||||
| import java.net.URLEncoder; | ||||
| import java.util.Locale; | ||||
| import java.util.regex.Matcher; | ||||
|  | @ -159,4 +164,15 @@ public class Utils { | |||
| 
 | ||||
|         return stringBuilder.toString(); | ||||
|     } | ||||
| 
 | ||||
|     public static void handleWebUrl(Context context,Uri url){ | ||||
|         CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); | ||||
|         builder.setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor)); | ||||
|         builder.setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor)); | ||||
|         builder.setExitAnimations(context, android.R.anim.slide_in_left, android.R.anim.slide_out_right); | ||||
|         CustomTabsIntent customTabsIntent = builder.build(); | ||||
|         customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         customTabsIntent.launchUrl(context, url); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import android.accounts.Account; | |||
| import android.accounts.AccountAuthenticatorActivity; | ||||
| import android.accounts.AccountAuthenticatorResponse; | ||||
| import android.accounts.AccountManager; | ||||
| import android.app.Activity; | ||||
| import android.app.ProgressDialog; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
|  | @ -17,10 +18,12 @@ import android.support.v4.content.ContextCompat; | |||
| import android.support.v7.app.AppCompatDelegate; | ||||
| import android.text.Editable; | ||||
| import android.text.TextWatcher; | ||||
| import android.util.Log; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.inputmethod.InputMethodManager; | ||||
| import android.widget.Button; | ||||
| import android.widget.EditText; | ||||
| import android.widget.TextView; | ||||
|  | @ -69,6 +72,7 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|     @BindView(R.id.loginTwoFactor) EditText twoFactorEdit; | ||||
|     @BindView(R.id.error_message_container) ViewGroup errorMessageContainer; | ||||
|     @BindView(R.id.error_message) TextView errorMessage; | ||||
|     @BindView(R.id.login_credentials) TextView loginCredentials; | ||||
|     @BindView(R.id.two_factor_container)TextInputLayout twoFactorContainer; | ||||
|     ProgressDialog progressDialog; | ||||
|     private AppCompatDelegate delegate; | ||||
|  | @ -91,14 +95,39 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|         ButterKnife.bind(this); | ||||
| 
 | ||||
|         usernameEdit.addTextChangedListener(textWatcher); | ||||
|         usernameEdit.setOnFocusChangeListener((v, hasFocus) -> { | ||||
|             if (!hasFocus) { | ||||
|                 hideKeyboard(v); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         passwordEdit.addTextChangedListener(textWatcher); | ||||
|         passwordEdit.setOnFocusChangeListener((v, hasFocus) -> { | ||||
|             if (!hasFocus) { | ||||
|                 hideKeyboard(v); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         twoFactorEdit.addTextChangedListener(textWatcher); | ||||
|         passwordEdit.setOnEditorActionListener(newLoginInputActionListener()); | ||||
| 
 | ||||
|         loginButton.setOnClickListener(view -> performLogin()); | ||||
|         signupButton.setOnClickListener(view -> signUp()); | ||||
| 
 | ||||
|         if(BuildConfig.FLAVOR == "beta"){ | ||||
|             loginCredentials.setText(getString(R.string.login_credential)); | ||||
|         } else { | ||||
|             loginCredentials.setVisibility(View.GONE); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public void hideKeyboard(View view) { | ||||
|         InputMethodManager inputMethodManager =(InputMethodManager)this.getSystemService(Activity.INPUT_METHOD_SERVICE); | ||||
|         inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|  |  | |||
|  | @ -1,17 +1,22 @@ | |||
| package fr.free.nrw.commons.category; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.widget.LinearLayoutManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.text.Editable; | ||||
| import android.text.TextUtils; | ||||
| import android.text.TextWatcher; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.inputmethod.InputMethodManager; | ||||
| import android.widget.EditText; | ||||
| import android.widget.ProgressBar; | ||||
| import android.widget.TextView; | ||||
|  | @ -37,6 +42,7 @@ import fr.free.nrw.commons.R; | |||
| import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.upload.MwVolleyApi; | ||||
| import fr.free.nrw.commons.upload.SingleUploadFragment; | ||||
| import fr.free.nrw.commons.utils.StringSortingUtils; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
|  | @ -72,6 +78,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { | |||
|     private OnCategoriesSaveHandler onCategoriesSaveHandler; | ||||
|     private HashMap<String, ArrayList<String>> categoriesCache; | ||||
|     private List<CategoryItem> selectedCategories = new ArrayList<>(); | ||||
|     private TitleTextWatcher textWatcher = new TitleTextWatcher(); | ||||
| 
 | ||||
|     private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> { | ||||
|         if (item.isSelected()) { | ||||
|  | @ -102,6 +109,15 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { | |||
|         categoriesAdapter = adapterFactory.create(items); | ||||
|         categoriesList.setAdapter(categoriesAdapter); | ||||
| 
 | ||||
| 
 | ||||
|         categoriesFilter.addTextChangedListener(textWatcher); | ||||
| 
 | ||||
|         categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> { | ||||
|             if (!hasFocus) { | ||||
|                 hideKeyboard(v); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         RxTextView.textChanges(categoriesFilter) | ||||
|                 .takeUntil(RxView.detaches(categoriesFilter)) | ||||
|                 .debounce(500, TimeUnit.MILLISECONDS) | ||||
|  | @ -110,6 +126,18 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { | |||
|         return rootView; | ||||
|     } | ||||
| 
 | ||||
|     public void hideKeyboard(View view) { | ||||
|         InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); | ||||
|         inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onDestroyView() { | ||||
|         categoriesFilter.removeTextChangedListener(textWatcher); | ||||
|         super.onDestroyView(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         menu.clear(); | ||||
|  | @ -351,4 +379,21 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { | |||
|                 .create() | ||||
|                 .show(); | ||||
|     } | ||||
| 
 | ||||
|     private class TitleTextWatcher implements TextWatcher { | ||||
|         @Override | ||||
|         public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void afterTextChanged(Editable editable) { | ||||
|             if (getActivity() != null) { | ||||
|                 getActivity().invalidateOptionsMenu(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -197,6 +197,10 @@ public class Contribution extends Media { | |||
|         this.localUri = localUri; | ||||
|     } | ||||
| 
 | ||||
|     public void setDecimalCoords(String decimalCoords) { | ||||
|         this.decimalCoords = decimalCoords; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private String licenseTemplateFor(String license) { | ||||
|         switch (license) { | ||||
|  | @ -215,6 +219,7 @@ public class Contribution extends Media { | |||
|             case Prefs.Licenses.CC_BY_SA: | ||||
|                 return "{{self|cc-by-sa-3.0}}"; | ||||
|         } | ||||
| 
 | ||||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import android.preference.PreferenceManager; | |||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v4.app.FragmentTransaction; | ||||
| import android.support.v4.widget.SwipeRefreshLayout; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
|  | @ -60,7 +61,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|     private NearbyActivityMode viewMode; | ||||
|     private Disposable placesDisposable; | ||||
|     private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed | ||||
| 
 | ||||
|     @BindView(R.id.swipe_container) SwipeRefreshLayout swipeLayout; | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|  | @ -70,6 +71,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         bundle = new Bundle(); | ||||
|         initDrawer(); | ||||
|         initViewState(); | ||||
|         swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { | ||||
|             @Override | ||||
|             public void onRefresh() { | ||||
|                 lockNearbyView(false); | ||||
|                 refreshView(true); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void initViewState() { | ||||
|  | @ -309,7 +317,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         } else { | ||||
|             setListFragment(); | ||||
|         } | ||||
| 
 | ||||
|         swipeLayout.setRefreshing(false); | ||||
|         hideProgressBar(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,9 @@ package fr.free.nrw.commons.nearby; | |||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.customtabs.CustomTabsIntent; | ||||
| import android.support.v4.app.FragmentActivity; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v7.widget.PopupMenu; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MenuItem; | ||||
|  | @ -17,6 +19,7 @@ import butterknife.ButterKnife; | |||
| import butterknife.OnClick; | ||||
| import butterknife.Unbinder; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.ui.widget.OverlayDialog; | ||||
| import fr.free.nrw.commons.utils.DialogUtil; | ||||
|  | @ -141,8 +144,7 @@ public class NearbyInfoDialog extends OverlayDialog { | |||
|     } | ||||
| 
 | ||||
|     private void openWebView(Uri link) { | ||||
|         Intent browserIntent = new Intent(Intent.ACTION_VIEW, link); | ||||
|         startActivity(browserIntent); | ||||
|         Utils.handleWebUrl(getContext(),link); | ||||
|     } | ||||
| 
 | ||||
|     @OnClick(R.id.emptyLayout) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import android.support.v7.widget.RecyclerView; | |||
| 
 | ||||
| import com.pedrogomez.renderers.RVRendererAdapter; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
|  | @ -58,6 +59,7 @@ public class NotificationActivity extends NavigationBaseActivity { | |||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe(notificationList -> { | ||||
|                     Collections.reverse(notificationList); | ||||
|                     Timber.d("Number of notifications is %d", notificationList.size()); | ||||
|                     setAdapter(notificationList); | ||||
|                 }, throwable -> Timber.e(throwable, "Error occurred while loading notifications")); | ||||
|  |  | |||
|  | @ -0,0 +1,59 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.graphics.BitmapRegionDecoder; | ||||
| import android.net.Uri; | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.utils.ImageUtils; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * Created by bluesir9 on 16/9/17. | ||||
|  * | ||||
|  * <p>Responsible for checking if the picture that the user is trying to upload is useful or not. Will attempt to filter | ||||
|  * away completely black,fuzzy/blurry pictures(for now). | ||||
|  * | ||||
|  * <p>todo: Detect selfies? | ||||
|  */ | ||||
| 
 | ||||
| public class DetectUnwantedPicturesAsync extends AsyncTask<Void, Void, ImageUtils.Result> { | ||||
| 
 | ||||
|     interface Callback { | ||||
|         void onResult(ImageUtils.Result result); | ||||
|     } | ||||
| 
 | ||||
|     private final Callback callback; | ||||
|     private final String imageMediaFilePath; | ||||
| 
 | ||||
|     DetectUnwantedPicturesAsync(String imageMediaFilePath, Callback callback) { | ||||
|         this.callback = callback; | ||||
|         this.imageMediaFilePath = imageMediaFilePath; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ImageUtils.Result doInBackground(Void... voids) { | ||||
|         try { | ||||
|             Timber.d("FilePath: " + imageMediaFilePath); | ||||
|             if (imageMediaFilePath == null) { | ||||
|                 return ImageUtils.Result.IMAGE_OK; | ||||
|             } | ||||
| 
 | ||||
|             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(imageMediaFilePath,false); | ||||
| 
 | ||||
|             return ImageUtils.checkIfImageIsTooDark(decoder); | ||||
|         } catch (IOException ioe) { | ||||
|             Timber.e(ioe, "IO Exception"); | ||||
|             return ImageUtils.Result.IMAGE_OK; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(ImageUtils.Result result) { | ||||
|         super.onPostExecute(result); | ||||
|         //callback to UI so that it can take necessary decision based on the result obtained | ||||
|         callback.onResult(result); | ||||
|     } | ||||
| } | ||||
|  | @ -3,20 +3,25 @@ package fr.free.nrw.commons.upload; | |||
| import android.annotation.SuppressLint; | ||||
| import android.content.ContentUris; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Environment; | ||||
| import android.os.ParcelFileDescriptor; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.provider.DocumentsContract; | ||||
| import android.provider.MediaStore; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileDescriptor; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.channels.FileChannel; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -36,6 +41,7 @@ public class FileUtils { | |||
|     @Nullable | ||||
|     public static String getPath(Context context, Uri uri) { | ||||
| 
 | ||||
|         String returnPath = null; | ||||
|         final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; | ||||
| 
 | ||||
|         // DocumentProvider | ||||
|  | @ -47,7 +53,7 @@ public class FileUtils { | |||
|                 final String type = split[0]; | ||||
| 
 | ||||
|                 if ("primary".equalsIgnoreCase(type)) { | ||||
|                     return Environment.getExternalStorageDirectory() + "/" + split[1]; | ||||
|                     returnPath = Environment.getExternalStorageDirectory() + "/" + split[1]; | ||||
|                 } | ||||
|             } else if (isDownloadsDocument(uri))  { // DownloadsProvider | ||||
| 
 | ||||
|  | @ -55,8 +61,9 @@ public class FileUtils { | |||
|                 final Uri contentUri = ContentUris.withAppendedId( | ||||
|                         Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); | ||||
| 
 | ||||
|                 return getDataColumn(context, contentUri, null, null); | ||||
|                 returnPath =  getDataColumn(context, contentUri, null, null); | ||||
|             } else if (isMediaDocument(uri)) { // MediaProvider | ||||
| 
 | ||||
|                 final String docId = DocumentsContract.getDocumentId(uri); | ||||
|                 final String[] split = docId.split(":"); | ||||
|                 final String type = split[0]; | ||||
|  | @ -81,16 +88,55 @@ public class FileUtils { | |||
|                         split[1] | ||||
|                 }; | ||||
| 
 | ||||
|                 return getDataColumn(context, contentUri, selection, selectionArgs); | ||||
|                 returnPath = getDataColumn(context, contentUri, selection, selectionArgs); | ||||
|             } | ||||
|         } | ||||
|         // MediaStore (and general) | ||||
|         else if ("content".equalsIgnoreCase(uri.getScheme())) { | ||||
|             return getDataColumn(context, uri, null, null); | ||||
|             returnPath = getDataColumn(context, uri, null, null); | ||||
|         } | ||||
|         // File | ||||
|         else if ("file".equalsIgnoreCase(uri.getScheme())) { | ||||
|             return uri.getPath(); | ||||
|             returnPath = uri.getPath(); | ||||
|         } | ||||
| 
 | ||||
|         if(returnPath == null) { | ||||
|             //fetching path may fail depending on the source URI and all hope is lost | ||||
|             //so we will create and use a copy of the file, which seems to work | ||||
|             String copyPath = null; | ||||
|             try { | ||||
|                 ParcelFileDescriptor descriptor | ||||
|                         = context.getContentResolver().openFileDescriptor(uri, "r"); | ||||
|                 if (descriptor != null) { | ||||
| 
 | ||||
|                     SharedPreferences sharedPref = PreferenceManager | ||||
|                             .getDefaultSharedPreferences(context); | ||||
|                     boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true); | ||||
|                     if (useExtStorage) { | ||||
|                         copyPath = Environment.getExternalStorageDirectory().toString() | ||||
|                                 + "/CommonsApp/" + new Date().getTime() + ".jpg"; | ||||
|                         File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp"); | ||||
|                         newFile.mkdir(); | ||||
|                         FileUtils.copy( | ||||
|                                 descriptor.getFileDescriptor(), | ||||
|                                 copyPath); | ||||
|                         Timber.d("Filepath (copied): %s", copyPath); | ||||
|                         return copyPath; | ||||
|                     } | ||||
|                     copyPath = context.getCacheDir().getAbsolutePath() | ||||
|                             + "/" + new Date().getTime() + ".jpg"; | ||||
|                     FileUtils.copy( | ||||
|                             descriptor.getFileDescriptor(), | ||||
|                             copyPath); | ||||
|                     Timber.d("Filepath (copied): %s", copyPath); | ||||
|                     return copyPath; | ||||
|                 } | ||||
|             } catch (IOException e) { | ||||
|                 Timber.w(e, "Error in file " + copyPath); | ||||
|                 return null; | ||||
|             } | ||||
|         } else { | ||||
|             return returnPath; | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|  | @ -111,7 +157,7 @@ public class FileUtils { | |||
|                                        String[] selectionArgs) { | ||||
| 
 | ||||
|         Cursor cursor = null; | ||||
|         final String column = "_data"; | ||||
|         final String column = MediaStore.Images.ImageColumns.DATA; | ||||
|         final String[] projection = { | ||||
|                 column | ||||
|         }; | ||||
|  |  | |||
|  | @ -11,7 +11,9 @@ import android.database.DataSetObserver; | |||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.ParcelFileDescriptor; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.app.FragmentManager; | ||||
| import android.support.v4.content.ContextCompat; | ||||
|  | @ -21,6 +23,7 @@ import android.view.inputmethod.InputMethodManager; | |||
| import android.widget.AdapterView; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import java.io.FileNotFoundException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
|  | @ -69,6 +72,8 @@ public class MultipleShareActivity extends AuthenticatedActivity | |||
|     private MediaDetailPagerFragment mediaDetails; | ||||
|     private CategorizationFragment categorizationFragment; | ||||
| 
 | ||||
|     private boolean locationPermitted = false; | ||||
| 
 | ||||
|     @Override | ||||
|     public Media getMediaAtPosition(int i) { | ||||
|         return photosList.get(i); | ||||
|  | @ -213,6 +218,14 @@ public class MultipleShareActivity extends AuthenticatedActivity | |||
| 
 | ||||
|         getSupportFragmentManager().addOnBackStackChangedListener(this); | ||||
|         requestAuthToken(); | ||||
| 
 | ||||
|         //TODO: 15/10/17 should location permission be explicitly requested if not provided? | ||||
|         //check if location permission is enabled | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { | ||||
|                 locationPermitted = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -258,6 +271,11 @@ public class MultipleShareActivity extends AuthenticatedActivity | |||
|                     up.setTag("sequence", i); | ||||
|                     up.setSource(Contribution.SOURCE_EXTERNAL); | ||||
|                     up.setMultiple(true); | ||||
|                     String imageGpsCoordinates = extractImageGpsData(uri); | ||||
|                     if (imageGpsCoordinates != null) { | ||||
|                         Timber.d("GPS data for image found!"); | ||||
|                         up.setDecimalCoords(imageGpsCoordinates); | ||||
|                     } | ||||
|                     photosList.add(up); | ||||
|                 } | ||||
|             } | ||||
|  | @ -287,4 +305,46 @@ public class MultipleShareActivity extends AuthenticatedActivity | |||
|         getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Will attempt to extract the gps coordinates using exif data or by using the current | ||||
|      * location if available for the image who's imageUri has been provided. | ||||
|      * @param imageUri The uri of the image who's GPS coordinates data we wish to extract | ||||
|      * @return GPS coordinates as a String as is returned by {@link GPSExtractor} | ||||
|      */ | ||||
|     @Nullable | ||||
|     private String extractImageGpsData(Uri imageUri) { | ||||
|         Timber.d("Entering extractImagesGpsData"); | ||||
| 
 | ||||
|         if (imageUri == null) { | ||||
|             //now why would you do that??? | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         GPSExtractor gpsExtractor = null; | ||||
| 
 | ||||
|         try { | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|                 ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(imageUri,"r"); | ||||
|                 if (fd != null) { | ||||
|                     gpsExtractor = new GPSExtractor(fd.getFileDescriptor(),this,prefs); | ||||
|                 } | ||||
|             } else { | ||||
|                 String filePath = FileUtils.getPath(this,imageUri); | ||||
|                 if (filePath != null) { | ||||
|                     gpsExtractor = new GPSExtractor(filePath,this,prefs); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (gpsExtractor != null) { | ||||
|                 //get image coordinates from exif data or user location | ||||
|                 return gpsExtractor.getCoords(locationPermitted); | ||||
|             } | ||||
| 
 | ||||
|         } catch (FileNotFoundException fnfe) { | ||||
|             Timber.w(fnfe); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
| import android.graphics.Point; | ||||
| import android.net.Uri; | ||||
|  | @ -10,6 +11,7 @@ import android.text.Editable; | |||
| import android.text.TextUtils; | ||||
| import android.text.TextWatcher; | ||||
| import android.util.DisplayMetrics; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
|  | @ -177,9 +179,21 @@ public class MultipleUploadListFragment extends Fragment { | |||
|         photosGrid.setColumnWidth(photoSize.x); | ||||
| 
 | ||||
|         baseTitle.addTextChangedListener(textWatcher); | ||||
| 
 | ||||
|         baseTitle.setOnFocusChangeListener((v, hasFocus) -> { | ||||
|             if (!hasFocus) { | ||||
|                 hideKeyboard(v); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         return view; | ||||
|     } | ||||
| 
 | ||||
|     public void hideKeyboard(View view) { | ||||
|         InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); | ||||
|         inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onDestroyView() { | ||||
|         baseTitle.removeTextChangedListener(textWatcher); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import android.Manifest; | |||
| import android.app.Activity; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
|  | @ -20,6 +21,7 @@ import android.support.graphics.drawable.VectorDrawableCompat; | |||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.app.FragmentManager; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
|  | @ -50,11 +52,14 @@ import fr.free.nrw.commons.caching.CacheController; | |||
| import fr.free.nrw.commons.category.CategorizationFragment; | ||||
| import fr.free.nrw.commons.category.OnCategoriesSaveHandler; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.modifications.CategoryModifier; | ||||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequenceDao; | ||||
| import fr.free.nrw.commons.modifications.TemplateRemoveModifier; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.utils.ImageUtils; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -290,7 +295,7 @@ public class ShareActivity | |||
|                         REQUEST_PERM_ON_CREATE_LOCATION); | ||||
|             } | ||||
|         } | ||||
|         performPreuploadProcessingOfFile(); | ||||
|         performPreUploadProcessingOfFile(); | ||||
| 
 | ||||
| 
 | ||||
|         SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView"); | ||||
|  | @ -314,7 +319,7 @@ public class ShareActivity | |||
|                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     backgroundImageView.setImageURI(mediaUri); | ||||
|                     storagePermitted = true; | ||||
|                     performPreuploadProcessingOfFile(); | ||||
|                     performPreUploadProcessingOfFile(); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|  | @ -322,7 +327,7 @@ public class ShareActivity | |||
|                 if (grantResults.length >= 1 | ||||
|                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     locationPermitted = true; | ||||
|                     performPreuploadProcessingOfFile(); | ||||
|                     performPreUploadProcessingOfFile(); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|  | @ -331,12 +336,12 @@ public class ShareActivity | |||
|                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     backgroundImageView.setImageURI(mediaUri); | ||||
|                     storagePermitted = true; | ||||
|                     performPreuploadProcessingOfFile(); | ||||
|                     performPreUploadProcessingOfFile(); | ||||
|                 } | ||||
|                 if (grantResults.length >= 2 | ||||
|                         && grantResults[1] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     locationPermitted = true; | ||||
|                     performPreuploadProcessingOfFile(); | ||||
|                     performPreUploadProcessingOfFile(); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|  | @ -347,7 +352,7 @@ public class ShareActivity | |||
|                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     //It is OK to call this at both (1) and (4) because if perm had been granted at | ||||
|                     //snackbar, user should not be prompted at submit button | ||||
|                     performPreuploadProcessingOfFile(); | ||||
|                     performPreUploadProcessingOfFile(); | ||||
| 
 | ||||
|                     //Uploading only begins if storage permission granted from arrow icon | ||||
|                     uploadBegins(); | ||||
|  | @ -358,7 +363,7 @@ public class ShareActivity | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void performPreuploadProcessingOfFile() { | ||||
|     private void performPreUploadProcessingOfFile() { | ||||
|         if (!useNewPermissions || storagePermitted) { | ||||
|             if (!duplicateCheckPassed) { | ||||
|                 //Test SHA1 of image to see if it matches SHA1 of a file on Commons | ||||
|  | @ -373,7 +378,17 @@ public class ShareActivity | |||
|                                 Timber.d("%s duplicate check: %s", mediaUri.toString(), result); | ||||
|                                 duplicateCheckPassed = (result == DUPLICATE_PROCEED | ||||
|                                         || result == NO_DUPLICATE); | ||||
|                             }, mwApi); | ||||
|                                 /* | ||||
|                                  TODO: 16/9/17 should we run DetectUnwantedPicturesAsync if DUPLICATE_PROCEED is returned? Since that means | ||||
|                                  we are processing images that are already on server???... | ||||
|                                 */ | ||||
| 
 | ||||
|                                 if (duplicateCheckPassed) { | ||||
|                                     //image can be uploaded, so now check if its a useless picture or not | ||||
|                                     performUnwantedPictureDetectionProcess(); | ||||
|                                 } | ||||
| 
 | ||||
|                             },mwApi); | ||||
|                     fileAsyncTask.execute(); | ||||
|                 } catch (IOException e) { | ||||
|                     Timber.d(e, "IO Exception: "); | ||||
|  | @ -387,6 +402,37 @@ public class ShareActivity | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void performUnwantedPictureDetectionProcess() { | ||||
|         String imageMediaFilePath = FileUtils.getPath(this,mediaUri); | ||||
|         DetectUnwantedPicturesAsync detectUnwantedPicturesAsync = new DetectUnwantedPicturesAsync(imageMediaFilePath, result -> { | ||||
| 
 | ||||
|             if (result != ImageUtils.Result.IMAGE_OK) { | ||||
|                 //show appropriate error message | ||||
|                 String errorMessage = result == ImageUtils.Result.IMAGE_DARK ? getString(R.string.upload_image_too_dark) : getString(R.string.upload_image_blurry); | ||||
|                 AlertDialog.Builder errorDialogBuilder = new AlertDialog.Builder(this); | ||||
|                 errorDialogBuilder.setMessage(errorMessage); | ||||
|                 errorDialogBuilder.setTitle(getString(R.string.warning)); | ||||
|                 errorDialogBuilder.setPositiveButton(getString(R.string.no), (dialogInterface, i) -> { | ||||
|                     //user does not wish to upload the picture, take them back to ContributionsActivity | ||||
|                     Intent intent = new Intent(ShareActivity.this, ContributionsActivity.class); | ||||
|                     dialogInterface.dismiss(); | ||||
|                     startActivity(intent); | ||||
|                 }); | ||||
|                 errorDialogBuilder.setNegativeButton(getString(R.string.yes), (dialogInterface, i) -> { | ||||
|                     //user wishes to go ahead with the upload of this picture, just dismiss this dialog | ||||
|                     dialogInterface.dismiss(); | ||||
|                 }); | ||||
| 
 | ||||
|                 AlertDialog errorDialog = errorDialogBuilder.create(); | ||||
|                 if (!isFinishing()) { | ||||
|                     errorDialog.show(); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         detectUnwantedPicturesAsync.execute(); | ||||
|     } | ||||
| 
 | ||||
|     private Snackbar requestPermissionUsingSnackBar(String rationale, | ||||
|                                                     final String[] perms, | ||||
|                                                     final int code) { | ||||
|  |  | |||
							
								
								
									
										135
									
								
								app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| package fr.free.nrw.commons.utils; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapRegionDecoder; | ||||
| import android.graphics.Color; | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * Created by bluesir9 on 3/10/17. | ||||
|  */ | ||||
| 
 | ||||
| public class ImageUtils { | ||||
|     //atleast 50% of the image in question should be considered dark for the entire image to be dark | ||||
|     private static final double MINIMUM_DARKNESS_FACTOR = 0.50; | ||||
|     //atleast 50% of the image in question should be considered blurry for the entire image to be blurry | ||||
|     private static final double MINIMUM_BLURRYNESS_FACTOR = 0.50; | ||||
|     private static final int LAPLACIAN_VARIANCE_THRESHOLD = 70; | ||||
| 
 | ||||
|     public enum Result { | ||||
|         IMAGE_DARK, | ||||
|         IMAGE_OK | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * BitmapRegionDecoder allows us to process a large bitmap by breaking it down into smaller rectangles. The rectangles | ||||
|      * are obtained by setting an initial width, height and start position of the rectangle as a factor of the width and | ||||
|      * height of the original bitmap and then manipulating the width, height and position to loop over the entire original | ||||
|      * bitmap. Each individual rectangle is independently processed to check if its too dark. Based on | ||||
|      * the factor of "bright enough" individual rectangles amongst the total rectangles into which the image | ||||
|      * was divided, we will declare the image as wanted/unwanted | ||||
|      * | ||||
|      * @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process | ||||
|      * @return Result.IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null | ||||
|      *         Result.IMAGE_DARK if image is too dark | ||||
|      */ | ||||
|     public static Result checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) { | ||||
|         if (bitmapRegionDecoder == null) { | ||||
|             Timber.e("Expected bitmapRegionDecoder was null"); | ||||
|             return Result.IMAGE_OK; | ||||
|         } | ||||
| 
 | ||||
|         int loadImageHeight = bitmapRegionDecoder.getHeight(); | ||||
|         int loadImageWidth = bitmapRegionDecoder.getWidth(); | ||||
| 
 | ||||
|         int checkImageTopPosition = 0; | ||||
|         int checkImageBottomPosition = loadImageHeight / 10; | ||||
|         int checkImageLeftPosition = 0; | ||||
|         int checkImageRightPosition = loadImageWidth / 10; | ||||
| 
 | ||||
|         int totalDividedRectangles = 0; | ||||
|         int numberOfDarkRectangles = 0; | ||||
| 
 | ||||
|         while ((checkImageRightPosition <= loadImageWidth) && (checkImageLeftPosition < checkImageRightPosition)) { | ||||
|             while ((checkImageBottomPosition <= loadImageHeight) && (checkImageTopPosition < checkImageBottomPosition)) { | ||||
|                 Timber.d("left: " + checkImageLeftPosition + " right: " + checkImageRightPosition + " top: " + checkImageTopPosition + " bottom: " + checkImageBottomPosition); | ||||
| 
 | ||||
|                 Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition,checkImageRightPosition,checkImageBottomPosition); | ||||
|                 totalDividedRectangles++; | ||||
| 
 | ||||
|                 Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null); | ||||
| 
 | ||||
|                 if (checkIfImageIsDark(processBitmap)) { | ||||
|                     numberOfDarkRectangles++; | ||||
|                 } | ||||
| 
 | ||||
|                 checkImageTopPosition = checkImageBottomPosition; | ||||
|                 checkImageBottomPosition += (checkImageBottomPosition < (loadImageHeight - checkImageBottomPosition)) ? checkImageBottomPosition : (loadImageHeight - checkImageBottomPosition); | ||||
|             } | ||||
| 
 | ||||
|             checkImageTopPosition = 0; //reset to start | ||||
|             checkImageBottomPosition = loadImageHeight / 10; //reset to start | ||||
|             checkImageLeftPosition = checkImageRightPosition; | ||||
|             checkImageRightPosition += (checkImageRightPosition < (loadImageWidth - checkImageRightPosition)) ? checkImageRightPosition : (loadImageWidth - checkImageRightPosition); | ||||
|         } | ||||
| 
 | ||||
|         Timber.d("dark rectangles count = " + numberOfDarkRectangles + ", total rectangles count = " + totalDividedRectangles); | ||||
| 
 | ||||
|         if (numberOfDarkRectangles > totalDividedRectangles * MINIMUM_DARKNESS_FACTOR) { | ||||
|             return Result.IMAGE_DARK; | ||||
|         } | ||||
| 
 | ||||
|         return Result.IMAGE_OK; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Pulls the pixels into an array and then runs through it while checking the brightness of each pixel. | ||||
|      * The calculation of brightness of each pixel is done by extracting the RGB constituents of the pixel | ||||
|      * and then applying the formula to calculate its "Luminance". If this brightness value is less than | ||||
|      * 50 then the pixel is considered to be dark. Based on the MINIMUM_DARKNESS_FACTOR if enough pixels | ||||
|      * are dark then the entire bitmap is considered to be dark. | ||||
|      * | ||||
|      * <p>For more information on this brightness/darkness calculation technique refer the accepted answer | ||||
|      * on this -> https://stackoverflow.com/questions/35914461/how-to-detect-dark-photos-in-android/35914745 | ||||
|      * SO question and follow the trail. | ||||
|      * | ||||
|      * @param bitmap The bitmap that needs to be checked. | ||||
|      * @return true if bitmap is dark or null, false if bitmap is bright | ||||
|      */ | ||||
|     private static boolean checkIfImageIsDark(Bitmap bitmap) { | ||||
|         if (bitmap == null) { | ||||
|             Timber.e("Expected bitmap was null"); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         int bitmapWidth = bitmap.getWidth(); | ||||
|         int bitmapHeight = bitmap.getHeight(); | ||||
| 
 | ||||
|         int allPixelsCount = bitmapWidth * bitmapHeight; | ||||
|         int[] bitmapPixels = new int[allPixelsCount]; | ||||
| 
 | ||||
|         bitmap.getPixels(bitmapPixels,0,bitmapWidth,0,0,bitmapWidth,bitmapHeight); | ||||
|         boolean isImageDark = false; | ||||
|         int darkPixelsCount = 0; | ||||
| 
 | ||||
|         for (int pixel : bitmapPixels) { | ||||
|             int r = Color.red(pixel); | ||||
|             int g = Color.green(pixel); | ||||
|             int b = Color.blue(pixel); | ||||
| 
 | ||||
|             int brightness = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b); | ||||
|             if (brightness < 50) { | ||||
|                 //pixel is dark | ||||
|                 darkPixelsCount++; | ||||
|                 if (darkPixelsCount > allPixelsCount * MINIMUM_DARKNESS_FACTOR) { | ||||
|                     isImageDark = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return isImageDark; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Hassan
						Hassan