├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── chromadoze_icon-web.png │ ├── java │ ├── com │ │ └── mobeta │ │ │ └── android │ │ │ └── dslv │ │ │ ├── DragSortController.java │ │ │ ├── DragSortCursorAdapter.java │ │ │ ├── DragSortItemView.java │ │ │ ├── DragSortItemViewCheckable.java │ │ │ ├── DragSortListView.java │ │ │ ├── ResourceDragSortCursorAdapter.java │ │ │ ├── SimpleDragSortCursorAdapter.java │ │ │ └── SimpleFloatViewManager.java │ ├── net │ │ └── pmarks │ │ │ └── chromadoze │ │ │ ├── AboutFragment.java │ │ │ ├── AudioFocusHelper.java │ │ │ ├── AudioParams.java │ │ │ ├── CheckableLinearLayout.java │ │ │ ├── ChromaDoze.java │ │ │ ├── EqualizerView.java │ │ │ ├── EqualizerViewLite.java │ │ │ ├── FragmentIndex.java │ │ │ ├── MainFragment.java │ │ │ ├── MemoryArrayAdapter.java │ │ │ ├── MemoryFragment.java │ │ │ ├── NoiseService.java │ │ │ ├── OptionsFragment.java │ │ │ ├── Phonon.java │ │ │ ├── PhononMutable.java │ │ │ ├── SampleGenerator.java │ │ │ ├── SampleGeneratorState.java │ │ │ ├── SampleShuffler.java │ │ │ ├── SpectrumData.java │ │ │ ├── TheBackupAgent.java │ │ │ ├── TrackedPosition.java │ │ │ ├── UIState.java │ │ │ └── XORShiftRandom.java │ └── org │ │ └── jtransforms │ │ ├── dct │ │ └── FloatDCT_1D.java │ │ └── utils │ │ └── CommonUtils.java │ └── res │ ├── drawable-hdpi-v11 │ └── ic_stat_bars.png │ ├── drawable-hdpi-v9 │ └── ic_stat_bars.png │ ├── drawable-hdpi │ ├── action_lock.png │ ├── action_unlock.png │ ├── av_play.png │ ├── av_stop.png │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ ├── grip_dots.png │ ├── ic_menu_save_disabled.png │ ├── ic_menu_save_normal.png │ └── ic_stat_bars.png │ ├── drawable-ldpi-v11 │ └── ic_stat_bars.png │ ├── drawable-ldpi-v9 │ └── ic_stat_bars.png │ ├── drawable-ldpi │ ├── wave_amplitude.png │ └── wave_period.png │ ├── drawable-mdpi-v11 │ └── ic_stat_bars.png │ ├── drawable-mdpi-v9 │ └── ic_stat_bars.png │ ├── drawable-mdpi │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ ├── ic_stat_bars.png │ └── toolbar_icon.png │ ├── drawable-xhdpi │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ └── toolbar_icon.png │ ├── drawable │ ├── btn_default_holo_dark.xml │ ├── ic_menu_save.xml │ └── spectrum.png │ ├── layout-v11 │ └── notification_with_stop_button.xml │ ├── layout │ ├── about_fragment.xml │ ├── main.xml │ ├── main_fragment.xml │ ├── memory_list.xml │ ├── memory_list_divider.xml │ ├── memory_list_item.xml │ ├── memory_list_item_common.xml │ ├── memory_list_item_top.xml │ ├── options_fragment.xml │ └── spinner_title.xml │ ├── mipmap-anydpi-v26 │ ├── chromadoze_icon.xml │ └── chromadoze_icon_round.xml │ ├── mipmap-hdpi │ ├── chromadoze_icon.png │ ├── chromadoze_icon_background.png │ ├── chromadoze_icon_foreground.png │ └── chromadoze_icon_round.png │ ├── mipmap-mdpi │ ├── chromadoze_icon.png │ ├── chromadoze_icon_background.png │ ├── chromadoze_icon_foreground.png │ └── chromadoze_icon_round.png │ ├── mipmap-xhdpi │ ├── chromadoze_icon.png │ ├── chromadoze_icon_background.png │ ├── chromadoze_icon_foreground.png │ └── chromadoze_icon_round.png │ └── values │ ├── colors.xml │ ├── dslv_attrs.xml │ ├── ids.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── misc ├── bars.xcf ├── chromadoze-feature-1024x500.xcf.bz2 ├── chromadoze-icon-48.xcf ├── chromadoze-icon-512.xcf.bz2 ├── chromadoze-icon-96.xcf ├── chromadoze-icon-plain-48.xcf ├── importing-dslv.txt ├── privacy_policy.txt ├── readme-android-studio.txt ├── screenshot1.png ├── screenshot2.png └── screenshot3.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .DS_Store 4 | 5 | /.idea 6 | build/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chroma Doze is an Android app that dynamically generates white/colored noise according to a frequency/amplitude spectrum sketched by the user. 2 | 3 | It is intended to be used as a sleep sound generator. It provides rapid feedback to adjustments in the spectrum, and is designed to minimize CPU usage in the steady state. 4 | 5 | It works by running shaped white noise through an Inverse Discrete Cosine Transform, generating a few megabytes of distinct audio blocks. The steady-state behavior selects blocks at random, and smoothly crossfades between them. 6 | 7 | Here's its [page on Google Play](https://play.google.com/store/apps/details?id=net.pmarks.chromadoze). 8 | 9 | Screenshots: 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | 6 | defaultConfig { 7 | applicationId 'net.pmarks.chromadoze' 8 | minSdkVersion 14 9 | targetSdkVersion 34 10 | } 11 | 12 | buildTypes { 13 | release { 14 | minifyEnabled true 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 16 | } 17 | } 18 | namespace 'net.pmarks.chromadoze' 19 | } 20 | 21 | dependencies { 22 | implementation 'androidx.appcompat:appcompat:1.6.1' 23 | } 24 | -------------------------------------------------------------------------------- /app/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/chromadoze_icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/chromadoze_icon-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/DragSortController.java: -------------------------------------------------------------------------------- 1 | package com.mobeta.android.dslv; 2 | 3 | import android.graphics.Point; 4 | import android.view.GestureDetector; 5 | import android.view.HapticFeedbackConstants; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.ViewConfiguration; 9 | import android.widget.AdapterView; 10 | 11 | /** 12 | * Class that starts and stops item drags on a {@link DragSortListView} based on 13 | * touch gestures. This class also inherits from {@link SimpleFloatViewManager}, 14 | * which provides basic float View creation. 15 | * 16 | * An instance of this class is meant to be passed to the methods 17 | * {@link DragSortListView#setTouchListener()} and 18 | * {@link DragSortListView#setFloatViewManager()} of your 19 | * {@link DragSortListView} instance. 20 | */ 21 | public class DragSortController extends SimpleFloatViewManager implements 22 | View.OnTouchListener, GestureDetector.OnGestureListener { 23 | 24 | /** 25 | * Drag init mode enum. 26 | */ 27 | public static final int ON_DOWN = 0; 28 | public static final int ON_DRAG = 1; 29 | public static final int ON_LONG_PRESS = 2; 30 | 31 | private int mDragInitMode = ON_DOWN; 32 | 33 | private boolean mSortEnabled = true; 34 | 35 | /** 36 | * Remove mode enum. 37 | */ 38 | public static final int CLICK_REMOVE = 0; 39 | public static final int FLING_REMOVE = 1; 40 | 41 | /** 42 | * The current remove mode. 43 | */ 44 | private int mRemoveMode; 45 | 46 | private boolean mRemoveEnabled = false; 47 | private boolean mIsRemoving = false; 48 | 49 | private GestureDetector mDetector; 50 | 51 | private GestureDetector mFlingRemoveDetector; 52 | 53 | private int mTouchSlop; 54 | 55 | public static final int MISS = -1; 56 | 57 | private int mHitPos = MISS; 58 | private int mFlingHitPos = MISS; 59 | 60 | private int mClickRemoveHitPos = MISS; 61 | 62 | private int[] mTempLoc = new int[2]; 63 | 64 | private int mItemX; 65 | private int mItemY; 66 | 67 | private int mCurrX; 68 | private int mCurrY; 69 | 70 | private boolean mDragging = false; 71 | 72 | private float mFlingSpeed = 500f; 73 | 74 | private int mDragHandleId; 75 | 76 | private int mClickRemoveId; 77 | 78 | private int mFlingHandleId; 79 | private boolean mCanDrag; 80 | 81 | private DragSortListView mDslv; 82 | private int mPositionX; 83 | 84 | /** 85 | * Calls {@link #DragSortController(DragSortListView, int)} with a 0 drag 86 | * handle id, FLING_RIGHT_REMOVE remove mode, and ON_DOWN drag init. By 87 | * default, sorting is enabled, and removal is disabled. 88 | * 89 | * @param dslv 90 | * The DSLV instance 91 | */ 92 | public DragSortController(DragSortListView dslv) { 93 | this(dslv, 0, ON_DOWN, FLING_REMOVE); 94 | } 95 | 96 | public DragSortController(DragSortListView dslv, int dragHandleId, 97 | int dragInitMode, int removeMode) { 98 | this(dslv, dragHandleId, dragInitMode, removeMode, 0); 99 | } 100 | 101 | public DragSortController(DragSortListView dslv, int dragHandleId, 102 | int dragInitMode, int removeMode, int clickRemoveId) { 103 | this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0); 104 | } 105 | 106 | /** 107 | * By default, sorting is enabled, and removal is disabled. 108 | * 109 | * @param dslv 110 | * The DSLV instance 111 | * @param dragHandleId 112 | * The resource id of the View that represents the drag handle in 113 | * a list item. 114 | */ 115 | public DragSortController(DragSortListView dslv, int dragHandleId, 116 | int dragInitMode, int removeMode, int clickRemoveId, 117 | int flingHandleId) { 118 | super(dslv); 119 | mDslv = dslv; 120 | mDetector = new GestureDetector(dslv.getContext(), this); 121 | mFlingRemoveDetector = new GestureDetector(dslv.getContext(), 122 | mFlingRemoveListener); 123 | mFlingRemoveDetector.setIsLongpressEnabled(false); 124 | mTouchSlop = ViewConfiguration.get(dslv.getContext()) 125 | .getScaledTouchSlop(); 126 | mDragHandleId = dragHandleId; 127 | mClickRemoveId = clickRemoveId; 128 | mFlingHandleId = flingHandleId; 129 | setRemoveMode(removeMode); 130 | setDragInitMode(dragInitMode); 131 | } 132 | 133 | public int getDragInitMode() { 134 | return mDragInitMode; 135 | } 136 | 137 | /** 138 | * Set how a drag is initiated. Needs to be one of {@link ON_DOWN}, 139 | * {@link ON_DRAG}, or {@link ON_LONG_PRESS}. 140 | * 141 | * @param mode 142 | * The drag init mode. 143 | */ 144 | public void setDragInitMode(int mode) { 145 | mDragInitMode = mode; 146 | } 147 | 148 | /** 149 | * Enable/Disable list item sorting. Disabling is useful if only item 150 | * removal is desired. Prevents drags in the vertical direction. 151 | * 152 | * @param enabled 153 | * Set true to enable list item sorting. 154 | */ 155 | public void setSortEnabled(boolean enabled) { 156 | mSortEnabled = enabled; 157 | } 158 | 159 | public boolean isSortEnabled() { 160 | return mSortEnabled; 161 | } 162 | 163 | /** 164 | * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE}, 165 | * {@link FLING_LEFT_REMOVE}, {@link SLIDE_RIGHT_REMOVE}, or 166 | * {@link SLIDE_LEFT_REMOVE}. 167 | */ 168 | public void setRemoveMode(int mode) { 169 | mRemoveMode = mode; 170 | } 171 | 172 | public int getRemoveMode() { 173 | return mRemoveMode; 174 | } 175 | 176 | /** 177 | * Enable/Disable item removal without affecting remove mode. 178 | */ 179 | public void setRemoveEnabled(boolean enabled) { 180 | mRemoveEnabled = enabled; 181 | } 182 | 183 | public boolean isRemoveEnabled() { 184 | return mRemoveEnabled; 185 | } 186 | 187 | /** 188 | * Set the resource id for the View that represents the drag handle in a 189 | * list item. 190 | * 191 | * @param id 192 | * An android resource id. 193 | */ 194 | public void setDragHandleId(int id) { 195 | mDragHandleId = id; 196 | } 197 | 198 | /** 199 | * Set the resource id for the View that represents the fling handle in a 200 | * list item. 201 | * 202 | * @param id 203 | * An android resource id. 204 | */ 205 | public void setFlingHandleId(int id) { 206 | mFlingHandleId = id; 207 | } 208 | 209 | /** 210 | * Set the resource id for the View that represents click removal button. 211 | * 212 | * @param id 213 | * An android resource id. 214 | */ 215 | public void setClickRemoveId(int id) { 216 | mClickRemoveId = id; 217 | } 218 | 219 | /** 220 | * Sets flags to restrict certain motions of the floating View based on 221 | * DragSortController settings (such as remove mode). Starts the drag on the 222 | * DragSortListView. 223 | * 224 | * @param position 225 | * The list item position (includes headers). 226 | * @param deltaX 227 | * Touch x-coord minus left edge of floating View. 228 | * @param deltaY 229 | * Touch y-coord minus top edge of floating View. 230 | * 231 | * @return True if drag started, false otherwise. 232 | */ 233 | public boolean startDrag(int position, int deltaX, int deltaY) { 234 | 235 | int dragFlags = 0; 236 | if (mSortEnabled && !mIsRemoving) { 237 | dragFlags |= DragSortListView.DRAG_POS_Y 238 | | DragSortListView.DRAG_NEG_Y; 239 | } 240 | if (mRemoveEnabled && mIsRemoving) { 241 | dragFlags |= DragSortListView.DRAG_POS_X; 242 | dragFlags |= DragSortListView.DRAG_NEG_X; 243 | } 244 | 245 | mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), 246 | dragFlags, deltaX, deltaY); 247 | return mDragging; 248 | } 249 | 250 | @Override 251 | public boolean onTouch(View v, MotionEvent ev) { 252 | if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) { 253 | return false; 254 | } 255 | 256 | mDetector.onTouchEvent(ev); 257 | if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) { 258 | mFlingRemoveDetector.onTouchEvent(ev); 259 | } 260 | 261 | int action = ev.getAction() & MotionEvent.ACTION_MASK; 262 | switch (action) { 263 | case MotionEvent.ACTION_DOWN: 264 | mCurrX = (int) ev.getX(); 265 | mCurrY = (int) ev.getY(); 266 | break; 267 | case MotionEvent.ACTION_UP: 268 | if (mRemoveEnabled && mIsRemoving) { 269 | int x = mPositionX >= 0 ? mPositionX : -mPositionX; 270 | int removePoint = mDslv.getWidth() / 2; 271 | if (x > removePoint) { 272 | mDslv.stopDragWithVelocity(true, 0); 273 | } 274 | } 275 | case MotionEvent.ACTION_CANCEL: 276 | mIsRemoving = false; 277 | mDragging = false; 278 | break; 279 | } 280 | 281 | return false; 282 | } 283 | 284 | /** 285 | * Overrides to provide fading when slide removal is enabled. 286 | */ 287 | @Override 288 | public void onDragFloatView(View floatView, Point position, Point touch) { 289 | 290 | if (mRemoveEnabled && mIsRemoving) { 291 | mPositionX = position.x; 292 | } 293 | } 294 | 295 | /** 296 | * Get the position to start dragging based on the ACTION_DOWN MotionEvent. 297 | * This function simply calls {@link #dragHandleHitPosition(MotionEvent)}. 298 | * Override to change drag handle behavior; this function is called 299 | * internally when an ACTION_DOWN event is detected. 300 | * 301 | * @param ev 302 | * The ACTION_DOWN MotionEvent. 303 | * 304 | * @return The list position to drag if a drag-init gesture is detected; 305 | * MISS if unsuccessful. 306 | */ 307 | public int startDragPosition(MotionEvent ev) { 308 | return dragHandleHitPosition(ev); 309 | } 310 | 311 | public int startFlingPosition(MotionEvent ev) { 312 | return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS; 313 | } 314 | 315 | /** 316 | * Checks for the touch of an item's drag handle (specified by 317 | * {@link #setDragHandleId(int)}), and returns that item's position if a 318 | * drag handle touch was detected. 319 | * 320 | * @param ev 321 | * The ACTION_DOWN MotionEvent. 322 | * 323 | * @return The list position of the item whose drag handle was touched; MISS 324 | * if unsuccessful. 325 | */ 326 | public int dragHandleHitPosition(MotionEvent ev) { 327 | return viewIdHitPosition(ev, mDragHandleId); 328 | } 329 | 330 | public int flingHandleHitPosition(MotionEvent ev) { 331 | return viewIdHitPosition(ev, mFlingHandleId); 332 | } 333 | 334 | public int viewIdHitPosition(MotionEvent ev, int id) { 335 | final int x = (int) ev.getX(); 336 | final int y = (int) ev.getY(); 337 | 338 | int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers 339 | 340 | final int numHeaders = mDslv.getHeaderViewsCount(); 341 | final int numFooters = mDslv.getFooterViewsCount(); 342 | final int count = mDslv.getCount(); 343 | 344 | // Log.d("mobeta", "touch down on position " + itemnum); 345 | // We're only interested if the touch was on an 346 | // item that's not a header or footer. 347 | if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders 348 | && touchPos < (count - numFooters)) { 349 | final View item = mDslv.getChildAt(touchPos 350 | - mDslv.getFirstVisiblePosition()); 351 | final int rawX = (int) ev.getRawX(); 352 | final int rawY = (int) ev.getRawY(); 353 | 354 | View dragBox = id == 0 ? item : (View) item.findViewById(id); 355 | if (dragBox != null) { 356 | dragBox.getLocationOnScreen(mTempLoc); 357 | 358 | if (rawX > mTempLoc[0] && rawY > mTempLoc[1] 359 | && rawX < mTempLoc[0] + dragBox.getWidth() 360 | && rawY < mTempLoc[1] + dragBox.getHeight()) { 361 | 362 | mItemX = item.getLeft(); 363 | mItemY = item.getTop(); 364 | 365 | return touchPos; 366 | } 367 | } 368 | } 369 | 370 | return MISS; 371 | } 372 | 373 | @Override 374 | public boolean onDown(MotionEvent ev) { 375 | if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { 376 | mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId); 377 | } 378 | 379 | mHitPos = startDragPosition(ev); 380 | if (mHitPos != MISS && mDragInitMode == ON_DOWN) { 381 | startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() 382 | - mItemY); 383 | } 384 | 385 | mIsRemoving = false; 386 | mCanDrag = true; 387 | mPositionX = 0; 388 | mFlingHitPos = startFlingPosition(ev); 389 | 390 | return true; 391 | } 392 | 393 | @Override 394 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 395 | float distanceY) { 396 | 397 | final int x1 = (int) e1.getX(); 398 | final int y1 = (int) e1.getY(); 399 | final int x2 = (int) e2.getX(); 400 | final int y2 = (int) e2.getY(); 401 | final int deltaX = x2 - mItemX; 402 | final int deltaY = y2 - mItemY; 403 | 404 | if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) { 405 | if (mHitPos != MISS) { 406 | if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop 407 | && mSortEnabled) { 408 | startDrag(mHitPos, deltaX, deltaY); 409 | } else if (mDragInitMode != ON_DOWN 410 | && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) { 411 | mIsRemoving = true; 412 | startDrag(mFlingHitPos, deltaX, deltaY); 413 | } 414 | } else if (mFlingHitPos != MISS) { 415 | if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) { 416 | mIsRemoving = true; 417 | startDrag(mFlingHitPos, deltaX, deltaY); 418 | } else if (Math.abs(y2 - y1) > mTouchSlop) { 419 | mCanDrag = false; // if started to scroll the list then 420 | // don't allow sorting nor fling-removing 421 | } 422 | } 423 | } 424 | // return whatever 425 | return false; 426 | } 427 | 428 | @Override 429 | public void onLongPress(MotionEvent e) { 430 | // Log.d("mobeta", "lift listener long pressed"); 431 | if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) { 432 | mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 433 | startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY); 434 | } 435 | } 436 | 437 | // complete the OnGestureListener interface 438 | @Override 439 | public final boolean onFling(MotionEvent e1, MotionEvent e2, 440 | float velocityX, float velocityY) { 441 | return false; 442 | } 443 | 444 | // complete the OnGestureListener interface 445 | @Override 446 | public boolean onSingleTapUp(MotionEvent ev) { 447 | if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { 448 | if (mClickRemoveHitPos != MISS) { 449 | mDslv.removeItem(mClickRemoveHitPos 450 | - mDslv.getHeaderViewsCount()); 451 | } 452 | } 453 | return true; 454 | } 455 | 456 | // complete the OnGestureListener interface 457 | @Override 458 | public void onShowPress(MotionEvent ev) { 459 | // do nothing 460 | } 461 | 462 | private GestureDetector.OnGestureListener mFlingRemoveListener = new GestureDetector.SimpleOnGestureListener() { 463 | @Override 464 | public final boolean onFling(MotionEvent e1, MotionEvent e2, 465 | float velocityX, float velocityY) { 466 | // Log.d("mobeta", "on fling remove called"); 467 | if (mRemoveEnabled && mIsRemoving) { 468 | int w = mDslv.getWidth(); 469 | int minPos = w / 5; 470 | if (velocityX > mFlingSpeed) { 471 | if (mPositionX > -minPos) { 472 | mDslv.stopDragWithVelocity(true, velocityX); 473 | } 474 | } else if (velocityX < -mFlingSpeed) { 475 | if (mPositionX < minPos) { 476 | mDslv.stopDragWithVelocity(true, velocityX); 477 | } 478 | } 479 | mIsRemoving = false; 480 | } 481 | return false; 482 | } 483 | }; 484 | 485 | } 486 | -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mobeta.android.dslv; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.util.SparseIntArray; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.CursorAdapter; 11 | 12 | /** 13 | * A subclass of {@link android.widget.CursorAdapter} that provides reordering 14 | * of the elements in the Cursor based on completed drag-sort operations. The 15 | * reordering is a simple mapping of list positions into Cursor positions (the 16 | * Cursor is unchanged). To persist changes made by drag-sorts, one can retrieve 17 | * the mapping with the {@link #getCursorPositions()} method, which returns the 18 | * reordered list of Cursor positions. 19 | * 20 | * An instance of this class is passed to 21 | * {@link DragSortListView#setAdapter(ListAdapter)} and, since this class 22 | * implements the {@link DragSortListView.DragSortListener} interface, it is 23 | * automatically set as the DragSortListener for the DragSortListView instance. 24 | */ 25 | public abstract class DragSortCursorAdapter extends CursorAdapter implements 26 | DragSortListView.DragSortListener { 27 | 28 | public static final int REMOVED = -1; 29 | 30 | /** 31 | * Key is ListView position, value is Cursor position 32 | */ 33 | private SparseIntArray mListMapping = new SparseIntArray(); 34 | 35 | private ArrayList mRemovedCursorPositions = new ArrayList(); 36 | 37 | public DragSortCursorAdapter(Context context, Cursor c) { 38 | super(context, c); 39 | } 40 | 41 | public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) { 42 | super(context, c, autoRequery); 43 | } 44 | 45 | public DragSortCursorAdapter(Context context, Cursor c, int flags) { 46 | super(context, c, flags); 47 | } 48 | 49 | /** 50 | * Swaps Cursor and clears list-Cursor mapping. 51 | * 52 | * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor) 53 | */ 54 | @Override 55 | public Cursor swapCursor(Cursor newCursor) { 56 | Cursor old = super.swapCursor(newCursor); 57 | resetMappings(); 58 | return old; 59 | } 60 | 61 | /** 62 | * Changes Cursor and clears list-Cursor mapping. 63 | * 64 | * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor) 65 | */ 66 | @Override 67 | public void changeCursor(Cursor cursor) { 68 | super.changeCursor(cursor); 69 | resetMappings(); 70 | } 71 | 72 | /** 73 | * Resets list-cursor mapping. 74 | */ 75 | public void reset() { 76 | resetMappings(); 77 | notifyDataSetChanged(); 78 | } 79 | 80 | private void resetMappings() { 81 | mListMapping.clear(); 82 | mRemovedCursorPositions.clear(); 83 | } 84 | 85 | @Override 86 | public Object getItem(int position) { 87 | return super.getItem(mListMapping.get(position, position)); 88 | } 89 | 90 | @Override 91 | public long getItemId(int position) { 92 | return super.getItemId(mListMapping.get(position, position)); 93 | } 94 | 95 | @Override 96 | public View getDropDownView(int position, View convertView, ViewGroup parent) { 97 | return super.getDropDownView(mListMapping.get(position, position), 98 | convertView, parent); 99 | } 100 | 101 | @Override 102 | public View getView(int position, View convertView, ViewGroup parent) { 103 | return super.getView(mListMapping.get(position, position), convertView, 104 | parent); 105 | } 106 | 107 | /** 108 | * On drop, this updates the mapping between Cursor positions and ListView 109 | * positions. The Cursor is unchanged. Retrieve the current mapping with 110 | * {@link getCursorPositions()}. 111 | * 112 | * @see DragSortListView.DropListener#drop(int, int) 113 | */ 114 | @Override 115 | public void drop(int from, int to) { 116 | if (from != to) { 117 | int cursorFrom = mListMapping.get(from, from); 118 | 119 | if (from > to) { 120 | for (int i = from; i > to; --i) { 121 | mListMapping.put(i, mListMapping.get(i - 1, i - 1)); 122 | } 123 | } else { 124 | for (int i = from; i < to; ++i) { 125 | mListMapping.put(i, mListMapping.get(i + 1, i + 1)); 126 | } 127 | } 128 | mListMapping.put(to, cursorFrom); 129 | 130 | cleanMapping(); 131 | notifyDataSetChanged(); 132 | } 133 | } 134 | 135 | /** 136 | * On remove, this updates the mapping between Cursor positions and ListView 137 | * positions. The Cursor is unchanged. Retrieve the current mapping with 138 | * {@link getCursorPositions()}. 139 | * 140 | * @see DragSortListView.RemoveListener#remove(int) 141 | */ 142 | @Override 143 | public void remove(int which) { 144 | int cursorPos = mListMapping.get(which, which); 145 | if (!mRemovedCursorPositions.contains(cursorPos)) { 146 | mRemovedCursorPositions.add(cursorPos); 147 | } 148 | 149 | int newCount = getCount(); 150 | for (int i = which; i < newCount; ++i) { 151 | mListMapping.put(i, mListMapping.get(i + 1, i + 1)); 152 | } 153 | 154 | mListMapping.delete(newCount); 155 | 156 | cleanMapping(); 157 | notifyDataSetChanged(); 158 | } 159 | 160 | /** 161 | * Does nothing. Just completes DragSortListener interface. 162 | */ 163 | @Override 164 | public void drag(int from, int to) { 165 | // do nothing 166 | } 167 | 168 | /** 169 | * Remove unnecessary mappings from sparse array. 170 | */ 171 | private void cleanMapping() { 172 | ArrayList toRemove = new ArrayList(); 173 | 174 | int size = mListMapping.size(); 175 | for (int i = 0; i < size; ++i) { 176 | if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) { 177 | toRemove.add(mListMapping.keyAt(i)); 178 | } 179 | } 180 | 181 | size = toRemove.size(); 182 | for (int i = 0; i < size; ++i) { 183 | mListMapping.delete(toRemove.get(i)); 184 | } 185 | } 186 | 187 | @Override 188 | public int getCount() { 189 | return super.getCount() - mRemovedCursorPositions.size(); 190 | } 191 | 192 | /** 193 | * Get the Cursor position mapped to by the provided list position (given 194 | * all previously handled drag-sort operations). 195 | * 196 | * @param position 197 | * List position 198 | * 199 | * @return The mapped-to Cursor position 200 | */ 201 | public int getCursorPosition(int position) { 202 | return mListMapping.get(position, position); 203 | } 204 | 205 | /** 206 | * Get the current order of Cursor positions presented by the list. 207 | */ 208 | public ArrayList getCursorPositions() { 209 | ArrayList result = new ArrayList(); 210 | 211 | for (int i = 0; i < getCount(); ++i) { 212 | result.add(mListMapping.get(i, i)); 213 | } 214 | 215 | return result; 216 | } 217 | 218 | /** 219 | * Get the list position mapped to by the provided Cursor position. If the 220 | * provided Cursor position has been removed by a drag-sort, this returns 221 | * {@link #REMOVED}. 222 | * 223 | * @param cursorPosition 224 | * A Cursor position 225 | * @return The mapped-to list position or REMOVED 226 | */ 227 | public int getListPosition(int cursorPosition) { 228 | if (mRemovedCursorPositions.contains(cursorPosition)) { 229 | return REMOVED; 230 | } 231 | 232 | int index = mListMapping.indexOfValue(cursorPosition); 233 | if (index < 0) { 234 | return cursorPosition; 235 | } else { 236 | return mListMapping.keyAt(index); 237 | } 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java: -------------------------------------------------------------------------------- 1 | package com.mobeta.android.dslv; 2 | 3 | import android.content.Context; 4 | import android.view.Gravity; 5 | import android.view.View; 6 | import android.view.View.MeasureSpec; 7 | import android.view.ViewGroup; 8 | import android.widget.AbsListView; 9 | 10 | /** 11 | * Lightweight ViewGroup that wraps list items obtained from user's ListAdapter. 12 | * ItemView expects a single child that has a definite height (i.e. the child's 13 | * layout height is not MATCH_PARENT). The width of ItemView will always match 14 | * the width of its child (that is, the width MeasureSpec given to ItemView is 15 | * passed directly to the child, and the ItemView measured width is set to the 16 | * child's measured width). The height of ItemView can be anything; the 17 | * 18 | * 19 | * The purpose of this class is to optimize slide shuffle animations. 20 | */ 21 | public class DragSortItemView extends ViewGroup { 22 | 23 | private int mGravity = Gravity.TOP; 24 | 25 | public DragSortItemView(Context context) { 26 | super(context); 27 | 28 | // always init with standard ListView layout params 29 | setLayoutParams(new AbsListView.LayoutParams( 30 | ViewGroup.LayoutParams.FILL_PARENT, 31 | ViewGroup.LayoutParams.WRAP_CONTENT)); 32 | 33 | // setClipChildren(true); 34 | } 35 | 36 | public void setGravity(int gravity) { 37 | mGravity = gravity; 38 | } 39 | 40 | public int getGravity() { 41 | return mGravity; 42 | } 43 | 44 | @Override 45 | protected void onLayout(boolean changed, int left, int top, int right, 46 | int bottom) { 47 | final View child = getChildAt(0); 48 | 49 | if (child == null) { 50 | return; 51 | } 52 | 53 | if (mGravity == Gravity.TOP) { 54 | child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight()); 55 | } else { 56 | child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), 57 | getMeasuredWidth(), getMeasuredHeight()); 58 | } 59 | } 60 | 61 | /** 62 | * 63 | */ 64 | @Override 65 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 66 | 67 | int height = MeasureSpec.getSize(heightMeasureSpec); 68 | int width = MeasureSpec.getSize(widthMeasureSpec); 69 | 70 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 71 | 72 | final View child = getChildAt(0); 73 | if (child == null) { 74 | setMeasuredDimension(0, width); 75 | return; 76 | } 77 | 78 | if (child.isLayoutRequested()) { 79 | // Always let child be as tall as it wants. 80 | measureChild(child, widthMeasureSpec, 81 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 82 | } 83 | 84 | if (heightMode == MeasureSpec.UNSPECIFIED) { 85 | ViewGroup.LayoutParams lp = getLayoutParams(); 86 | 87 | if (lp.height > 0) { 88 | height = lp.height; 89 | } else { 90 | height = child.getMeasuredHeight(); 91 | } 92 | } 93 | 94 | setMeasuredDimension(width, height); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java: -------------------------------------------------------------------------------- 1 | package com.mobeta.android.dslv; 2 | 3 | import android.content.Context; 4 | import android.view.Gravity; 5 | import android.view.View; 6 | import android.view.View.MeasureSpec; 7 | import android.view.ViewGroup; 8 | import android.widget.AbsListView; 9 | import android.widget.Checkable; 10 | import android.util.Log; 11 | 12 | /** 13 | * Lightweight ViewGroup that wraps list items obtained from user's ListAdapter. 14 | * ItemView expects a single child that has a definite height (i.e. the child's 15 | * layout height is not MATCH_PARENT). The width of ItemView will always match 16 | * the width of its child (that is, the width MeasureSpec given to ItemView is 17 | * passed directly to the child, and the ItemView measured width is set to the 18 | * child's measured width). The height of ItemView can be anything; the 19 | * 20 | * 21 | * The purpose of this class is to optimize slide shuffle animations. 22 | */ 23 | public class DragSortItemViewCheckable extends DragSortItemView implements 24 | Checkable { 25 | 26 | public DragSortItemViewCheckable(Context context) { 27 | super(context); 28 | } 29 | 30 | @Override 31 | public boolean isChecked() { 32 | View child = getChildAt(0); 33 | if (child instanceof Checkable) 34 | return ((Checkable) child).isChecked(); 35 | else 36 | return false; 37 | } 38 | 39 | @Override 40 | public void setChecked(boolean checked) { 41 | View child = getChildAt(0); 42 | if (child instanceof Checkable) 43 | ((Checkable) child).setChecked(checked); 44 | } 45 | 46 | @Override 47 | public void toggle() { 48 | View child = getChildAt(0); 49 | if (child instanceof Checkable) 50 | ((Checkable) child).toggle(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 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 | 17 | package com.mobeta.android.dslv; 18 | 19 | import android.content.Context; 20 | import android.database.Cursor; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.view.LayoutInflater; 24 | 25 | // taken from v4 rev. 10 ResourceCursorAdapter.java 26 | 27 | /** 28 | * Static library support version of the framework's 29 | * {@link android.widget.ResourceCursorAdapter}. Used to write apps that run on 30 | * platforms prior to Android 3.0. When running on Android 3.0 or above, this 31 | * implementation is still used; it does not try to switch to the framework's 32 | * implementation. See the framework SDK documentation for a class overview. 33 | */ 34 | public abstract class ResourceDragSortCursorAdapter extends 35 | DragSortCursorAdapter { 36 | private int mLayout; 37 | 38 | private int mDropDownLayout; 39 | 40 | private LayoutInflater mInflater; 41 | 42 | /** 43 | * Constructor the enables auto-requery. 44 | * 45 | * @deprecated This option is discouraged, as it results in Cursor queries 46 | * being performed on the application's UI thread and thus can 47 | * cause poor responsiveness or even Application Not Responding 48 | * errors. As an alternative, use 49 | * {@link android.app.LoaderManager} with a 50 | * {@link android.content.CursorLoader}. 51 | * 52 | * @param context 53 | * The context where the ListView associated with this adapter is 54 | * running 55 | * @param layout 56 | * resource identifier of a layout file that defines the views 57 | * for this list item. Unless you override them later, this will 58 | * define both the item views and the drop down views. 59 | */ 60 | @Deprecated 61 | public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) { 62 | super(context, c); 63 | mLayout = mDropDownLayout = layout; 64 | mInflater = (LayoutInflater) context 65 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 66 | } 67 | 68 | /** 69 | * Constructor with default behavior as per 70 | * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is 71 | * recommended you not use this, but instead 72 | * {@link #ResourceCursorAdapter(Context, int, Cursor, int)}. When using 73 | * this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} will always be 74 | * set. 75 | * 76 | * @param context 77 | * The context where the ListView associated with this adapter is 78 | * running 79 | * @param layout 80 | * resource identifier of a layout file that defines the views 81 | * for this list item. Unless you override them later, this will 82 | * define both the item views and the drop down views. 83 | * @param c 84 | * The cursor from which to get the data. 85 | * @param autoRequery 86 | * If true the adapter will call requery() on the cursor whenever 87 | * it changes so the most recent data is always displayed. Using 88 | * true here is discouraged. 89 | */ 90 | public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, 91 | boolean autoRequery) { 92 | super(context, c, autoRequery); 93 | mLayout = mDropDownLayout = layout; 94 | mInflater = (LayoutInflater) context 95 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 96 | } 97 | 98 | /** 99 | * Standard constructor. 100 | * 101 | * @param context 102 | * The context where the ListView associated with this adapter is 103 | * running 104 | * @param layout 105 | * Resource identifier of a layout file that defines the views 106 | * for this list item. Unless you override them later, this will 107 | * define both the item views and the drop down views. 108 | * @param c 109 | * The cursor from which to get the data. 110 | * @param flags 111 | * Flags used to determine the behavior of the adapter, as per 112 | * {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}. 113 | */ 114 | public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, 115 | int flags) { 116 | super(context, c, flags); 117 | mLayout = mDropDownLayout = layout; 118 | mInflater = (LayoutInflater) context 119 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 120 | } 121 | 122 | /** 123 | * Inflates view(s) from the specified XML file. 124 | * 125 | * @see android.widget.CursorAdapter#newView(android.content.Context, 126 | * android.database.Cursor, ViewGroup) 127 | */ 128 | @Override 129 | public View newView(Context context, Cursor cursor, ViewGroup parent) { 130 | return mInflater.inflate(mLayout, parent, false); 131 | } 132 | 133 | @Override 134 | public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { 135 | return mInflater.inflate(mDropDownLayout, parent, false); 136 | } 137 | 138 | /** 139 | *

140 | * Sets the layout resource of the item views. 141 | *

142 | * 143 | * @param layout 144 | * the layout resources used to create item views 145 | */ 146 | public void setViewResource(int layout) { 147 | mLayout = layout; 148 | } 149 | 150 | /** 151 | *

152 | * Sets the layout resource of the drop down views. 153 | *

154 | * 155 | * @param dropDownLayout 156 | * the layout resources used to create drop down views 157 | */ 158 | public void setDropDownViewResource(int dropDownLayout) { 159 | mDropDownLayout = dropDownLayout; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java: -------------------------------------------------------------------------------- 1 | package com.mobeta.android.dslv; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Point; 5 | import android.graphics.Color; 6 | import android.widget.ListView; 7 | import android.widget.ImageView; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | /** 12 | * Simple implementation of the FloatViewManager class. Uses list items as they 13 | * appear in the ListView to create the floating View. 14 | */ 15 | public class SimpleFloatViewManager implements 16 | DragSortListView.FloatViewManager { 17 | 18 | private Bitmap mFloatBitmap; 19 | 20 | private ImageView mImageView; 21 | 22 | private int mFloatBGColor = Color.BLACK; 23 | 24 | private ListView mListView; 25 | 26 | public SimpleFloatViewManager(ListView lv) { 27 | mListView = lv; 28 | } 29 | 30 | public void setBackgroundColor(int color) { 31 | mFloatBGColor = color; 32 | } 33 | 34 | /** 35 | * This simple implementation creates a Bitmap copy of the list item 36 | * currently shown at ListView position. 37 | */ 38 | @Override 39 | public View onCreateFloatView(int position) { 40 | // Guaranteed that this will not be null? I think so. Nope, got 41 | // a NullPointerException once... 42 | View v = mListView.getChildAt(position 43 | + mListView.getHeaderViewsCount() 44 | - mListView.getFirstVisiblePosition()); 45 | 46 | if (v == null) { 47 | return null; 48 | } 49 | 50 | v.setPressed(false); 51 | 52 | // Create a copy of the drawing cache so that it does not get 53 | // recycled by the framework when the list tries to clean up memory 54 | // v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); 55 | v.setDrawingCacheEnabled(true); 56 | mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache()); 57 | v.setDrawingCacheEnabled(false); 58 | 59 | if (mImageView == null) { 60 | mImageView = new ImageView(mListView.getContext()); 61 | } 62 | mImageView.setBackgroundColor(mFloatBGColor); 63 | mImageView.setPadding(0, 0, 0, 0); 64 | mImageView.setImageBitmap(mFloatBitmap); 65 | mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v 66 | .getHeight())); 67 | 68 | return mImageView; 69 | } 70 | 71 | /** 72 | * This does nothing 73 | */ 74 | @Override 75 | public void onDragFloatView(View floatView, Point position, Point touch) { 76 | // do nothing 77 | } 78 | 79 | /** 80 | * Removes the Bitmap from the ImageView created in onCreateFloatView() and 81 | * tells the system to recycle it. 82 | */ 83 | @Override 84 | public void onDestroyFloatView(View floatView) { 85 | ((ImageView) floatView).setImageDrawable(null); 86 | 87 | mFloatBitmap.recycle(); 88 | mFloatBitmap = null; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/AboutFragment.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.content.pm.PackageInfo; 22 | import android.content.pm.PackageManager.NameNotFoundException; 23 | import android.os.Bundle; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.TextView; 28 | 29 | import androidx.fragment.app.Fragment; 30 | 31 | public class AboutFragment extends Fragment { 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 34 | Bundle savedInstanceState) { 35 | View v = inflater.inflate(R.layout.about_fragment, container, false); 36 | 37 | PackageInfo pinfo; 38 | try { 39 | Context context = getActivity(); 40 | pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 41 | } catch (NameNotFoundException e) { 42 | throw new RuntimeException("Can't find package?"); 43 | } 44 | 45 | // Evaluate the format string in VersionText. 46 | TextView versionText = (TextView) v.findViewById(R.id.VersionText); 47 | String versionFormat = versionText.getText().toString(); 48 | versionText.setText(String.format(versionFormat, pinfo.versionName)); 49 | 50 | return v; 51 | } 52 | 53 | @Override 54 | public void onResume() { 55 | super.onResume(); 56 | ((ChromaDoze) getActivity()).setFragmentId(FragmentIndex.ID_ABOUT); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/AudioFocusHelper.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.ComponentName; 21 | import android.content.Context; 22 | import android.media.AudioFocusRequest; 23 | import android.media.AudioManager; 24 | import android.media.AudioManager.OnAudioFocusChangeListener; 25 | import android.os.Build; 26 | 27 | // This file keeps track of AudioFocus events. 28 | // http://developer.android.com/training/managing-audio/audio-focus.html 29 | 30 | class AudioFocusHelper implements OnAudioFocusChangeListener { 31 | private final Context mContext; 32 | private final SampleShuffler.VolumeListener mVolumeListener; 33 | private final AudioManager mAudioManager; 34 | private boolean mActive = false; 35 | private AudioFocusRequest mRequest; 36 | 37 | public AudioFocusHelper(Context ctx, SampleShuffler.VolumeListener volumeListener) { 38 | mContext = ctx; 39 | mVolumeListener = volumeListener; 40 | mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 41 | 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 43 | // For Android Oreo (API 26) and above 44 | mRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) 45 | .setAudioAttributes(AudioParams.makeAudioAttributes()) 46 | .setOnAudioFocusChangeListener(this) 47 | .build(); 48 | } 49 | } 50 | 51 | public void setActive(boolean active) { 52 | if (mActive == active) { 53 | return; 54 | } 55 | if (active) { 56 | requestFocus(); 57 | } else { 58 | abandonFocus(); 59 | } 60 | mActive = active; 61 | } 62 | 63 | @SuppressWarnings("deprecation") 64 | private void requestFocus() { 65 | // I'm too lazy to check the return value. 66 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 67 | mAudioManager.requestAudioFocus(mRequest); 68 | } else { 69 | mAudioManager.requestAudioFocus(this, AudioParams.STREAM_TYPE, AudioManager.AUDIOFOCUS_GAIN); 70 | } 71 | } 72 | 73 | @SuppressWarnings("deprecation") 74 | private void abandonFocus() { 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 76 | mAudioManager.abandonAudioFocusRequest(mRequest); 77 | } else { 78 | mAudioManager.abandonAudioFocus(this); 79 | } 80 | } 81 | 82 | @Override 83 | public void onAudioFocusChange(int focusChange) { 84 | switch (focusChange) { 85 | case AudioManager.AUDIOFOCUS_LOSS: 86 | // For example, a music player or a sleep timer stealing focus. 87 | NoiseService.stopNow(mContext, R.string.stop_reason_audiofocus); 88 | break; 89 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 90 | // For example, an alarm or phone call. 91 | mVolumeListener.setDuckLevel(SampleShuffler.VolumeListener.DuckLevel.SILENT); 92 | break; 93 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 94 | // For example, an email notification. 95 | mVolumeListener.setDuckLevel(SampleShuffler.VolumeListener.DuckLevel.DUCK); 96 | break; 97 | case AudioManager.AUDIOFOCUS_GAIN: 98 | // Resume the default volume level. 99 | mVolumeListener.setDuckLevel(SampleShuffler.VolumeListener.DuckLevel.NORMAL); 100 | break; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/AudioParams.java: -------------------------------------------------------------------------------- 1 | package net.pmarks.chromadoze; 2 | 3 | import android.media.AudioAttributes; 4 | import android.media.AudioFormat; 5 | import android.media.AudioManager; 6 | import android.media.AudioTrack; 7 | import android.os.Build; 8 | 9 | import androidx.annotation.RequiresApi; 10 | 11 | class AudioParams { 12 | final static int STREAM_TYPE = AudioManager.STREAM_MUSIC; 13 | final static int CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_STEREO; 14 | final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; 15 | final static int SHORTS_PER_SAMPLE = 2; // 16-bit Stereo 16 | final static int BYTES_PER_SAMPLE = 4; // 16-bit Stereo 17 | final static int LATENCY_MS = 100; 18 | final int SAMPLE_RATE; 19 | final int BUF_BYTES; 20 | final int BUF_SAMPLES; 21 | 22 | AudioParams() { 23 | SAMPLE_RATE = AudioTrack.getNativeOutputSampleRate(STREAM_TYPE); 24 | BUF_BYTES = Math.max( 25 | AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT), 26 | (SAMPLE_RATE * LATENCY_MS / 1000) * BYTES_PER_SAMPLE); 27 | BUF_SAMPLES = BUF_BYTES / BYTES_PER_SAMPLE; 28 | } 29 | 30 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 31 | static AudioAttributes makeAudioAttributes() { 32 | return new AudioAttributes.Builder() 33 | .setUsage(AudioAttributes.USAGE_MEDIA) 34 | .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) 35 | .build(); 36 | } 37 | 38 | @SuppressWarnings("deprecation") 39 | private AudioTrack makeAudioTrackLegacy() { 40 | return new AudioTrack( 41 | STREAM_TYPE, SAMPLE_RATE, CHANNEL_CONFIG, 42 | AUDIO_FORMAT, BUF_BYTES, AudioTrack.MODE_STREAM); 43 | } 44 | 45 | AudioTrack makeAudioTrack() { 46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 47 | return new AudioTrack(makeAudioAttributes(), 48 | new AudioFormat.Builder() 49 | .setSampleRate(SAMPLE_RATE) 50 | .setChannelMask(CHANNEL_CONFIG) 51 | .setEncoding(AUDIO_FORMAT) 52 | .build(), 53 | BUF_BYTES, 54 | AudioTrack.MODE_STREAM, 55 | AudioManager.AUDIO_SESSION_ID_GENERATE); 56 | } else { 57 | return makeAudioTrackLegacy(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/CheckableLinearLayout.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.util.AttributeSet; 22 | import android.widget.Checkable; 23 | import android.widget.LinearLayout; 24 | 25 | public class CheckableLinearLayout extends LinearLayout implements Checkable { 26 | 27 | private Checkable mChild; 28 | 29 | public CheckableLinearLayout(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | } 32 | 33 | @Override 34 | protected void onFinishInflate() { 35 | super.onFinishInflate(); 36 | for (int i = 0; i < getChildCount(); i++) { 37 | try { 38 | mChild = (Checkable) getChildAt(i); 39 | return; 40 | } catch (ClassCastException e) { 41 | } 42 | } 43 | } 44 | 45 | @Override 46 | public boolean isChecked() { 47 | return mChild.isChecked(); 48 | } 49 | 50 | @Override 51 | public void setChecked(boolean checked) { 52 | mChild.setChecked(checked); 53 | } 54 | 55 | @Override 56 | public void toggle() { 57 | mChild.toggle(); 58 | } 59 | 60 | @Override 61 | public void setEnabled(boolean enabled) { 62 | super.setEnabled(enabled); 63 | for (int i = 0; i < getChildCount(); i++) { 64 | getChildAt(i).setEnabled(enabled); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/ChromaDoze.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.app.backup.BackupManager; 21 | import android.content.SharedPreferences; 22 | import android.graphics.Bitmap; 23 | import android.graphics.BlendMode; 24 | import android.graphics.BlendModeColorFilter; 25 | import android.graphics.PorterDuff; 26 | import android.graphics.PorterDuff.Mode; 27 | import android.graphics.drawable.BitmapDrawable; 28 | import android.graphics.drawable.Drawable; 29 | import android.os.Build; 30 | import android.os.Bundle; 31 | import android.util.TypedValue; 32 | import android.view.Menu; 33 | import android.view.MenuItem; 34 | import android.view.View; 35 | import android.view.ViewGroup; 36 | import android.widget.AdapterView; 37 | import android.widget.AdapterView.OnItemSelectedListener; 38 | import android.widget.ArrayAdapter; 39 | import android.widget.ImageButton; 40 | import android.widget.Spinner; 41 | 42 | import androidx.appcompat.app.ActionBar; 43 | import androidx.appcompat.app.AppCompatActivity; 44 | import androidx.appcompat.widget.Toolbar; 45 | import androidx.core.content.ContextCompat; 46 | import androidx.core.view.MenuItemCompat; 47 | import androidx.fragment.app.Fragment; 48 | import androidx.fragment.app.FragmentManager; 49 | import androidx.fragment.app.FragmentTransaction; 50 | 51 | import java.util.Date; 52 | 53 | public class ChromaDoze extends AppCompatActivity implements 54 | NoiseService.PercentListener, UIState.LockListener, OnItemSelectedListener { 55 | private static final int MENU_PLAY_STOP = 1; 56 | private static final int MENU_LOCK = 2; 57 | 58 | private UIState mUiState; 59 | private int mFragmentId = FragmentIndex.ID_CHROMA_DOZE; 60 | 61 | private Drawable mToolbarIcon; 62 | private Spinner mNavSpinner; 63 | 64 | private boolean mServiceActive; 65 | 66 | // The name to use when accessing our SharedPreferences. 67 | public static final String PREF_NAME = "ChromaDoze"; 68 | 69 | @Override 70 | public void onCreate(Bundle savedInstanceState) { 71 | super.onCreate(savedInstanceState); 72 | setContentView(R.layout.main); 73 | 74 | mUiState = new UIState(getApplication()); 75 | 76 | SharedPreferences pref = getSharedPreferences(PREF_NAME, MODE_PRIVATE); 77 | mUiState.loadState(pref); 78 | 79 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 80 | setSupportActionBar(toolbar); 81 | 82 | ActionBar actionBar = getSupportActionBar(); 83 | actionBar.setDisplayHomeAsUpEnabled(true); 84 | actionBar.setTitle(""); 85 | 86 | mNavSpinner = (Spinner) findViewById(R.id.nav_spinner); 87 | ArrayAdapter adapter = new ArrayAdapter<>( 88 | actionBar.getThemedContext(), R.layout.spinner_title, 89 | FragmentIndex.getStrings(this)); 90 | adapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item); 91 | mNavSpinner.setAdapter(adapter); 92 | mNavSpinner.setOnItemSelectedListener(this); 93 | 94 | 95 | // Created a scaled-down icon for the Toolbar. 96 | { 97 | TypedValue tv = new TypedValue(); 98 | getTheme().resolveAttribute(R.attr.actionBarSize, tv, true); 99 | int height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics()); 100 | // This originally used a scaled-down launcher icon, but I don't feel like figuring 101 | // out how to render R.mipmap.chromadoze_icon correctly. 102 | mToolbarIcon = ContextCompat.getDrawable(this, R.drawable.toolbar_icon); 103 | } 104 | 105 | // When this Activity is first created, set up the initial fragment. 106 | // After a save/restore, the framework will drop in the last-used 107 | // fragment automatically. 108 | if (savedInstanceState == null) { 109 | changeFragment(new MainFragment(), false); 110 | } 111 | } 112 | 113 | @Override 114 | public void onResume() { 115 | super.onResume(); 116 | // Start receiving progress events. 117 | NoiseService.addPercentListener(this); 118 | mUiState.addLockListener(this); 119 | 120 | if (mUiState.getAutoPlay()) { 121 | mUiState.sendToService(); 122 | } 123 | } 124 | 125 | @Override 126 | protected void onPause() { 127 | super.onPause(); 128 | 129 | // If the equalizer is silent, stop the service. 130 | // This makes it harder to leave running accidentally. 131 | if (mServiceActive && mUiState.getPhonon().isSilent()) { 132 | NoiseService.stopNow(getApplication(), R.string.stop_reason_silent); 133 | } 134 | 135 | SharedPreferences.Editor pref = getSharedPreferences(PREF_NAME, MODE_PRIVATE).edit(); 136 | pref.clear(); 137 | mUiState.saveState(pref); 138 | pref.commit(); 139 | new BackupManager(this).dataChanged(); 140 | 141 | // Stop receiving progress events. 142 | NoiseService.removePercentListener(this); 143 | mUiState.removeLockListener(this); 144 | } 145 | 146 | @Override 147 | public boolean onCreateOptionsMenu(Menu menu) { 148 | menu.add(0, MENU_PLAY_STOP, 0, getString(R.string.play_stop)).setShowAsAction( 149 | MenuItem.SHOW_AS_ACTION_ALWAYS); 150 | 151 | if (mFragmentId == FragmentIndex.ID_CHROMA_DOZE) { 152 | menu.add(0, MENU_LOCK, 0, getString(R.string.lock_unlock)).setShowAsAction( 153 | MenuItem.SHOW_AS_ACTION_ALWAYS); 154 | } 155 | 156 | return super.onCreateOptionsMenu(menu); 157 | } 158 | 159 | @Override 160 | public boolean onPrepareOptionsMenu(Menu menu) { 161 | menu.findItem(MENU_PLAY_STOP).setIcon( 162 | mServiceActive ? R.drawable.av_stop : R.drawable.av_play); 163 | MenuItem mi = menu.findItem(MENU_LOCK); 164 | if (mi != null) { 165 | mi.setIcon(getLockIcon()); 166 | } 167 | return super.onPrepareOptionsMenu(menu); 168 | } 169 | 170 | @Override 171 | public void onLockStateChange(LockEvent e) { 172 | // Redraw the lock icon for both event types. 173 | supportInvalidateOptionsMenu(); 174 | } 175 | 176 | @SuppressWarnings("deprecation") 177 | private static void setColorFilterCompat(Drawable drawable, int color, PorterDuff.Mode mode) { 178 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 179 | drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.valueOf(mode.name()))); 180 | } else { 181 | drawable.setColorFilter(color, mode); 182 | } 183 | } 184 | 185 | // Get the lock icon which reflects the current action. 186 | private Drawable getLockIcon() { 187 | Drawable d = ContextCompat.getDrawable(this, mUiState.getLocked() ? 188 | R.drawable.action_unlock : R.drawable.action_lock); 189 | if (mUiState.getLockBusy()) { 190 | setColorFilterCompat(d, 0xFFFF4444, Mode.SRC_IN); 191 | } else { 192 | d.clearColorFilter(); 193 | } 194 | return d; 195 | } 196 | 197 | @Override 198 | public boolean onSupportNavigateUp() { 199 | // Rewind the back stack. 200 | getSupportFragmentManager().popBackStack(null, 201 | FragmentManager.POP_BACK_STACK_INCLUSIVE); 202 | return true; 203 | } 204 | 205 | @Override 206 | public boolean onOptionsItemSelected(MenuItem item) { 207 | switch (item.getItemId()) { 208 | case MENU_PLAY_STOP: 209 | // Force the service into its expected state. 210 | if (!mServiceActive) { 211 | mUiState.sendToService(); 212 | } else { 213 | NoiseService.stopNow(getApplication(), R.string.stop_reason_toolbar); 214 | } 215 | return true; 216 | case MENU_LOCK: 217 | mUiState.toggleLocked(); 218 | supportInvalidateOptionsMenu(); 219 | return true; 220 | } 221 | return false; 222 | } 223 | 224 | @Override 225 | public void onNoiseServicePercentChange(int percent, Date stopTimestamp, int stopReasonId) { 226 | boolean newServiceActive = (percent >= 0); 227 | if (mServiceActive != newServiceActive) { 228 | mServiceActive = newServiceActive; 229 | 230 | // Redraw the "Play/Stop" button. 231 | supportInvalidateOptionsMenu(); 232 | } 233 | } 234 | 235 | private void changeFragment(Fragment f, boolean allowBack) { 236 | FragmentManager fragmentManager = getSupportFragmentManager(); 237 | 238 | // Prune the stack, so "back" always leads home. 239 | if (fragmentManager.getBackStackEntryCount() > 0) { 240 | onSupportNavigateUp(); 241 | } 242 | 243 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 244 | transaction.replace(R.id.fragment_container, f); 245 | if (allowBack) { 246 | transaction.addToBackStack(null); 247 | transaction 248 | .setTransition(FragmentTransaction.TRANSIT_NONE); 249 | } 250 | transaction.commit(); 251 | } 252 | 253 | // Fragments can read this >= onActivityCreated(). 254 | public UIState getUIState() { 255 | return mUiState; 256 | } 257 | 258 | // Each fragment calls this from onResume to tweak the ActionBar. 259 | public void setFragmentId(int id) { 260 | mFragmentId = id; 261 | 262 | final boolean enableUp = id != FragmentIndex.ID_CHROMA_DOZE; 263 | ActionBar actionBar = getSupportActionBar(); 264 | supportInvalidateOptionsMenu(); 265 | 266 | // Use the default left arrow, or a scaled-down Chroma Doze icon. 267 | actionBar.setHomeAsUpIndicator(enableUp ? null : mToolbarIcon); 268 | 269 | // When we're on the main page, make the icon non-clickable. 270 | ImageButton navUp = findImageButton(findViewById(R.id.toolbar)); 271 | if (navUp != null) { 272 | navUp.setClickable(enableUp); 273 | } 274 | 275 | mNavSpinner.setSelection(id); 276 | } 277 | 278 | // Search a View for the first ImageButton. We use it to locate the 279 | // home/up button in a Toolbar. 280 | private static ImageButton findImageButton(View view) { 281 | if (view instanceof ImageButton) { 282 | return (ImageButton) view; 283 | } else if (view instanceof ViewGroup) { 284 | ViewGroup vg = (ViewGroup) view; 285 | for (int i = 0; i < vg.getChildCount(); i++) { 286 | ImageButton found = findImageButton(vg.getChildAt(i)); 287 | if (found != null) { 288 | return found; 289 | } 290 | } 291 | } 292 | return null; 293 | } 294 | 295 | // Handle nav_spinner selection. 296 | @Override 297 | public void onItemSelected(AdapterView parent, View view, int position, 298 | long id) { 299 | if (position == mFragmentId) { 300 | return; 301 | } 302 | switch (position) { 303 | case FragmentIndex.ID_CHROMA_DOZE: 304 | onSupportNavigateUp(); 305 | return; 306 | case FragmentIndex.ID_OPTIONS: 307 | changeFragment(new OptionsFragment(), true); 308 | return; 309 | case FragmentIndex.ID_MEMORY: 310 | changeFragment(new MemoryFragment(), true); 311 | return; 312 | case FragmentIndex.ID_ABOUT: 313 | changeFragment(new AboutFragment(), true); 314 | return; 315 | } 316 | } 317 | 318 | @Override 319 | public void onNothingSelected(AdapterView parent) { 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/EqualizerView.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.graphics.Bitmap; 22 | import android.graphics.BitmapFactory; 23 | import android.graphics.Canvas; 24 | import android.graphics.Color; 25 | import android.graphics.Paint; 26 | import android.graphics.Path; 27 | import android.os.Build; 28 | import android.util.AttributeSet; 29 | import android.view.MotionEvent; 30 | 31 | import androidx.annotation.NonNull; 32 | 33 | public class EqualizerView extends android.view.View implements UIState.LockListener { 34 | private static final int BAND_COUNT = SpectrumData.BAND_COUNT; 35 | 36 | // 3D projection offsets (multiple of mBarWidth) 37 | private static final float PROJECT_X = 0.4f; 38 | private static final float PROJECT_Y = -0.25f; 39 | 40 | // L=light, M=medium, D=dark 41 | private final Paint mBarColorL[] = new Paint[BAND_COUNT]; 42 | private final Paint mBarColorM[] = new Paint[BAND_COUNT]; 43 | private final Paint mBarColorD[] = new Paint[BAND_COUNT]; 44 | private final Paint mBaseColorL[] = new Paint[4]; 45 | private final Paint mBaseColorM[] = new Paint[4]; 46 | private final Paint mBaseColorD[] = new Paint[4]; 47 | 48 | private UIState mUiState; 49 | 50 | private float mWidth; 51 | private float mHeight; 52 | private float mBarWidth; 53 | private float mZeroLineY; 54 | 55 | private Path mCubeTop; 56 | private Path mCubeSide; 57 | 58 | public EqualizerView(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | makeColors(); 61 | } 62 | 63 | public void setUiState(UIState uiState) { 64 | mUiState = uiState; 65 | invalidate(); 66 | } 67 | 68 | private void makeColors() { 69 | Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.spectrum); 70 | for (int i = 0; i < BAND_COUNT; i++) { 71 | Paint p = new Paint(); 72 | int x = (bmp.getWidth() - 1) * i / (BAND_COUNT - 1); 73 | p.setColor(bmp.getPixel(x, 0)); 74 | mBarColorL[i] = p; 75 | } 76 | darken(0.7f, mBarColorL, mBarColorM); 77 | darken(0.5f, mBarColorL, mBarColorD); 78 | 79 | int i = 0; 80 | for (int v : new int[]{100, 75, 55, 50}) { 81 | Paint p = new Paint(); 82 | p.setColor(Color.rgb(v, v, v)); 83 | mBaseColorL[i++] = p; 84 | } 85 | darken(0.7f, mBaseColorL, mBaseColorM); 86 | darken(0.5f, mBaseColorL, mBaseColorD); 87 | } 88 | 89 | private void darken(float mult, Paint[] src, Paint[] dst) { 90 | if (src.length != dst.length) { 91 | throw new IllegalArgumentException("length mismatch"); 92 | } 93 | for (int i = 0; i < src.length; i++) { 94 | int color = src[i].getColor(); 95 | int r = (int) (Color.red(color) * mult); 96 | int g = (int) (Color.green(color) * mult); 97 | int b = (int) (Color.blue(color) * mult); 98 | Paint p = new Paint(src[i]); 99 | p.setColor(Color.argb(Color.alpha(color), r, g, b)); 100 | dst[i] = p; 101 | } 102 | } 103 | 104 | @Override 105 | protected void onDraw(Canvas canvas) { 106 | final Phonon ph = mUiState != null ? mUiState.getPhonon() : null; 107 | final boolean isLocked = mUiState != null ? mUiState.getLocked() : false; 108 | final Path p = new Path(); 109 | 110 | for (int i = 0; i < BAND_COUNT; i++) { 111 | float bar = ph != null ? ph.getBar(i) : .5f; 112 | float startX = bandToX(i); 113 | float stopX = startX + mBarWidth; 114 | float startY = barToY(bar); 115 | float midY = startY + mBarWidth; 116 | 117 | // Lower the brightness and contrast when locked. 118 | int baseCol = i % 2 + (isLocked ? 2 : 0); 119 | 120 | // Bar right (the top-left corner of this rectangle will be clipped.) 121 | float projX = mBarWidth * PROJECT_X; 122 | float projY = mBarWidth * PROJECT_Y; 123 | canvas.drawRect(stopX, midY + projY,stopX + projX, mHeight, mBaseColorD[baseCol]); 124 | 125 | // Bar front 126 | canvas.drawRect(startX, midY, stopX, mHeight, mBaseColorL[baseCol]); 127 | 128 | if (bar > 0) { 129 | // Cube right 130 | mCubeSide.offset(stopX, startY, p); 131 | canvas.drawPath(p, mBarColorD[i]); 132 | 133 | // Cube top 134 | mCubeTop.offset(startX, startY, p); 135 | canvas.drawPath(p, mBarColorM[i]); 136 | 137 | // Cube front 138 | canvas.drawRect(startX, startY, stopX, midY, mBarColorL[i]); 139 | } else { 140 | // Bar top 141 | mCubeTop.offset(startX, midY, p); 142 | canvas.drawPath(p, mBaseColorM[baseCol]); 143 | } 144 | } 145 | } 146 | 147 | private float mLastX; 148 | private float mLastY; 149 | 150 | @Override 151 | public boolean onTouchEvent(@NonNull MotionEvent event) { 152 | if (mUiState.getLocked()) { 153 | switch (event.getAction()) { 154 | case MotionEvent.ACTION_DOWN: 155 | mUiState.setLockBusy(true); 156 | return true; 157 | case MotionEvent.ACTION_UP: 158 | mUiState.setLockBusy(false); 159 | return true; 160 | case MotionEvent.ACTION_MOVE: 161 | return true; 162 | } 163 | return false; 164 | } 165 | 166 | switch (event.getAction()) { 167 | case MotionEvent.ACTION_DOWN: 168 | mLastX = event.getX(); 169 | mLastY = event.getY(); 170 | break; 171 | case MotionEvent.ACTION_UP: 172 | case MotionEvent.ACTION_MOVE: 173 | break; 174 | default: 175 | return false; 176 | } 177 | 178 | PhononMutable phm = mUiState.getPhononMutable(); 179 | for (int i = 0; i < event.getHistorySize(); i++) { 180 | touchLine(phm, event.getHistoricalX(i), event.getHistoricalY(i)); 181 | } 182 | touchLine(phm, event.getX(), event.getY()); 183 | 184 | if (mUiState.sendIfDirty()) { 185 | invalidate(); 186 | } 187 | return true; 188 | } 189 | 190 | @Override 191 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 192 | mWidth = getWidth(); 193 | mHeight = getHeight(); 194 | mBarWidth = mWidth / (BAND_COUNT + 2); 195 | mZeroLineY = mHeight * .9f; 196 | mCubeTop = projectCube(mBarWidth, true); 197 | mCubeSide = projectCube(mBarWidth, false); 198 | } 199 | 200 | // Draw the top or right side of a cube. 201 | private static Path projectCube(float unit, boolean isTop) { 202 | float projX = unit * PROJECT_X; 203 | float projY = unit * PROJECT_Y; 204 | Path p = new Path(); 205 | p.moveTo(0, 0); 206 | p.lineTo(projX, projY); 207 | if (isTop) { 208 | // Top 209 | p.lineTo(unit + projX, projY); 210 | p.lineTo(unit, 0); 211 | } else { 212 | // Side 213 | p.lineTo(projX, unit + projY); 214 | p.lineTo(0, unit); 215 | } 216 | p.close(); 217 | return p; 218 | } 219 | 220 | private float yToBar(float y) { 221 | float barHeight = 1f - (y / (mZeroLineY - mBarWidth)); 222 | if (barHeight < 0) { 223 | return 0; 224 | } 225 | if (barHeight > 1) { 226 | return 1; 227 | } 228 | return barHeight; 229 | } 230 | 231 | private float barToY(float barHeight) { 232 | return (1f - barHeight) * (mZeroLineY - mBarWidth); 233 | } 234 | 235 | // Accepts 0 <= barIndex < BAND_COUNT, 236 | // leaving a 1-bar gap on each side of the screen. 237 | private float bandToX(int barIndex) { 238 | return mBarWidth * (barIndex + 1); 239 | } 240 | 241 | // Returns 0 <= out < BAND_COUNT, 242 | // leaving a 1-bar gap on each side of the screen. 243 | private int xToBand(float x) { 244 | int out = ((int) (x / mBarWidth)) - 1; 245 | if (out < 0) { 246 | out = 0; 247 | } 248 | if (out > BAND_COUNT - 1) { 249 | out = BAND_COUNT - 1; 250 | } 251 | return out; 252 | 253 | } 254 | 255 | // Starting bar? 256 | // Ending bar? 257 | // For each bar it exits: 258 | // set Y to exit-Y. 259 | // For the ending point: 260 | // set Y to final-Y. 261 | 262 | // Exits: 263 | // Right: 264 | // 0->3: 0, 1, 2 [endpoint in 3] 265 | // Left: 266 | // 3->0: 3, 2, 1 [endpoint in 0] 267 | 268 | private void touchLine(PhononMutable phm, float stopX, float stopY) { 269 | float startX = mLastX; 270 | float startY = mLastY; 271 | mLastX = stopX; 272 | mLastY = stopY; 273 | int startBand = xToBand(startX); 274 | int stopBand = xToBand(stopX); 275 | int direction = stopBand > startBand ? 1 : -1; 276 | for (int i = startBand; i != stopBand; i += direction) { 277 | // Get the x-coordinate where we exited band i. 278 | float exitX = bandToX(direction < 0 ? i : i + 1); 279 | 280 | // Get the Y value at exitX. 281 | float slope = (stopY - startY) / (stopX - startX); 282 | float exitY = startY + slope * (exitX - startX); 283 | phm.setBar(i, yToBar(exitY)); 284 | } 285 | // Set the Y endpoint. 286 | phm.setBar(stopBand, yToBar(stopY)); 287 | } 288 | 289 | @Override 290 | public void onLockStateChange(LockEvent e) { 291 | // Only spend time redrawing if this is an on/off event. 292 | if (e == LockEvent.TOGGLE) { 293 | invalidate(); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/EqualizerViewLite.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.content.res.Resources; 22 | import android.graphics.Bitmap; 23 | import android.graphics.BitmapFactory; 24 | import android.graphics.Canvas; 25 | import android.graphics.Color; 26 | import android.graphics.Paint; 27 | import android.graphics.Path; 28 | import android.graphics.PorterDuff.Mode; 29 | import android.graphics.PorterDuffXfermode; 30 | import android.graphics.Rect; 31 | import android.util.AttributeSet; 32 | import android.util.TypedValue; 33 | import android.view.View; 34 | 35 | public class EqualizerViewLite extends View { 36 | private static final int BAND_COUNT = SpectrumData.BAND_COUNT; 37 | 38 | private Phonon mPhonon; 39 | 40 | private int mWidth; 41 | private int mHeight; 42 | private float mBarWidth; 43 | 44 | private Bitmap mBitmap = null; 45 | 46 | public EqualizerViewLite(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | public void setPhonon(Phonon ph) { 51 | if (mPhonon != ph) { 52 | mPhonon = ph; 53 | mBitmap = null; 54 | invalidate(); 55 | } 56 | } 57 | 58 | private Bitmap makeBitmap() { 59 | Bitmap bmp = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 60 | Canvas canvas = new Canvas(bmp); 61 | 62 | // Draw a white line 63 | Paint whitePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 64 | whitePaint.setColor(Color.WHITE); 65 | whitePaint.setAlpha(isEnabled() ? 250 : 94); 66 | whitePaint.setStyle(Paint.Style.STROKE); 67 | whitePaint.setStrokeWidth(dpToPixels(3)); 68 | 69 | Path path = new Path(); 70 | boolean first = true; 71 | for (int i = 0; i < BAND_COUNT; i++) { 72 | float bar = mPhonon != null ? mPhonon.getBar(i) : .5f; 73 | float x = mBarWidth * (i + 0.5f); 74 | float y = barToY(bar); 75 | 76 | if (first) { 77 | first = false; 78 | path.moveTo(x, y); 79 | } else { 80 | path.lineTo(x, y); 81 | } 82 | } 83 | canvas.drawPath(path, whitePaint); 84 | 85 | // Overlay the spectrum bitmap to add color. 86 | Bitmap colorBmp = BitmapFactory.decodeResource(getResources(), R.drawable.spectrum); 87 | Rect src = new Rect(0, 0, colorBmp.getWidth(), colorBmp.getHeight()); 88 | Rect dst = new Rect(0, 0, bmp.getWidth(), bmp.getHeight()); 89 | Paint alphaPaint = new Paint(); 90 | alphaPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); 91 | canvas.drawBitmap(colorBmp, src, dst, alphaPaint); 92 | 93 | return bmp; 94 | } 95 | 96 | @Override 97 | protected void onDraw(Canvas canvas) { 98 | if (mBitmap == null) { 99 | mBitmap = makeBitmap(); 100 | } 101 | canvas.drawBitmap(mBitmap, 0, 0, null); 102 | } 103 | 104 | @Override 105 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 106 | mWidth = getWidth(); 107 | mHeight = getHeight(); 108 | mBarWidth = (float) mWidth / BAND_COUNT; 109 | mBitmap = null; 110 | } 111 | 112 | private float barToY(float barHeight) { 113 | return (1f - barHeight) * mHeight; 114 | } 115 | 116 | private float dpToPixels(float dp) { 117 | Resources r = getResources(); 118 | return TypedValue.applyDimension( 119 | TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()); 120 | } 121 | 122 | @Override 123 | public void setEnabled(boolean enabled) { 124 | super.setEnabled(enabled); 125 | mBitmap = null; 126 | invalidate(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/FragmentIndex.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | 22 | class FragmentIndex { 23 | static final int ID_CHROMA_DOZE = 0; 24 | static final int ID_OPTIONS = 1; 25 | static final int ID_MEMORY = 2; 26 | static final int ID_ABOUT = 3; 27 | static final int ID_COUNT = 4; 28 | 29 | static String[] getStrings(Context context) { 30 | String[] out = new String[ID_COUNT]; 31 | out[ID_CHROMA_DOZE] = getPaddedString(context, R.string.app_name); 32 | out[ID_OPTIONS] = getPaddedString(context, R.string.options); 33 | out[ID_MEMORY] = getPaddedString(context, R.string.memory); 34 | out[ID_ABOUT] = getPaddedString(context, R.string.about_menu); 35 | return out; 36 | } 37 | 38 | private static String getPaddedString(Context context, int resId) { 39 | return context.getString(resId) + " "; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/MainFragment.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.os.Bundle; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.ProgressBar; 25 | import android.widget.TextView; 26 | 27 | import androidx.fragment.app.Fragment; 28 | 29 | import java.text.DateFormat; 30 | import java.util.Date; 31 | 32 | public class MainFragment extends Fragment implements NoiseService.PercentListener { 33 | private EqualizerView mEqualizer; 34 | private TextView mStateText; 35 | private ProgressBar mPercentBar; 36 | private UIState mUiState; 37 | 38 | @Override 39 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 40 | Bundle savedInstanceState) { 41 | View v = inflater.inflate(R.layout.main_fragment, container, false); 42 | 43 | mEqualizer = (EqualizerView) v.findViewById(R.id.EqualizerView); 44 | mStateText = (TextView) v.findViewById(R.id.StateText); 45 | mPercentBar = (ProgressBar) v.findViewById(R.id.PercentBar); 46 | return v; 47 | } 48 | 49 | @Override 50 | public void onViewCreated(View view, Bundle savedInstanceState) { 51 | super.onViewCreated(view, savedInstanceState); 52 | mUiState = ((ChromaDoze) getActivity()).getUIState(); 53 | mEqualizer.setUiState(mUiState); 54 | } 55 | 56 | @Override 57 | public void onResume() { 58 | super.onResume(); 59 | // Start receiving progress events. 60 | NoiseService.addPercentListener(this); 61 | mUiState.addLockListener(mEqualizer); 62 | 63 | ((ChromaDoze) getActivity()).setFragmentId(FragmentIndex.ID_CHROMA_DOZE); 64 | } 65 | 66 | @Override 67 | public void onPause() { 68 | super.onPause(); 69 | // Stop receiving progress events. 70 | NoiseService.removePercentListener(this); 71 | mUiState.removeLockListener(mEqualizer); 72 | } 73 | 74 | @Override 75 | public void onNoiseServicePercentChange(int percent, Date stopTimestamp, int stopReasonId) { 76 | boolean showGenerating = false; 77 | boolean showStopReason = false; 78 | if (percent < 0) { 79 | mPercentBar.setVisibility(View.INVISIBLE); 80 | // While the service is stopped, show what event caused it to stop. 81 | showStopReason = (stopReasonId != 0); 82 | } else if (percent < 100) { 83 | mPercentBar.setVisibility(View.VISIBLE); 84 | mPercentBar.setProgress(percent); 85 | showGenerating = true; 86 | } else { 87 | mPercentBar.setVisibility(View.INVISIBLE); 88 | // While the service is active, only the restart event is worth showing. 89 | showStopReason = (stopReasonId == R.string.stop_reason_restarted); 90 | } 91 | if (showStopReason) { 92 | // Expire the message after 12 hours, to avoid date ambiguity. 93 | long diff = new Date().getTime() - stopTimestamp.getTime(); 94 | if (diff > 12 * 3600 * 1000L) { 95 | showStopReason = false; 96 | } 97 | } 98 | if (showGenerating) { 99 | mStateText.setText(R.string.generating); 100 | } else if (showStopReason) { 101 | String timeFmt = DateFormat.getTimeInstance(DateFormat.SHORT).format(stopTimestamp); 102 | mStateText.setText(timeFmt + ": " + getString(stopReasonId)); 103 | } else { 104 | mStateText.setText(""); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/MemoryArrayAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.ArrayAdapter; 25 | import android.widget.TextView; 26 | 27 | import java.util.List; 28 | 29 | public class MemoryArrayAdapter extends ArrayAdapter { 30 | 31 | enum Saved {YES, NO, NONE} 32 | 33 | public MemoryArrayAdapter(Context context, List objects) { 34 | super(context, 0, objects); 35 | } 36 | 37 | @Override 38 | public View getView(int position, View convertView, ViewGroup parent) { 39 | LayoutInflater inflater = (LayoutInflater) 40 | getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 41 | 42 | View view; 43 | if (convertView == null) { 44 | view = inflater.inflate(R.layout.memory_list_item, parent, false); 45 | } else { 46 | view = convertView; 47 | } 48 | 49 | initListItem(view, getItem(position), Saved.NONE); 50 | 51 | return view; 52 | 53 | } 54 | 55 | public void initListItem(View view, Phonon ph, Saved saved) { 56 | StringBuilder buf = new StringBuilder(); 57 | if (ph.getMinVol() != 100) { 58 | buf.append(ph.getMinVolText()); 59 | buf.append('\n'); 60 | buf.append(ph.getPeriodText()); 61 | if (saved != Saved.NONE) { 62 | buf.append('\n'); 63 | } 64 | } 65 | if (saved == Saved.YES) { 66 | buf.append('\u21E9'); // Down arrow. 67 | } else if (saved == Saved.NO) { 68 | buf.append(getContext().getString(R.string.unsaved)); 69 | } 70 | ((TextView) view.findViewById(R.id.text)).setText(buf.toString()); 71 | ((EqualizerViewLite) view.findViewById(R.id.EqualizerView)).setPhonon(ph); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/MemoryFragment.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.os.Bundle; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.View.OnClickListener; 24 | import android.view.ViewGroup; 25 | import android.widget.AdapterView; 26 | import android.widget.AdapterView.OnItemClickListener; 27 | 28 | import androidx.fragment.app.ListFragment; 29 | 30 | import com.mobeta.android.dslv.DragSortListView; 31 | import com.mobeta.android.dslv.DragSortListView.DropListener; 32 | import com.mobeta.android.dslv.DragSortListView.RemoveListener; 33 | 34 | import net.pmarks.chromadoze.MemoryArrayAdapter.Saved; 35 | 36 | public class MemoryFragment extends ListFragment implements 37 | OnItemClickListener, DropListener, RemoveListener { 38 | 39 | private View mHeaderView; 40 | private DragSortListView mDslv; 41 | private UIState mUiState; 42 | 43 | private MemoryArrayAdapter mAdapter; 44 | 45 | // This is basically the cached result of findScratchCopy(). 46 | private final TrackedPosition mScratchPos = new TrackedPosition(); 47 | 48 | @Override 49 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 50 | Bundle savedInstanceState) { 51 | mDslv = (DragSortListView) inflater.inflate(R.layout.memory_list, 52 | container, false); 53 | 54 | View v = inflater.inflate(R.layout.memory_list_item_top, null); 55 | View button = v.findViewById(R.id.save_button); 56 | button.setOnClickListener(new OnClickListener() { 57 | @Override 58 | public void onClick(View arg0) { 59 | // Clicked the "Save" button. 60 | final Phonon ph = mUiState.mScratchPhonon.makeMutableCopy(); 61 | mAdapter.insert(ph, 0); 62 | // Gray out the header row. 63 | setScratchPosAndDraw(findScratchCopy()); 64 | // Fake-click the header row. 65 | onItemClick(null, null, 0, 0); 66 | } 67 | }); 68 | mHeaderView = v; 69 | mDslv.addHeaderView(mHeaderView, null, true); 70 | 71 | mDslv.addHeaderView( 72 | inflater.inflate(R.layout.memory_list_divider, null), null, 73 | false); 74 | 75 | return mDslv; 76 | } 77 | 78 | @Override 79 | public void onViewCreated(View view, Bundle savedInstanceState) { 80 | super.onViewCreated(view, savedInstanceState); 81 | mUiState = ((ChromaDoze) getActivity()).getUIState(); 82 | 83 | mAdapter = new MemoryArrayAdapter(getActivity(), mUiState.mSavedPhonons); 84 | setListAdapter(mAdapter); 85 | 86 | mDslv.setOnItemClickListener(this); 87 | mDslv.setDropListener(this); 88 | mDslv.setRemoveListener(this); 89 | } 90 | 91 | @Override 92 | public void onResume() { 93 | super.onResume(); 94 | ((ChromaDoze) getActivity()).setFragmentId(FragmentIndex.ID_MEMORY); 95 | 96 | setScratchPosAndDraw(findScratchCopy()); 97 | syncActiveItem(false); 98 | } 99 | 100 | @Override 101 | public void drop(int from, int to) { 102 | if (from != to) { 103 | Phonon item = mAdapter.getItem(from); 104 | mAdapter.remove(item); 105 | mAdapter.insert(item, to); 106 | moveTrackedPositions(from, to, null); 107 | } 108 | } 109 | 110 | @Override 111 | public void remove(int which) { 112 | Phonon item = mAdapter.getItem(which); 113 | mAdapter.remove(item); 114 | moveTrackedPositions(which, TrackedPosition.NOWHERE, item); 115 | } 116 | 117 | private void moveTrackedPositions(int from, int to, Phonon deleted) { 118 | if ((to == TrackedPosition.NOWHERE) != (deleted != null)) { 119 | throw new IllegalArgumentException(); 120 | } 121 | try { 122 | if (mUiState.mActivePos.move(from, to)) { 123 | // Move the radio button. 124 | syncActiveItem(false); 125 | } 126 | } catch (TrackedPosition.Deleted e) { 127 | // The active item was deleted! 128 | // Move it to scratch, so it can keep playing. 129 | mUiState.mScratchPhonon = deleted.makeMutableCopy(); 130 | setScratchPosAndDraw(-1); 131 | mUiState.mActivePos.setPos(-1); 132 | syncActiveItem(true); 133 | return; 134 | } 135 | 136 | try { 137 | mScratchPos.move(from, to); 138 | } catch (TrackedPosition.Deleted e) { 139 | // The (inactive) scratch copy was deleted! 140 | // Reactivate the real scratch. 141 | setScratchPosAndDraw(-1); 142 | } 143 | } 144 | 145 | @Override 146 | public void onItemClick(AdapterView parent, View view, int position, 147 | long id) { 148 | mUiState.setActivePhonon(position == 0 ? 149 | -1 : position - mDslv.getHeaderViewsCount()); 150 | 151 | if (position == 0) { 152 | // User clicked on the scratch. Jump to a copy if one exists. 153 | syncActiveItem(true); 154 | } 155 | } 156 | 157 | // Header row should be grayed out whenever the scratch is redundant. 158 | private void setScratchPosAndDraw(int pos) { 159 | mScratchPos.setPos(pos); 160 | final boolean enabled = (pos == -1); 161 | mHeaderView.setEnabled(enabled); 162 | mAdapter.initListItem(mHeaderView, mUiState.mScratchPhonon, 163 | enabled ? Saved.NO : Saved.YES); 164 | } 165 | 166 | // If the scratch Phonon is unique, return -1. Otherwise, return the 167 | // copy's index within mArray. 168 | private int findScratchCopy() { 169 | final PhononMutable scratch = mUiState.mScratchPhonon; 170 | for (int i = 0; i < mUiState.mSavedPhonons.size(); i++) { 171 | if (mUiState.mSavedPhonons.get(i).fastEquals(scratch)) { 172 | return i; 173 | } 174 | } 175 | return -1; 176 | } 177 | 178 | private void syncActiveItem(boolean scrollThere) { 179 | // Determine which index to check. 180 | int index = mUiState.mActivePos.getPos(); 181 | if (index == -1) { 182 | index = mScratchPos.getPos(); 183 | mUiState.mActivePos.setPos(index); 184 | } 185 | 186 | // Convert the index to a list row. 187 | if (index == -1) { 188 | index = 0; 189 | } else { 190 | index += mDslv.getHeaderViewsCount(); 191 | } 192 | 193 | // Modify the UI. 194 | mDslv.setItemChecked(index, true); 195 | if (scrollThere) { 196 | mDslv.smoothScrollToPosition(index); 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/NoiseService.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.app.Notification; 21 | import android.app.PendingIntent; 22 | import android.app.Service; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.pm.ServiceInfo; 26 | import android.os.Build; 27 | import android.os.Handler; 28 | import android.os.IBinder; 29 | import android.os.Looper; 30 | import android.os.Message; 31 | import android.os.PowerManager; 32 | import android.os.Parcelable; 33 | import android.view.View; 34 | import android.widget.FrameLayout; 35 | import android.widget.RemoteViews; 36 | import android.widget.TextView; 37 | 38 | import androidx.core.app.NotificationChannelCompat; 39 | import androidx.core.app.NotificationCompat; 40 | import androidx.core.app.NotificationManagerCompat; 41 | 42 | import java.util.ArrayList; 43 | import java.util.Date; 44 | 45 | public class NoiseService extends Service { 46 | private static final int PERCENT_MSG = 1; 47 | 48 | // These must be accessed only from the main thread. 49 | private static int sLastPercent = -1; 50 | private static final ArrayList sPercentListeners = new ArrayList<>(); 51 | 52 | // Save the reason for the most recent stop/restart. In theory, it would 53 | // be more correct to use persistent storage, but the values should stick 54 | // around in RAM long enough for practical purposes. 55 | private static Date sStopTimestamp = null; 56 | private static int sStopReasonId = 0; 57 | 58 | private SampleShuffler mSampleShuffler; 59 | private SampleGenerator mSampleGenerator; 60 | private AudioFocusHelper mAudioFocusHelper; 61 | 62 | private static final int NOTIFY_ID = 1; 63 | private PowerManager.WakeLock mWakeLock; 64 | private static final String CHANNEL_ID = "chromadoze_default"; 65 | 66 | private int lastStartId = -1; 67 | 68 | private Handler mPercentHandler; 69 | 70 | private static class PercentHandler extends Handler { 71 | 72 | PercentHandler() { 73 | super(Looper.getMainLooper()); 74 | } 75 | 76 | @Override 77 | public void handleMessage(Message msg) { 78 | if (msg.what != PERCENT_MSG) { 79 | throw new AssertionError("Unexpected message: " + msg.what); 80 | } 81 | updatePercent(msg.arg1); 82 | } 83 | } 84 | 85 | @Override 86 | @SuppressWarnings("WakelockTimeout") 87 | public void onCreate() { 88 | // Set up a message handler in the main thread. 89 | mPercentHandler = new PercentHandler(); 90 | AudioParams params = new AudioParams(); 91 | mSampleShuffler = new SampleShuffler(params); 92 | mSampleGenerator = new SampleGenerator(this, params, mSampleShuffler); 93 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 94 | mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "chromadoze:NoiseService"); 95 | mWakeLock.acquire(); 96 | 97 | final CharSequence name = getString(R.string.channel_name); 98 | final String description = getString(R.string.channel_description); 99 | final int importance = NotificationManagerCompat.IMPORTANCE_LOW; 100 | NotificationManagerCompat.from(this).createNotificationChannel( 101 | new NotificationChannelCompat.Builder(CHANNEL_ID, importance) 102 | .setName(name) 103 | .setDescription(description) 104 | .build()); 105 | 106 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 107 | startForeground(NOTIFY_ID, makeNotify(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); 108 | } else { 109 | startForeground(NOTIFY_ID, makeNotify()); 110 | } 111 | 112 | // Note: This leaks memory if I use "this" instead of "getApplicationContext()". 113 | mAudioFocusHelper = new AudioFocusHelper( 114 | getApplicationContext(), mSampleShuffler.getVolumeListener()); 115 | } 116 | 117 | @SuppressWarnings("deprecation") 118 | private static T getParcelableExtraCompat(Intent intent, String name, Class clazz) { 119 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 120 | return intent.getParcelableExtra(name, clazz); 121 | } else { 122 | return intent.getParcelableExtra(name); 123 | } 124 | } 125 | 126 | @SuppressWarnings("deprecation") 127 | private void stopForegroundCompat() { 128 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 129 | stopForeground(STOP_FOREGROUND_REMOVE); 130 | } else { 131 | stopForeground(true); 132 | } 133 | } 134 | 135 | @Override 136 | public int onStartCommand(Intent intent, int flags, int startId) { 137 | // When multiple spectra arrive, only the latest should remain active. 138 | if (lastStartId >= 0) { 139 | stopSelf(lastStartId); 140 | lastStartId = -1; 141 | } 142 | 143 | // Handle the Stop intent. 144 | int stopReasonId = intent.getIntExtra("stopReasonId", 0); 145 | if (stopReasonId != 0) { 146 | saveStopReason(stopReasonId); 147 | stopSelf(startId); 148 | return START_NOT_STICKY; 149 | } 150 | 151 | // Notify the user that the OS restarted the process. 152 | if ((flags & START_FLAG_REDELIVERY) != 0) { 153 | saveStopReason(R.string.stop_reason_restarted); 154 | } 155 | 156 | SpectrumData spectrum = getParcelableExtraCompat(intent, "spectrum", SpectrumData.class); 157 | 158 | // Synchronous updates. 159 | mSampleShuffler.setAmpWave( 160 | intent.getFloatExtra("minvol", -1), 161 | intent.getFloatExtra("period", -1)); 162 | mSampleShuffler.getVolumeListener().setVolumeLevel( 163 | intent.getFloatExtra("volumeLimit", -1)); 164 | mAudioFocusHelper.setActive( 165 | !intent.getBooleanExtra("ignoreAudioFocus", false)); 166 | 167 | // Background updates. 168 | mSampleGenerator.updateSpectrum(spectrum); 169 | 170 | // If the kernel decides to kill this process, let Android restart it 171 | // using the most-recent spectrum. It's important that we call 172 | // stopSelf() with this startId when a replacement spectrum arrives, 173 | // or if we're stopping the service intentionally. 174 | lastStartId = startId; 175 | return START_REDELIVER_INTENT; 176 | } 177 | 178 | @Override 179 | public void onDestroy() { 180 | if (lastStartId != -1) { 181 | // This condition can be triggered from adb shell: 182 | // $ am stopservice net.pmarks.chromadoze/.NoiseService 183 | saveStopReason(R.string.stop_reason_mysterious); 184 | } 185 | 186 | mSampleGenerator.stopThread(); 187 | mSampleShuffler.stopThread(); 188 | 189 | mPercentHandler.removeMessages(PERCENT_MSG); 190 | updatePercent(-1); 191 | mAudioFocusHelper.setActive(false); 192 | stopForegroundCompat(); 193 | mWakeLock.release(); 194 | } 195 | 196 | @Override 197 | public IBinder onBind(Intent intent) { 198 | // Don't use binding. 199 | return null; 200 | } 201 | 202 | // Create an icon for the notification bar. 203 | private Notification makeNotify() { 204 | NotificationCompat.Builder b = new NotificationCompat.Builder(this, CHANNEL_ID) 205 | .setSmallIcon(R.drawable.ic_stat_bars) 206 | .setWhen(0) 207 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 208 | .setContentIntent(PendingIntent.getActivity( 209 | this, 210 | 0, 211 | new Intent(this, ChromaDoze.class) 212 | .setAction(Intent.ACTION_MAIN) 213 | .addCategory(Intent.CATEGORY_LAUNCHER), 214 | PendingIntent.FLAG_IMMUTABLE)); 215 | 216 | RemoteViews rv = new RemoteViews( 217 | getPackageName(), R.layout.notification_with_stop_button); 218 | PendingIntent pendingIntent = PendingIntent.getService( 219 | this, 220 | 0, 221 | newStopIntent(this, R.string.stop_reason_notification), 222 | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); 223 | rv.setOnClickPendingIntent(R.id.stop_button, pendingIntent); 224 | 225 | // Temporarily inflate the notification, to copy colors from its default style. 226 | final View inflated = rv.apply(this, new FrameLayout(this)); 227 | final TextView titleText = inflated.findViewById(R.id.title); 228 | rv.setInt(R.id.divider, "setBackgroundColor", titleText.getTextColors().getDefaultColor()); 229 | rv.setInt(R.id.stop_button_square, "setBackgroundColor", titleText.getTextColors().getDefaultColor()); 230 | 231 | // It would be nice if there were some way to omit the "expander affordance", 232 | // but this seems good enough. 233 | b.setCustomContentView(rv); 234 | b.setStyle(new NotificationCompat.DecoratedCustomViewStyle()); 235 | 236 | return b.build(); 237 | } 238 | 239 | // Call updatePercent() from any thread. 240 | public void updatePercentAsync(int percent) { 241 | mPercentHandler.removeMessages(PERCENT_MSG); 242 | Message m = Message.obtain(mPercentHandler, PERCENT_MSG); 243 | m.arg1 = percent; 244 | m.sendToTarget(); 245 | } 246 | 247 | // If connected, notify the main activity of our progress. 248 | // This must run in the main thread. 249 | private static void updatePercent(int percent) { 250 | for (PercentListener listener : sPercentListeners) { 251 | listener.onNoiseServicePercentChange(percent, sStopTimestamp, sStopReasonId); 252 | } 253 | sLastPercent = percent; 254 | } 255 | 256 | // Connect the main activity so it receives progress updates. 257 | // This must run in the main thread. 258 | public static void addPercentListener(PercentListener listener) { 259 | sPercentListeners.add(listener); 260 | listener.onNoiseServicePercentChange(sLastPercent, sStopTimestamp, sStopReasonId); 261 | } 262 | 263 | public static void removePercentListener(PercentListener listener) { 264 | if (!sPercentListeners.remove(listener)) { 265 | throw new IllegalStateException(); 266 | } 267 | } 268 | 269 | public interface PercentListener { 270 | void onNoiseServicePercentChange(int percent, Date stopTimestamp, int stopReasonId); 271 | } 272 | 273 | private static Intent newStopIntent(Context ctx, int stopReasonId) { 274 | return new Intent(ctx, NoiseService.class).putExtra("stopReasonId", stopReasonId); 275 | } 276 | 277 | public static void stopNow(Context ctx, int stopReasonId) { 278 | try { 279 | ctx.startService(newStopIntent(ctx, stopReasonId)); 280 | } catch (IllegalStateException e) { 281 | // This can be triggered by running "adb shell input keyevent 86" when the app 282 | // is not running. We ignore it, because in that case there's nothing to stop. 283 | } 284 | } 285 | 286 | private static void saveStopReason(int stopReasonId) { 287 | sStopTimestamp = new Date(); 288 | sStopReasonId = stopReasonId; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/OptionsFragment.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2011 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.os.Bundle; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.CompoundButton; 25 | import android.widget.CompoundButton.OnCheckedChangeListener; 26 | import android.widget.SeekBar; 27 | import android.widget.SeekBar.OnSeekBarChangeListener; 28 | import android.widget.TextView; 29 | 30 | import androidx.appcompat.widget.SwitchCompat; 31 | import androidx.fragment.app.Fragment; 32 | 33 | public class OptionsFragment extends Fragment implements OnSeekBarChangeListener, OnCheckedChangeListener { 34 | private UIState mUiState; 35 | private SeekBar mMinVolSeek; 36 | private TextView mMinVolText; 37 | private SeekBar mPeriodSeek; 38 | private TextView mPeriodText; 39 | private SwitchCompat mAutoPlayCheck; 40 | private SwitchCompat mIgnoreAudioFocusCheck; 41 | private SwitchCompat mVolumeLimitCheck; 42 | private SeekBar mVolumeLimitSeek; 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 46 | Bundle savedInstanceState) { 47 | View v = inflater.inflate(R.layout.options_fragment, container, false); 48 | 49 | mMinVolSeek = (SeekBar) v.findViewById(R.id.MinVolSeek); 50 | mMinVolText = (TextView) v.findViewById(R.id.MinVolText); 51 | mPeriodSeek = (SeekBar) v.findViewById(R.id.PeriodSeek); 52 | mPeriodText = (TextView) v.findViewById(R.id.PeriodText); 53 | 54 | mAutoPlayCheck = (SwitchCompat) v.findViewById(R.id.AutoPlayCheck); 55 | 56 | mIgnoreAudioFocusCheck = (SwitchCompat) v.findViewById(R.id.IgnoreAudioFocusCheck); 57 | mVolumeLimitCheck = (SwitchCompat) v.findViewById(R.id.VolumeLimitCheck); 58 | mVolumeLimitSeek = (SeekBar) v.findViewById(R.id.VolumeLimitSeek); 59 | 60 | return v; 61 | } 62 | 63 | @Override 64 | public void onViewCreated(View view, Bundle savedInstanceState) { 65 | super.onViewCreated(view, savedInstanceState); 66 | mUiState = ((ChromaDoze) getActivity()).getUIState(); 67 | final Phonon ph = mUiState.getPhonon(); 68 | 69 | mMinVolText.setText(ph.getMinVolText()); 70 | mMinVolSeek.setProgress(ph.getMinVol()); 71 | mMinVolSeek.setOnSeekBarChangeListener(this); 72 | 73 | mPeriodText.setText(ph.getPeriodText()); 74 | mPeriodSeek.setProgress(ph.getPeriod()); 75 | // When the volume is at 100%, disable the period bar. 76 | mPeriodSeek.setEnabled(ph.getMinVol() != 100); 77 | mPeriodSeek.setMax(PhononMutable.PERIOD_MAX); 78 | mPeriodSeek.setOnSeekBarChangeListener(this); 79 | 80 | mAutoPlayCheck.setChecked(mUiState.getAutoPlay()); 81 | mAutoPlayCheck.setOnCheckedChangeListener(this); 82 | 83 | mIgnoreAudioFocusCheck.setChecked(mUiState.getIgnoreAudioFocus()); 84 | mIgnoreAudioFocusCheck.setOnCheckedChangeListener(this); 85 | 86 | mVolumeLimitCheck.setOnCheckedChangeListener(this); 87 | mVolumeLimitSeek.setMax(UIState.MAX_VOLUME); 88 | mVolumeLimitSeek.setOnSeekBarChangeListener(this); 89 | redrawVolumeLimit(); 90 | } 91 | 92 | @Override 93 | public void onResume() { 94 | super.onResume(); 95 | ((ChromaDoze) getActivity()).setFragmentId(FragmentIndex.ID_OPTIONS); 96 | } 97 | 98 | @Override 99 | public void onProgressChanged(SeekBar seekBar, int progress, 100 | boolean fromUser) { 101 | if (!fromUser) { 102 | return; 103 | } 104 | if (seekBar == mVolumeLimitSeek) { 105 | mUiState.setVolumeLimit(progress); 106 | redrawVolumeLimit(); 107 | } else { 108 | final PhononMutable phm = mUiState.getPhononMutable(); 109 | if (seekBar == mMinVolSeek) { 110 | phm.setMinVol(progress); 111 | mMinVolText.setText(phm.getMinVolText()); 112 | mPeriodSeek.setEnabled(progress != 100); 113 | } else if (seekBar == mPeriodSeek) { 114 | phm.setPeriod(progress); 115 | mPeriodText.setText(phm.getPeriodText()); 116 | } 117 | } 118 | mUiState.sendIfDirty(); 119 | } 120 | 121 | @Override 122 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 123 | if (buttonView == mAutoPlayCheck) { 124 | mUiState.setAutoPlay(isChecked, true); 125 | } else if (buttonView == mIgnoreAudioFocusCheck) { 126 | mUiState.setIgnoreAudioFocus(isChecked); 127 | } else if (buttonView == mVolumeLimitCheck) { 128 | mUiState.setVolumeLimitEnabled(isChecked); 129 | redrawVolumeLimit(); 130 | } 131 | mUiState.sendIfDirty(); 132 | } 133 | 134 | @Override 135 | public void onStartTrackingTouch(SeekBar seekBar) { 136 | } 137 | 138 | @Override 139 | public void onStopTrackingTouch(SeekBar seekBar) { 140 | } 141 | 142 | private void redrawVolumeLimit() { 143 | boolean enabled = mUiState.getVolumeLimitEnabled(); 144 | mVolumeLimitCheck.setChecked(enabled); 145 | mVolumeLimitSeek.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); 146 | mVolumeLimitSeek.setProgress(mUiState.getVolumeLimit()); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/Phonon.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Intent; 21 | 22 | // Read-only view of a PhononMutable 23 | public interface Phonon { 24 | public String toJSON(); 25 | 26 | public boolean isSilent(); 27 | 28 | public float getBar(int band); 29 | 30 | public int getMinVol(); 31 | 32 | public String getMinVolText(); 33 | 34 | public int getPeriod(); 35 | 36 | public String getPeriodText(); 37 | 38 | public PhononMutable makeMutableCopy(); 39 | 40 | public void writeIntent(Intent intent); 41 | 42 | public boolean fastEquals(Phonon other); 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/PhononMutable.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Intent; 21 | import android.content.SharedPreferences; 22 | 23 | import org.json.JSONArray; 24 | import org.json.JSONException; 25 | import org.json.JSONObject; 26 | 27 | import java.util.Arrays; 28 | import java.util.Locale; 29 | 30 | // A Phonon represents a collection of user-tweakable sound 31 | // information, presented as a single row in the "Memory" view. 32 | // 33 | // Supported operations: 34 | // - Convert to/from JSON for storage. 35 | // - Efficient equality testing. 36 | // - Convert to SpectrumData for playback. 37 | // - Get and set sound-related values. 38 | public class PhononMutable implements Phonon { 39 | private static final int BAND_COUNT = SpectrumData.BAND_COUNT; 40 | 41 | // The current value of each bar, [0, 1023] 42 | private static final short BAR_MAX = 1023; 43 | private final short mBars[] = new short[BAND_COUNT]; 44 | 45 | private int mMinVol = 100; 46 | 47 | public static final int PERIOD_MAX = 53; // Maps to 60 seconds. 48 | private int mPeriod = 18; // Maps to 1 second 49 | 50 | private boolean mDirty = true; 51 | private int mHash; 52 | 53 | public void resetToDefault() { 54 | for (int i = 0; i < BAND_COUNT; i++) { 55 | setBar(i, .5f); 56 | } 57 | mMinVol = 100; 58 | mPeriod = 18; 59 | cleanMe(); 60 | } 61 | 62 | // Load data from <= Chroma Doze 2.2. 63 | public boolean loadFromLegacyPrefs(SharedPreferences pref) { 64 | if (pref.getFloat("barHeight0", -1) < 0) { 65 | return false; 66 | } 67 | for (int i = 0; i < BAND_COUNT; i++) { 68 | setBar(i, pref.getFloat("barHeight" + i, .5f)); 69 | } 70 | setMinVol(pref.getInt("minVol", 100)); 71 | setPeriod(pref.getInt("period", 18)); 72 | cleanMe(); 73 | return true; 74 | } 75 | 76 | public boolean loadFromJSON(String jsonString) { 77 | if (jsonString == null) { 78 | return false; 79 | } 80 | try { 81 | JSONObject j = new JSONObject(jsonString); 82 | setMinVol(j.getInt("minvol")); 83 | setPeriod(j.getInt("period")); 84 | JSONArray jBars = j.getJSONArray("bars"); 85 | for (int i = 0; i < BAND_COUNT; i++) { 86 | final int b = jBars.getInt(i); 87 | if (!(0 <= b && b <= BAR_MAX)) { 88 | return false; 89 | } 90 | mBars[i] = (short) b; 91 | } 92 | } catch (JSONException e) { 93 | return false; 94 | } 95 | cleanMe(); 96 | return true; 97 | } 98 | 99 | // Storing everything as text might be useful if I ever want 100 | // to do an export feature. 101 | @Override 102 | public String toJSON() { 103 | try { 104 | JSONObject j = new JSONObject(); 105 | JSONArray jBars = new JSONArray(); 106 | for (short s : mBars) { 107 | jBars.put(s); 108 | } 109 | j.put("bars", jBars); 110 | j.put("minvol", mMinVol); 111 | j.put("period", mPeriod); 112 | return j.toString(); 113 | } catch (JSONException e) { 114 | throw new RuntimeException("impossible"); 115 | } 116 | } 117 | 118 | // band: The index number of the bar. 119 | // value: Between 0 and 1. 120 | public void setBar(int band, float value) { 121 | // Map from 0..1 to a discrete short. 122 | short sval; 123 | if (value <= 0f) { 124 | sval = 0; 125 | } else if (value >= 1f) { 126 | sval = BAR_MAX; 127 | } else { 128 | sval = (short) (value * BAR_MAX); 129 | } 130 | if (mBars[band] != sval) { 131 | mBars[band] = sval; 132 | mDirty = true; 133 | } 134 | } 135 | 136 | @Override 137 | public float getBar(int band) { 138 | return mBars[band] / (float) BAR_MAX; 139 | } 140 | 141 | private float[] getAllBars() { 142 | float[] out = new float[BAND_COUNT]; 143 | for (int i = 0; i < BAND_COUNT; i++) { 144 | out[i] = getBar(i); 145 | } 146 | return out; 147 | } 148 | 149 | // Return true if all equalizer bars are set to zero. 150 | @Override 151 | public boolean isSilent() { 152 | for (int i = 0; i < BAND_COUNT; i++) { 153 | if (mBars[i] > 0) { 154 | return false; 155 | } 156 | } 157 | return true; 158 | } 159 | 160 | // Range: [0, 100] 161 | public void setMinVol(int minVol) { 162 | if (minVol < 0) { 163 | minVol = 0; 164 | } else if (minVol > 100) { 165 | minVol = 100; 166 | } 167 | if (minVol != mMinVol) { 168 | mMinVol = minVol; 169 | mDirty = true; 170 | } 171 | } 172 | 173 | @Override 174 | public int getMinVol() { 175 | return mMinVol; 176 | } 177 | 178 | @Override 179 | public String getMinVolText() { 180 | return mMinVol + "%"; 181 | } 182 | 183 | public void setPeriod(int period) { 184 | if (period < 0) { 185 | period = 0; 186 | } else if (period > PERIOD_MAX) { 187 | period = PERIOD_MAX; 188 | } 189 | if (period != mPeriod) { 190 | mPeriod = period; 191 | mDirty = true; 192 | } 193 | } 194 | 195 | // This gets the slider position. 196 | @Override 197 | public int getPeriod() { 198 | return mPeriod; 199 | } 200 | 201 | @Override 202 | public String getPeriodText() { 203 | // This probably isn't very i18n friendly. 204 | float s = getPeriodSeconds(); 205 | if (s >= 1f) { 206 | return String.format(Locale.getDefault(), "%.2g sec", s); 207 | } else { 208 | return String.format(Locale.getDefault(), "%d ms", Math.round(s * 1000)); 209 | } 210 | } 211 | 212 | private float getPeriodSeconds() { 213 | // This is a somewhat human-friendly mapping from 214 | // scroll position to seconds. 215 | if (mPeriod < 9) { 216 | // 10ms, 20ms, ..., 90ms 217 | return (mPeriod + 1) * .010f; 218 | } else if (mPeriod < 18) { 219 | // 100ms, 200ms, ..., 900ms 220 | return (mPeriod - 9 + 1) * .100f; 221 | } else if (mPeriod < 36) { 222 | // 1.0s, 1.5s, ..., 9.5s 223 | return (mPeriod - 18 + 2) * .5f; 224 | } else if (mPeriod < 45) { 225 | // 10, 11, ..., 19 226 | return (mPeriod - 36 + 10) * 1f; 227 | } else { 228 | // 20, 25, 30, ... 60 229 | return (mPeriod - 45 + 4) * 5f; 230 | } 231 | } 232 | 233 | @Override 234 | public PhononMutable makeMutableCopy() { 235 | if (mDirty) { 236 | throw new IllegalStateException(); 237 | } 238 | PhononMutable c = new PhononMutable(); 239 | System.arraycopy(mBars, 0, c.mBars, 0, BAND_COUNT); 240 | c.mMinVol = mMinVol; 241 | c.mPeriod = mPeriod; 242 | c.mHash = mHash; 243 | c.mDirty = false; 244 | return c; 245 | } 246 | 247 | public boolean isDirty() { 248 | return mDirty; 249 | } 250 | 251 | // We assume that all Intents will be sent to the service, 252 | // so this also clears the dirty bit. 253 | @Override 254 | public void writeIntent(Intent intent) { 255 | intent.putExtra("spectrum", new SpectrumData(getAllBars())); 256 | intent.putExtra("minvol", mMinVol / 100f); 257 | intent.putExtra("period", getPeriodSeconds()); 258 | cleanMe(); 259 | } 260 | 261 | private void cleanMe() { 262 | int h = Arrays.hashCode(mBars); 263 | h = 31 * h + mMinVol; 264 | h = 31 * h + mPeriod; 265 | mHash = h; 266 | mDirty = false; 267 | } 268 | 269 | @Override 270 | public boolean fastEquals(Phonon other) { 271 | PhononMutable o = (PhononMutable) other; 272 | if (mDirty || o.mDirty) { 273 | throw new IllegalStateException(); 274 | } 275 | if (this == o) { 276 | return true; 277 | } 278 | return (mHash == o.mHash && 279 | mMinVol == o.mMinVol && 280 | mPeriod == o.mPeriod && 281 | Arrays.equals(mBars, o.mBars)); 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/SampleGenerator.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.os.Process; 21 | import android.os.SystemClock; 22 | 23 | import org.jtransforms.dct.FloatDCT_1D; 24 | 25 | class SampleGenerator { 26 | private final NoiseService mNoiseService; 27 | private final AudioParams mParams; 28 | private final SampleShuffler mSampleShuffler; 29 | private final Thread mWorkerThread; 30 | 31 | // Communication variables; must be synchronized. 32 | private boolean mStopping; 33 | private SpectrumData mPendingSpectrum; 34 | 35 | // Variables accessed from the thread only. 36 | private int mLastDctSize = -1; 37 | private FloatDCT_1D mDct; 38 | private final XORShiftRandom mRandom = new XORShiftRandom(); // Not thread safe. 39 | 40 | public SampleGenerator(NoiseService noiseService, AudioParams params, 41 | SampleShuffler sampleShuffler) { 42 | mNoiseService = noiseService; 43 | mParams = params; 44 | mSampleShuffler = sampleShuffler; 45 | 46 | mWorkerThread = new Thread("SampleGeneratorThread") { 47 | @Override 48 | public void run() { 49 | try { 50 | threadLoop(); 51 | } catch (StopException e) { 52 | } 53 | } 54 | }; 55 | mWorkerThread.start(); 56 | } 57 | 58 | public void stopThread() { 59 | synchronized (this) { 60 | mStopping = true; 61 | notify(); 62 | } 63 | try { 64 | mWorkerThread.join(); 65 | } catch (InterruptedException e) { 66 | } 67 | } 68 | 69 | public synchronized void updateSpectrum(SpectrumData spectrum) { 70 | mPendingSpectrum = spectrum; 71 | notify(); 72 | } 73 | 74 | private void threadLoop() throws StopException { 75 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 76 | 77 | // Chunk-making progress: 78 | final SampleGeneratorState state = new SampleGeneratorState(); 79 | SpectrumData spectrum = null; 80 | long waitMs = -1; 81 | 82 | while (true) { 83 | // This does one of 3 things: 84 | // - Throw StopException if stopThread() was called. 85 | // - Check if a new spectrum is waiting. 86 | // - Block if there's no work to do. 87 | final SpectrumData newSpectrum = popPendingSpectrum(waitMs); 88 | if (newSpectrum != null && !newSpectrum.sameSpectrum(spectrum)) { 89 | spectrum = newSpectrum; 90 | state.reset(); 91 | mNoiseService.updatePercentAsync(state.getPercent()); 92 | } else if (waitMs == -1) { 93 | // Nothing changed. Keep waiting. 94 | continue; 95 | } 96 | 97 | final long startMs = SystemClock.elapsedRealtime(); 98 | 99 | // Generate the next chunk of sound. 100 | float[] dctData = doIDCT(state.getChunkSize(), spectrum); 101 | if (mSampleShuffler.handleChunk(dctData, state.getStage())) { 102 | // Not dropped. 103 | state.advance(); 104 | mNoiseService.updatePercentAsync(state.getPercent()); 105 | } 106 | 107 | // Avoid burning the CPU while the user is scrubbing. For the 108 | // first couple large chunks, the next chunk should be ready 109 | // when this one is ~75% finished playing. 110 | final long sleepTargetMs = state.getSleepTargetMs(mParams.SAMPLE_RATE); 111 | final long elapsedMs = SystemClock.elapsedRealtime() - startMs; 112 | waitMs = sleepTargetMs - elapsedMs; 113 | if (waitMs < 0) waitMs = 0; 114 | if (waitMs > sleepTargetMs) waitMs = sleepTargetMs; 115 | 116 | if (state.done()) { 117 | // No chunks left; save RAM. 118 | mDct = null; 119 | mLastDctSize = -1; 120 | waitMs = -1; 121 | } 122 | } 123 | } 124 | 125 | private synchronized SpectrumData popPendingSpectrum(long waitMs) 126 | throws StopException { 127 | if (waitMs != 0 && !mStopping && mPendingSpectrum == null) { 128 | // Wait once. The retry loop is in the caller. 129 | try { 130 | if (waitMs < 0) { 131 | wait(/*forever*/); 132 | } else { 133 | wait(waitMs); 134 | } 135 | } catch (InterruptedException e) { 136 | } 137 | } 138 | if (mStopping) { 139 | throw new StopException(); 140 | } 141 | try { 142 | return mPendingSpectrum; 143 | } finally { 144 | mPendingSpectrum = null; 145 | } 146 | } 147 | 148 | private float[] doIDCT(int dctSize, SpectrumData spectrum) { 149 | if (dctSize != mLastDctSize) { 150 | mDct = new FloatDCT_1D(dctSize); 151 | mLastDctSize = dctSize; 152 | } 153 | float[] dctData = new float[dctSize]; 154 | 155 | spectrum.fill(dctData, mParams.SAMPLE_RATE); 156 | 157 | // Multiply by a block of white noise. 158 | for (int i = 0; i < dctSize; ) { 159 | long rand = mRandom.nextLong(); 160 | for (int b = 0; b < 8; b++) { 161 | dctData[i++] *= (byte) rand / 128f; 162 | rand >>= 8; 163 | } 164 | } 165 | 166 | mDct.inverse(dctData, false); 167 | return dctData; 168 | } 169 | 170 | private static class StopException extends Exception { 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/SampleGeneratorState.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | class SampleGeneratorState { 21 | // List of possible chunk stages. 22 | public static final int S_FIRST_SMALL = 0; 23 | public static final int S_OTHER_SMALL = 1; 24 | public static final int S_FIRST_VOLUME = 2; 25 | public static final int S_OTHER_VOLUME = 3; 26 | public static final int S_LAST_VOLUME = 4; 27 | public static final int S_LARGE_NOCLIP = 5; 28 | 29 | // How many small preview chunks to generate at first. 30 | private static final int N_SMALL_CHUNKS = 4; 31 | 32 | // How many final full-size chunks to generate. 33 | private static final int N_LARGE_CHUNKS = 20; 34 | 35 | // How many chunks overall. 36 | private static final int N_TOTAL_CHUNKS = N_SMALL_CHUNKS + N_LARGE_CHUNKS; 37 | 38 | // How many large chunks to use for estimating the global volume. 39 | private static final int N_VOLUME_CHUNKS = 4; 40 | 41 | // Size of small/large chunks, in samples. 42 | private static final int SMALL_CHUNK_SIZE = 8192; 43 | private static final int LARGE_CHUNK_SIZE = 65536; 44 | 45 | // Begin in the "done" state. 46 | private int mChunkNumber = N_TOTAL_CHUNKS; 47 | 48 | public void reset() { 49 | mChunkNumber = 0; 50 | } 51 | 52 | public void advance() { 53 | mChunkNumber++; 54 | } 55 | 56 | public boolean done() { 57 | return mChunkNumber >= N_TOTAL_CHUNKS; 58 | } 59 | 60 | public int getStage() { 61 | if (mChunkNumber < N_SMALL_CHUNKS) { 62 | // Small chunk. 63 | switch (mChunkNumber) { 64 | case 0: 65 | return S_FIRST_SMALL; 66 | default: 67 | return S_OTHER_SMALL; 68 | } 69 | } else if (mChunkNumber < N_SMALL_CHUNKS + N_VOLUME_CHUNKS) { 70 | // Large chunk, with volume computation. 71 | switch (mChunkNumber) { 72 | case N_SMALL_CHUNKS: 73 | return S_FIRST_VOLUME; 74 | case N_SMALL_CHUNKS + N_VOLUME_CHUNKS - 1: 75 | return S_LAST_VOLUME; 76 | default: 77 | return S_OTHER_VOLUME; 78 | } 79 | } else { 80 | // Large chunk, volume already set. 81 | return S_LARGE_NOCLIP; 82 | } 83 | } 84 | 85 | public int getPercent() { 86 | return mChunkNumber * 100 / N_TOTAL_CHUNKS; 87 | } 88 | 89 | public int getChunkSize() { 90 | return mChunkNumber < N_SMALL_CHUNKS ? SMALL_CHUNK_SIZE : LARGE_CHUNK_SIZE; 91 | } 92 | 93 | // For the first couple large chunks, returns 75% of the chunk duration. 94 | public long getSleepTargetMs(int sampleRate) { 95 | final int i = mChunkNumber - N_SMALL_CHUNKS; 96 | if (0 <= i && i < 2) { 97 | return 750 * LARGE_CHUNK_SIZE / sampleRate; 98 | } 99 | return 0; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/SpectrumData.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.os.Parcel; 21 | import android.os.Parcelable; 22 | 23 | // SpectrumData is a Phonon translated into "machine readable" form. 24 | // 25 | // In other words, the values here are suitable for generating noise, 26 | // and not for storage or rendering UI elements. 27 | 28 | public class SpectrumData implements Parcelable { 29 | public static final Parcelable.Creator CREATOR 30 | = new Parcelable.Creator() { 31 | @Override 32 | public SpectrumData createFromParcel(Parcel in) { 33 | return new SpectrumData(in); 34 | } 35 | 36 | @Override 37 | public SpectrumData[] newArray(int size) { 38 | return new SpectrumData[size]; 39 | } 40 | }; 41 | 42 | private static final float MIN_FREQ = 100; 43 | private static final float MAX_FREQ = 20000; 44 | public static final int BAND_COUNT = 32; 45 | 46 | // The frequency of the edges between each bar. 47 | private static final int[] EDGE_FREQS = calculateEdgeFreqs(); 48 | 49 | private final float[] mData; 50 | 51 | private static int[] calculateEdgeFreqs() { 52 | int[] edgeFreqs = new int[BAND_COUNT + 1]; 53 | float range = MAX_FREQ / MIN_FREQ; 54 | for (int i = 0; i <= BAND_COUNT; i++) { 55 | edgeFreqs[i] = (int) (MIN_FREQ * Math.pow(range, (float) i / BAND_COUNT)); 56 | } 57 | return edgeFreqs; 58 | } 59 | 60 | public SpectrumData(float[] donateBars) { 61 | if (donateBars.length != BAND_COUNT) { 62 | throw new RuntimeException("Incorrect number of bands"); 63 | } 64 | mData = donateBars; 65 | for (int i = 0; i < BAND_COUNT; i++) { 66 | if (mData[i] <= 0f) { 67 | mData[i] = 0f; 68 | } else { 69 | mData[i] = 0.001f * (float) Math.pow(1000, mData[i]); 70 | } 71 | } 72 | } 73 | 74 | private SpectrumData(Parcel in) { 75 | mData = new float[BAND_COUNT]; 76 | in.readFloatArray(mData); 77 | } 78 | 79 | @Override 80 | public int describeContents() { 81 | return 0; 82 | } 83 | 84 | @Override 85 | public void writeToParcel(Parcel dest, int flags) { 86 | dest.writeFloatArray(mData); 87 | } 88 | 89 | public void fill(float[] out, int sampleRate) { 90 | int maxFreq = sampleRate / 2; 91 | subFill(out, 0f, 0, EDGE_FREQS[0], maxFreq); 92 | for (int i = 0; i < BAND_COUNT; i++) { 93 | subFill(out, mData[i], EDGE_FREQS[i], EDGE_FREQS[i + 1], maxFreq); 94 | } 95 | subFill(out, 0f, EDGE_FREQS[BAND_COUNT], maxFreq, maxFreq); 96 | } 97 | 98 | private void subFill(float[] out, float setValue, int startFreq, int limitFreq, int maxFreq) { 99 | // This min() applies if the sample rate is below 40kHz. 100 | int limitIndex = Math.min(out.length, limitFreq * out.length / maxFreq); 101 | for (int i = startFreq * out.length / maxFreq; i < limitIndex; i++) { 102 | out[i] = setValue; 103 | } 104 | } 105 | 106 | public boolean sameSpectrum(SpectrumData other) { 107 | if (other == null) { 108 | return false; 109 | } 110 | for (int i = 0; i < BAND_COUNT; i++) { 111 | if (mData[i] != other.mData[i]) { 112 | return false; 113 | } 114 | } 115 | return true; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/TheBackupAgent.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.app.backup.BackupAgentHelper; 21 | import android.app.backup.SharedPreferencesBackupHelper; 22 | 23 | // This implements a BackupAgent, not because the data is particularly 24 | // valuable, but because Android 6.0 will randomly kill the service in the 25 | // middle of the night to perform a "fullbackup" if we don't offer an 26 | // alternative (or disable backups entirely.) 27 | public class TheBackupAgent extends BackupAgentHelper { 28 | private static final String PREF_BACKUP_KEY = "pref"; 29 | 30 | @Override 31 | public void onCreate() { 32 | addHelper(PREF_BACKUP_KEY, new SharedPreferencesBackupHelper( 33 | this, ChromaDoze.PREF_NAME)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/TrackedPosition.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | // TrackedPosition keeps track of a single position within 21 | // an ArrayList, even as rows get rearranged by the DSLV. 22 | 23 | public class TrackedPosition { 24 | // Start at -1, so the scratch position can be tracked as well. 25 | private static final int MINVAL = -1; 26 | 27 | // NOWHERE must be larger than any other value, for the math to work. 28 | public static final int NOWHERE = Integer.MAX_VALUE; 29 | 30 | private int mPos = NOWHERE; 31 | 32 | void setPos(int p) { 33 | if (!(MINVAL <= p && p < NOWHERE)) { 34 | throw new IllegalArgumentException("Out of range"); 35 | } 36 | mPos = p; 37 | } 38 | 39 | int getPos() { 40 | return mPos; 41 | } 42 | 43 | // Move some item in the list. 44 | // If this position moved to nowhere, throw Deleted. 45 | // Otherwise, return true if this position moved at all. 46 | boolean move(int from, int to) throws Deleted { 47 | // from to result 48 | // ---- -- ------ 49 | // = = noop 50 | // = * omg! 51 | // < >= -1 52 | // < < noop 53 | // > <= +1 54 | // > > noop 55 | if (mPos == NOWHERE) { 56 | throw new IllegalStateException(); 57 | } 58 | if (from < 0 || to < 0) { 59 | throw new IllegalArgumentException(); 60 | } 61 | if (from == mPos) { 62 | if (to != mPos) { 63 | mPos = to; 64 | if (mPos == NOWHERE) { 65 | throw new Deleted(); 66 | } 67 | return true; 68 | } 69 | } else if (from < mPos) { 70 | if (to >= mPos) { 71 | mPos -= 1; 72 | if (mPos < MINVAL) { 73 | throw new IllegalStateException(); 74 | } 75 | return true; 76 | } 77 | } else if (from > mPos) { 78 | if (to <= mPos) { 79 | mPos += 1; 80 | if (mPos >= NOWHERE) { 81 | throw new IllegalStateException(); 82 | } 83 | return true; 84 | } 85 | } else { 86 | throw new RuntimeException(); 87 | } 88 | return false; 89 | } 90 | 91 | public static class Deleted extends Exception { 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/UIState.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2011 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | package net.pmarks.chromadoze; 19 | 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.SharedPreferences; 23 | 24 | import androidx.core.content.ContextCompat; 25 | 26 | import java.util.ArrayList; 27 | 28 | public class UIState { 29 | 30 | private final Context mContext; 31 | 32 | private boolean mLocked = false; 33 | private boolean mLockBusy = false; 34 | private final ArrayList mLockListeners = new ArrayList<>(); 35 | 36 | public final TrackedPosition mActivePos = new TrackedPosition(); 37 | public PhononMutable mScratchPhonon; 38 | public ArrayList mSavedPhonons; 39 | 40 | public UIState(Context context) { 41 | mContext = context; 42 | } 43 | 44 | private boolean mDirty = false; 45 | private boolean mAutoPlay; 46 | private boolean mIgnoreAudioFocus; 47 | private boolean mVolumeLimitEnabled; 48 | private int mVolumeLimit; 49 | public static final int MAX_VOLUME = 100; 50 | 51 | public void saveState(SharedPreferences.Editor pref) { 52 | pref.putBoolean("locked", mLocked); 53 | pref.putBoolean("autoPlay", mAutoPlay); 54 | pref.putBoolean("ignoreAudioFocus", mIgnoreAudioFocus); 55 | pref.putInt("volumeLimit", getVolumeLimit()); 56 | pref.putString("phononS", mScratchPhonon.toJSON()); 57 | for (int i = 0; i < mSavedPhonons.size(); i++) { 58 | pref.putString("phonon" + i, mSavedPhonons.get(i).toJSON()); 59 | mSavedPhonons.get(i); 60 | } 61 | pref.putInt("activePhonon", mActivePos.getPos()); 62 | } 63 | 64 | public void loadState(SharedPreferences pref) { 65 | mLocked = pref.getBoolean("locked", false); 66 | setAutoPlay(pref.getBoolean("autoPlay", false), false); 67 | setIgnoreAudioFocus(pref.getBoolean("ignoreAudioFocus", false)); 68 | setVolumeLimit(pref.getInt("volumeLimit", MAX_VOLUME)); 69 | setVolumeLimitEnabled(mVolumeLimit != MAX_VOLUME); 70 | 71 | // Load the scratch phonon. 72 | mScratchPhonon = new PhononMutable(); 73 | if (mScratchPhonon.loadFromJSON(pref.getString("phononS", null))) { 74 | } else if (mScratchPhonon.loadFromLegacyPrefs(pref)) { 75 | } else { 76 | mScratchPhonon.resetToDefault(); 77 | } 78 | 79 | // Load the saved phonons. 80 | mSavedPhonons = new ArrayList<>(); 81 | for (int i = 0; i < TrackedPosition.NOWHERE; i++) { 82 | PhononMutable phm = new PhononMutable(); 83 | if (!phm.loadFromJSON(pref.getString("phonon" + i, null))) { 84 | break; 85 | } 86 | mSavedPhonons.add(phm); 87 | } 88 | 89 | // Load the currently-selected phonon. 90 | final int active = pref.getInt("activePhonon", -1); 91 | mActivePos.setPos(-1 <= active && active < mSavedPhonons.size() ? 92 | active : -1); 93 | } 94 | 95 | public void addLockListener(LockListener l) { 96 | mLockListeners.add(l); 97 | } 98 | 99 | public void removeLockListener(LockListener l) { 100 | if (!mLockListeners.remove(l)) { 101 | throw new IllegalStateException(); 102 | } 103 | } 104 | 105 | private void notifyLockListeners(LockListener.LockEvent e) { 106 | for (LockListener l : mLockListeners) { 107 | l.onLockStateChange(e); 108 | } 109 | } 110 | 111 | public void sendToService() { 112 | Intent intent = new Intent(mContext, NoiseService.class); 113 | getPhonon().writeIntent(intent); 114 | intent.putExtra("volumeLimit", (float) getVolumeLimit() / MAX_VOLUME); 115 | intent.putExtra("ignoreAudioFocus", mIgnoreAudioFocus); 116 | ContextCompat.startForegroundService(mContext, intent); 117 | mDirty = false; 118 | } 119 | 120 | public boolean sendIfDirty() { 121 | if (mDirty || (mActivePos.getPos() == -1 && mScratchPhonon.isDirty())) { 122 | sendToService(); 123 | return true; 124 | } 125 | return false; 126 | } 127 | 128 | public void toggleLocked() { 129 | mLocked = !mLocked; 130 | if (!mLocked) { 131 | mLockBusy = false; 132 | } 133 | notifyLockListeners(LockListener.LockEvent.TOGGLE); 134 | } 135 | 136 | public boolean getLocked() { 137 | return mLocked; 138 | } 139 | 140 | public void setLockBusy(boolean busy) { 141 | if (!mLocked) throw new AssertionError("Expected mLocked"); 142 | if (mLockBusy != busy) { 143 | mLockBusy = busy; 144 | notifyLockListeners(LockListener.LockEvent.BUSY); 145 | } 146 | } 147 | 148 | public boolean getLockBusy() { 149 | return mLockBusy; 150 | } 151 | 152 | public Phonon getPhonon() { 153 | if (mActivePos.getPos() == -1) { 154 | return mScratchPhonon; 155 | } 156 | return mSavedPhonons.get(mActivePos.getPos()); 157 | } 158 | 159 | public PhononMutable getPhononMutable() { 160 | if (mActivePos.getPos() != -1) { 161 | mScratchPhonon = mSavedPhonons.get(mActivePos.getPos()).makeMutableCopy(); 162 | mActivePos.setPos(-1); 163 | } 164 | return mScratchPhonon; 165 | } 166 | 167 | // -1 or 0..n 168 | public void setActivePhonon(int index) { 169 | if (!(-1 <= index && index < mSavedPhonons.size())) { 170 | throw new ArrayIndexOutOfBoundsException(); 171 | } 172 | mActivePos.setPos(index); 173 | sendToService(); 174 | } 175 | 176 | // This interface is for receiving a callback when the state 177 | // of the Input Lock has changed. 178 | public interface LockListener { 179 | enum LockEvent {TOGGLE, BUSY} 180 | 181 | void onLockStateChange(LockEvent e); 182 | } 183 | 184 | public void setAutoPlay(boolean enabled, boolean fromUser) { 185 | mAutoPlay = enabled; 186 | if (fromUser) { 187 | // Demonstrate AutoPlay by acting like the Play/Stop button. 188 | if (enabled) { 189 | sendToService(); 190 | } else { 191 | NoiseService.stopNow(mContext, R.string.stop_reason_autoplay); 192 | } 193 | } 194 | } 195 | 196 | public boolean getAutoPlay() { 197 | return mAutoPlay; 198 | } 199 | 200 | public void setIgnoreAudioFocus(boolean enabled) { 201 | if (mIgnoreAudioFocus == enabled) { 202 | return; 203 | } 204 | mIgnoreAudioFocus = enabled; 205 | mDirty = true; 206 | } 207 | 208 | public boolean getIgnoreAudioFocus() { 209 | return mIgnoreAudioFocus; 210 | } 211 | 212 | public void setVolumeLimitEnabled(boolean enabled) { 213 | if (mVolumeLimitEnabled == enabled) { 214 | return; 215 | } 216 | mVolumeLimitEnabled = enabled; 217 | if (mVolumeLimit != MAX_VOLUME) { 218 | mDirty = true; 219 | } 220 | } 221 | 222 | public void setVolumeLimit(int limit) { 223 | if (limit < 0) { 224 | limit = 0; 225 | } else if (limit > MAX_VOLUME) { 226 | limit = MAX_VOLUME; 227 | } 228 | if (mVolumeLimit == limit) { 229 | return; 230 | } 231 | mVolumeLimit = limit; 232 | if (mVolumeLimitEnabled) { 233 | mDirty = true; 234 | } 235 | } 236 | 237 | public boolean getVolumeLimitEnabled() { 238 | return mVolumeLimitEnabled; 239 | } 240 | 241 | public int getVolumeLimit() { 242 | return mVolumeLimitEnabled ? mVolumeLimit : MAX_VOLUME; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /app/src/main/java/net/pmarks/chromadoze/XORShiftRandom.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2011 Paul Marks http://www.pmarks.net/ 2 | // 3 | // This file is part of Chroma Doze. 4 | // 5 | // Chroma Doze is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // Chroma Doze is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with Chroma Doze. If not, see . 17 | 18 | // XORShift is supposedly better and faster than java.util.Random. 19 | // This algorithm is from: 20 | // http://www.javamex.com/tutorials/random_numbers/xorshift.shtml 21 | 22 | package net.pmarks.chromadoze; 23 | 24 | class XORShiftRandom { 25 | private long mState = System.nanoTime(); 26 | 27 | public long nextLong() { 28 | long x = mState; 29 | x ^= (x << 21); 30 | x ^= (x >>> 35); 31 | x ^= (x << 4); 32 | mState = x; 33 | return x; 34 | } 35 | 36 | // Get a random number from [0, limit), for small values of limit. 37 | public int nextInt(int limit) { 38 | return ((int) nextLong() & 0x7FFFFFFF) % limit; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/org/jtransforms/dct/FloatDCT_1D.java: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * JTransforms 3 | * Copyright (c) 2007 onward, Piotr Wendykier 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | * 26 | * ***** END LICENSE BLOCK ***** */ 27 | package org.jtransforms.dct; 28 | 29 | import org.jtransforms.utils.CommonUtils; 30 | import static java.lang.Math.ceil; 31 | import static java.lang.Math.log; 32 | import static java.lang.Math.sqrt; 33 | 34 | // For Chroma Doze, all code unrelated to the IDCT operation has been deleted. 35 | 36 | /** 37 | * Computes 1D Discrete Cosine Transform (DCT) of single precision data. The 38 | * size of data can be an arbitrary number. This is a parallel implementation of 39 | * split-radix and mixed-radix algorithms optimized for SMP systems.
40 | *
41 | * Part of the code is derived from General Purpose FFT Package written by Takuya Ooura 42 | * (http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html) 43 | * 44 | * @author Piotr Wendykier (piotr.wendykier@gmail.com) 45 | */ 46 | public class FloatDCT_1D 47 | { 48 | 49 | private int n; 50 | 51 | private int[] ip; 52 | 53 | private float[] w; 54 | 55 | private int nw; 56 | 57 | private int nc; 58 | 59 | private boolean isPowerOfTwo = false; 60 | 61 | /** 62 | * Creates new instance of FloatDCT_1D. 63 | * 64 | * @param n size of data 65 | * 66 | */ 67 | public FloatDCT_1D(long n) 68 | { 69 | if (n < 1) { 70 | throw new IllegalArgumentException("n must be greater than 0"); 71 | } 72 | 73 | this.n = (int) n; 74 | if (true) { 75 | if (n > (1 << 28)) { 76 | throw new IllegalArgumentException("n must be smaller or equal to " + (1 << 28) + " when useLargeArrays argument is set to false"); 77 | } 78 | if (CommonUtils.isPowerOf2(n)) { 79 | this.isPowerOfTwo = true; 80 | this.ip = new int[(int) ceil(2 + (1 << (int) (log(n / 2 + 0.5) / log(2)) / 2))]; 81 | this.w = new float[this.n * 5 / 4]; 82 | nw = ip[0]; 83 | if (n > (nw << 2)) { 84 | nw = this.n >> 2; 85 | CommonUtils.makewt(nw, ip, w); 86 | } 87 | nc = ip[1]; 88 | if (n > nc) { 89 | nc = this.n; 90 | CommonUtils.makect(nc, w, nw, ip); 91 | } 92 | } else { 93 | throw new IllegalStateException(); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Computes 1D inverse DCT (DCT-III) leaving the result in a. 100 | * 101 | * @param a 102 | * data to transform 103 | * @param scale 104 | * if true then scaling is performed 105 | */ 106 | public void inverse(float[] a, boolean scale) 107 | { 108 | inverse(a, 0, scale); 109 | } 110 | 111 | /** 112 | * Computes 1D inverse DCT (DCT-III) leaving the result in a. 113 | * 114 | * @param a 115 | * data to transform 116 | * @param offa 117 | * index of the first element in array a 118 | * @param scale 119 | * if true then scaling is performed 120 | */ 121 | public void inverse(final float[] a, final int offa, boolean scale) 122 | { 123 | if (n == 1) 124 | return; 125 | if (isPowerOfTwo) { 126 | float xr; 127 | if (scale) { 128 | CommonUtils.scale(n, (float) sqrt(2.0 / n), a, offa, false); 129 | a[offa] = a[offa] / (float) sqrt(2.0); 130 | } 131 | CommonUtils.dctsub(n, a, offa, nc, w, nw); 132 | if (n > 4) { 133 | CommonUtils.cftfsub(n, a, offa, ip, nw, w); 134 | rftfsub(n, a, offa, nc, w, nw); 135 | } else if (n == 4) { 136 | CommonUtils.cftfsub(n, a, offa, ip, nw, w); 137 | } 138 | xr = a[offa] - a[offa + 1]; 139 | a[offa] += a[offa + 1]; 140 | for (int j = 2; j < n; j += 2) { 141 | a[offa + j - 1] = a[offa + j] - a[offa + j + 1]; 142 | a[offa + j] += a[offa + j + 1]; 143 | } 144 | a[offa + n - 1] = xr; 145 | } else { 146 | throw new IllegalStateException(); 147 | } 148 | } 149 | 150 | private static void rftfsub(int n, float[] a, int offa, int nc, float[] c, int startc) 151 | { 152 | int k, kk, ks, m; 153 | float wkr, wki, xr, xi, yr, yi; 154 | int idx1, idx2; 155 | m = n >> 1; 156 | ks = 2 * nc / m; 157 | kk = 0; 158 | for (int j = 2; j < m; j += 2) { 159 | k = n - j; 160 | kk += ks; 161 | wkr = 0.5f - c[startc + nc - kk]; 162 | wki = c[startc + kk]; 163 | idx1 = offa + j; 164 | idx2 = offa + k; 165 | xr = a[idx1] - a[idx2]; 166 | xi = a[idx1 + 1] + a[idx2 + 1]; 167 | yr = wkr * xr - wki * xi; 168 | yi = wkr * xi + wki * xr; 169 | a[idx1] -= yr; 170 | a[idx1 + 1] -= yi; 171 | a[idx2] += yr; 172 | a[idx2 + 1] -= yi; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi-v11/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi-v11/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi-v9/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi-v9/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/action_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/action_lock.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/action_unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/action_unlock.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/av_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/av_play.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/av_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/av_stop.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/grip_dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/grip_dots.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_save_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/ic_menu_save_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_save_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/ic_menu_save_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-hdpi/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi-v11/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-ldpi-v11/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi-v9/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-ldpi-v9/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/wave_amplitude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-ldpi/wave_amplitude.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/wave_period.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-ldpi/wave_period.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi-v11/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi-v11/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi-v9/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi-v9/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_stat_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/ic_stat_bars.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/toolbar_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-mdpi/toolbar_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/toolbar_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable-xhdpi/toolbar_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_default_holo_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_save.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/spectrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/drawable/spectrum.png -------------------------------------------------------------------------------- /app/src/main/res/layout-v11/notification_with_stop_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | 20 | 25 | 26 | 32 | 33 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | 27 | 28 | 34 | 35 | 40 | 41 | 46 | 47 | 48 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/memory_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/memory_list_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/memory_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/memory_list_item_common.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/memory_list_item_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/options_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 20 | 21 | 25 | 26 | 29 | 30 | 36 | 37 | 43 | 44 | 50 | 51 | 52 | 55 | 56 | 60 | 61 | 67 | 68 | 69 | 72 | 73 | 79 | 80 | 87 | 88 | 94 | 95 | 96 | 97 | 101 | 102 | 109 | 110 | 117 | 118 | 125 | 126 | 133 | 134 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /app/src/main/res/layout/spinner_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/chromadoze_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/chromadoze_icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/chromadoze_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-hdpi/chromadoze_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/chromadoze_icon_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-hdpi/chromadoze_icon_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/chromadoze_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-hdpi/chromadoze_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/chromadoze_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-hdpi/chromadoze_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/chromadoze_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-mdpi/chromadoze_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/chromadoze_icon_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-mdpi/chromadoze_icon_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/chromadoze_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-mdpi/chromadoze_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/chromadoze_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-mdpi/chromadoze_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/chromadoze_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-xhdpi/chromadoze_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/chromadoze_icon_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-xhdpi/chromadoze_icon_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/chromadoze_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-xhdpi/chromadoze_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/chromadoze_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/app/src/main/res/mipmap-xhdpi/chromadoze_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #000000 5 | #201E24 6 | #33b5e5 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dslv_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chroma Doze 4 | Chroma Doze is running 5 | About 6 | Options 7 | Generating IDCT blocks:  8 | Play/Stop 9 | Lock/Unlock Input 10 | Memory 11 | (unsaved) 12 | Stopped by auto-play option 13 | Lost audio focus 14 | Stopped from notification bar 15 | Stopped from toolbar 16 | Stopped by media button 17 | Exited while silent 18 | Stopped mysteriously 19 | Restarted by the OS 20 | Default 21 | This notification appears when ChromaDoze is generating noise 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | google() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:8.3.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | google() 16 | } 17 | tasks.withType(JavaCompile) { 18 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | android.enableJetifier=false 3 | android.nonTransitiveRClass=false 4 | android.nonFinalResIds=false 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /misc/bars.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/bars.xcf -------------------------------------------------------------------------------- /misc/chromadoze-feature-1024x500.xcf.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/chromadoze-feature-1024x500.xcf.bz2 -------------------------------------------------------------------------------- /misc/chromadoze-icon-48.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/chromadoze-icon-48.xcf -------------------------------------------------------------------------------- /misc/chromadoze-icon-512.xcf.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/chromadoze-icon-512.xcf.bz2 -------------------------------------------------------------------------------- /misc/chromadoze-icon-96.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/chromadoze-icon-96.xcf -------------------------------------------------------------------------------- /misc/chromadoze-icon-plain-48.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/chromadoze-icon-plain-48.xcf -------------------------------------------------------------------------------- /misc/importing-dslv.txt: -------------------------------------------------------------------------------- 1 | Here's how I merged DragSortListView into the Chroma Doze tree: 2 | 3 | - Download from here: 4 | https://github.com/bauerca/drag-sort-listview 5 | 6 | - Copy these paths: 7 | + library/res/values/dslv_attrs.xml 8 | + library/res/values/ids.xml 9 | + library/src/... 10 | 11 | - Add this line to to DragSortListView.java: 12 | import net.pmarks.chromadoze.R; 13 | -------------------------------------------------------------------------------- /misc/privacy_policy.txt: -------------------------------------------------------------------------------- 1 | Privacy Policy 2 | 3 | Chroma Doze does not handle personal information. 4 | -------------------------------------------------------------------------------- /misc/readme-android-studio.txt: -------------------------------------------------------------------------------- 1 | Importing Chroma Doze into Android Studio should hopefully be trivial: 2 | 3 | - Launch Android Studio. 4 | - Click "Import Project (Eclipse ADT, Gradle, etc.)" 5 | - Browse for the root of the repository. 6 | -------------------------------------------------------------------------------- /misc/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/screenshot1.png -------------------------------------------------------------------------------- /misc/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/screenshot2.png -------------------------------------------------------------------------------- /misc/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmarks-net/chromadoze/d97acd88c21de7d4136c4215245d02fa6631af72/misc/screenshot3.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' --------------------------------------------------------------------------------