package Ġme . h uf man . android aut oid rive . music . spotify Ċ Ċ import Ġandroid . app . Notification Channel Ċ import Ġandroid . app . Notification Manager Ċ import Ġandroid . app . Pending Intent Ċ import Ġandroid . content . Context Ċ import Ġandroid . content . Intent Ċ import Ġandroid . os . Build Ċ import Ġandroid . os . Handler Ċ import Ġandroid . os . Looper Ċ import Ġandroid . util . Log Ċ import Ġandroidx . core . app . Notification Compat Ċ import Ġcom . adam rat z man . spotify .* Ċ import Ġcom . adam rat z man . spotify . endpoints . pub . Search Api Ċ import Ġcom . adam rat z man . spotify . models .* Ċ import Ġkotlinx . coroutines . run Blocking Ċ import Ġme . h uf man . android aut oid rive . AppSettings Ċ import Ġme . h uf man . android aut oid rive . Mutable AppSettings Ċ import Ġme . h uf man . android aut oid rive . R Ċ import Ġme . h uf man . android aut oid rive . music . Music App Discovery Ċ import Ġme . h uf man . android aut oid rive . music . Music App Info Ċ import Ġme . h uf man . android aut oid rive . music . Music Metadata Ċ import Ġme . h uf man . android aut oid rive . music . controllers . Spot ify App Controller Ċ import Ġme . h uf man . android aut oid rive . phone ui . Spot ify Authorization Activity Ċ import Ġnet . openid . app auth .* Ċ import Ġjava . util .* Ċ import Ġkotlin . collections . ArrayList Ċ Ċ /** Ċ Ġ* ĠHandles Ġthe Ġlogic Ġaround Ġthe ĠSpotify ĠWeb ĠAPI . ĠThis Ġclass Ġis Ġa Ġsingleton Ġto Ġprevent Ġissues Ġof Ġhaving Ċ Ġ* Ġmultiple Ġ[ Spot ify Client Api ] s . Ċ Ġ*/ Ċ class ĠSpotify WebApi Ġprivate Ġconstructor ( val Ġcontext : ĠContext , Ġval Ġapp Settings : ĠMutable AppSettings ) Ġ{ Ċ ĉ comp anion Ġobject Ġ{ Ċĉ ĉ const Ġval ĠTAG Ġ= Ġ" Spot ify WebApi " Ċĉ ĉ const Ġval ĠNOT IFICATION _ CHANNEL _ ID Ġ= Ġ" Spot ify Authorization " Ċĉ ĉ const Ġval ĠNOT IFICATION _ REQ _ ID Ġ= Ġ 5 6 Ċĉ ĉ const Ġval ĠLI K ED _ SON GS _ PLAY LIST _ NAME Ġ= Ġ" AA ID RI VE _ LIK ED _ SON GS " Ċĉ ĉ const Ġval ĠAR TI ST _ SON GS _ PLAY LIST _ NAME Ġ= Ġ" AA ID RI VE _ AR TI ST _ SON GS " ĊĊĉ ĉ private Ġvar Ġweb Api Instance : ĠSpotify WebApi ? Ġ= Ġnull Ċĉ ĉ private Ġvar Ġinstance Count Ġ= Ġ 0 ĊĊĉ ĉ /** Ċĉĉ Ġ* ĠRetrieves Ġthe Ġcurrent Ġ[ Spot ify WebApi ] Ġinstance Ġcreating Ġone Ġif Ġit Ġthere Ġare Ġno Ġinstances Ċĉĉ Ġ* Ġcurrently Ġrunning . Ċĉĉ Ġ*/ Ċĉ ĉ fun ĠgetInstance ( context : ĠContext , Ġapp Settings : ĠMutable AppSettings ): ĠSpotify WebApi Ġ{ Ċĉĉ ĉ if Ġ( web Api Instance Ġ== Ġnull ) Ġ{ Ċĉĉĉ ĉ web Api Instance Ġ= ĠSpotify WebApi ( context , Ġapp Settings ) Ċĉĉ ĉ } Ċĉĉ ĉ instance Count ++ Ċĉĉ ĉ return Ġweb Api Instance Ġas ĠSpotify WebApi Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ // Ġwill Ġbe Ġnull Ġif Ġthe Ġuser Ġis Ġnot Ġauthenticated Ċ ĉ private Ġvar Ġweb Api : ĠSpotify Client Api ? Ġ= Ġnull ĊĊ ĉ private Ġval Ġauth StateManager : ĠSpotify Auth StateManager Ċ ĉ private Ġval ĠclientId : ĠString Ċ ĉ private Ġvar Ġpending Queue Metadata Create : Ġ(() Ġ-> ĠUnit )? Ġ= Ġnull ĊĊ ĉ var Ġis Using Spot ify : ĠBoolean Ġ= Ġfalse ĊĊ ĉ init Ġ{ Ċĉ ĉ Log . d ( TAG , Ġ" Initializing Ġfor Ġthe Ġfirst Ġtime ") Ċĉ ĉ auth StateManager Ġ= ĠSpotify Auth StateManager . getInstance ( app Settings ) Ċĉ ĉ clientId Ġ= ĠSpotify App Controller . getClient Id ( context ) Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠReturns Ġthe Ġsongs Ġfrom Ġa Ġspecified Ġplaylist ĠURI . ĠThis Ġsupports Ġplay lists Ġthat Ġinclude Ġboth Ċĉ Ġ* Ġsongs Ġand Ġpod casts . ĠLocal Ġtracks Ġare Ġnot Ġsupported . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġget Playlist S ongs ( spotify App Controller : ĠSpotify App Controller , Ġplaylist Uri : ĠString ): ĠList < Spot ify Music Metadata >? Ġ= Ġexecute Api Call (" Failed Ġto Ġget Ġsongs Ġfrom Ġplaylist Ġ$ playlist Uri ") Ġ{ Ċĉ ĉ if Ġ( web Api Ġ== Ġnull ) Ġ{ Ċĉĉ ĉ return @ execute Api Call Ġempty List () Ċĉ ĉ } ĊĊĉ ĉ val Ġsongs : ĠArrayList < Spot ify Music Metadata > Ġ= ĠArrayList () Ċĉ ĉ var Ġpage d S ongs Ġ= Ġweb Api ?. play lists ?. get Playlist Tracks ( playlist Uri , Ġ 5 0 , Ġ 0 , Ġnull ) Ċĉ ĉ while ( page d S ongs Ġ!= Ġnull ) Ġ{ Ċĉĉ ĉ page d S ongs . items . forEach Ġ{ Ġplaylist Track Ġ-> Ċĉĉĉ ĉ val Ġtrack Ġ= Ġplaylist Track . track ?. as Track Ċĉĉĉ ĉ if Ġ( track Ġ!= Ġnull ) Ġ{ Ċĉĉĉĉ ĉ songs . add ( create Spot ify Music Metadata From Track ( track , Ġspot ify App Controller )) Ċĉĉĉ ĉ } ĊĊĉĉĉ ĉ val Ġepisode Track Ġ= Ġplaylist Track . track ?. as Pod cast Episode Track Ċĉĉĉ ĉ if Ġ( episode Track Ġ!= Ġnull ) Ġ{ Ċĉĉĉĉ ĉ songs . add ( create Spot ify Music Metadata From Pod cast Episode Track ( episode Track , Ġspot ify App Controller )) Ċĉĉĉ ĉ } Ċĉĉ ĉ } Ċĉĉ ĉ page d S ongs Ġ= Ġpage d S ongs . getNext () Ċĉ ĉ } ĊĊĉ ĉ return @ execute Api Call Ġsongs Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠCreates Ġa Ġprivate Ġplaylist Ġwith Ġthe Ġprovided Ġname Ġand Ġoptionally Ġprovided Ġdescription . ĠThe Ċĉ Ġ* Ġnewly Ġcreated Ġplaylist 's Ġ[ Playlist Uri ] Ġis Ġreturned . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġcreate Playlist ( playlist Name : ĠString , Ġplaylist Description : ĠString ? Ġ= Ġnull ): ĠPlaylist Uri ? Ġ= Ġexecute Api Call (" Failed Ġto Ġcreate Ġplaylist Ġ$ playlist Name ") Ġ{ Ċĉ ĉ web Api ?. play lists ?. create Client Playlist ( name Ġ= Ġplaylist Name , Ġdescription Ġ= Ġplaylist Description , Ġpublic Ġ= Ġfalse )?. uri Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠAdds Ġthe Ġprovided Ġlist Ġof Ġsongs Ġto Ġthe Ġspecified Ġplaylist . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġadd S ongsTo Playlist ( playlist Id : ĠString , Ġsongs : ĠList < Music Metadata >) Ġ= Ġexecute Api Call (" Failed Ġto Ġadd Ġsongs Ġto Ġplaylist Ġ$ playlist Id ") Ġ{ Ċĉ ĉ // Ġneed Ġto Ġmanually Ġchunk Ġthe Ġplaylist Ġinto Ġ 1 0 0 Ġsong Ġchunks Ġand Ġsynchronous ly Ġcall Ġthe Ġadd Play ables To Client Playlist (..) Ċĉ ĉ // Ġmethod Ġfor Ġeach Ġas Ġa Ġworkaround Ġto Ġthe Ġbug Ġwhere Ġ> 1 0 0 Ġsongs Ġhas Ġthe Ġpotential Ġof Ġadding Ġthe Ġsongs Ġout Ġof Ġorder Ċĉ ĉ songs . chunk ed ( 1 0 0 ). forEach Ġ{ Ġchunk Ġ-> Ċĉĉ ĉ val Ġplay ables Ġ= Ġchunk . map Ġ{ ĠPlay able Uri ( it . media Id !! ) Ġ}. to Typed Array () Ċĉĉ ĉ web Api ?. play lists ?. add Play ables To Client Playlist ( playlist Id , Ġ* play ables ) Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠRe places Ġthe Ġsongs Ġof Ġthe Ġspecified Ġplaylist Ġwith Ġthe Ġprovided Ġlist Ġof Ġsongs . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġreplace Playlist S ongs ( playlist Id : ĠString , Ġsongs : ĠList < Music Metadata >) Ġ= Ġexecute Api Call (" Failed Ġto Ġreplace Ġplaylist Ġ$ playlist Id Ġsongs ") Ġ{ Ċĉ ĉ // Ġcan Ġonly Ġadd Ġa Ġmaximum Ġof Ġ 1 0 0 Ġtracks Ġper Ġreplace Ġplaylist Ġplay ables Ġrequest Ċĉ ĉ if Ġ( songs . size Ġ<= Ġ 1 0 0 ) Ġ{ Ċĉĉ ĉ web Api ?. play lists ?. replace Client Playlist Play ables ( playlist Id , Ġ* songs . map Ġ{ ĠPlay able Uri ( it . media Id !! ) Ġ}. to Typed Array ()) Ċĉ ĉ } Ġelse Ġ{ Ċĉĉ ĉ web Api ?. play lists ?. replace Client Playlist Play ables ( playlist Id ) Ċĉĉ ĉ add S ongsTo Playlist ( playlist Id , Ġsongs ) Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠReturns Ġthe Ġ[ Spot ify Uri ] Ġassociated Ġto Ġthe Ġprovided Ġplaylist Ġname Ġor Ġnull Ġif Ġit Ġis Ġnot Ġfound . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġget Playlist Uri ( playlist Name : ĠString ): ĠSpotify Uri ? Ġ= Ġexecute Api Call (" Failed Ġto Ġfind Ġplaylist Ġwith Ġname Ġ$ playlist Name ") Ġ{ Ċĉ ĉ var Ġplay lists Ġ= Ġweb Api ?. play lists ?. getClient Play lists ( 5 0 , 0 ) Ċĉ ĉ while Ġ( play lists Ġ!= Ġnull ) Ġ{ Ċĉĉ ĉ val Ġmatching Playlist Ġ= Ġplay lists . items . find Ġ{ Ġit . name Ġ== Ġplaylist Name Ġ} Ċĉĉ ĉ if Ġ( matching Playlist Ġ!= Ġnull ) Ġ{ Ċĉĉĉ ĉ return @ execute Api Call Ġmatching Playlist . uri Ċĉĉ ĉ } Ċĉĉ ĉ play lists Ġ= Ġplay lists . getNext () Ċĉ ĉ } ĊĊĉ ĉ return @ execute Api Call Ġnull Ċ ĉ } ĊĊ ĉ suspend Ġfun Ġget Lik ed S ongs ( spotify App Controller : ĠSpotify App Controller ): ĠList < Spot ify Music Metadata >? Ġ= Ġexecute Api Call (" Failed Ġto Ġget Ġdata Ġfrom ĠL ik ed ĠS ongs Ġlibrary ") Ġ{ Ċĉ ĉ if Ġ( web Api Ġ== Ġnull ) Ġ{ Ċĉĉ ĉ pending Queue Metadata Create Ġ= Ġ{ Ċĉĉĉ ĉ Log . d ( Spot ify App Controller . TAG , Ġ" Retry ing ĠL ik ed ĠS ongs Ġqueue Ġmetadata Ġcreation ") Ċĉĉĉ ĉ spotify App Controller . create Lik ed S ongs Queue Metadata () Ċĉĉ ĉ } ĊĊĉĉ ĉ return @ execute Api Call Ġempty List () Ċĉ ĉ } ĊĊĉ ĉ val Ġlik ed S ongs Ġ= Ġweb Api ?. library ?. get Saved Tracks ( 5 0 )?. getAll Items NotNull () Ċĉ ĉ return @ execute Api Call Ġlik ed S ongs ?. map Ġ{ Ċĉĉ ĉ create Spot ify Music Metadata From Track ( it . track , Ġspot ify App Controller ) Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ suspend Ġfun Ġget Artist Top S ongs ( spotify App Controller : ĠSpotify App Controller , Ġartist Uri : ĠString ): ĠList < Spot ify Music Metadata >? Ġ= Ġexecute Api Call (" Failed Ġto Ġget Ġtop Ġtracks Ġfrom ĠArtist Uri Ġ$ artist Uri ") Ġ{ Ċĉ ĉ if Ġ( web Api Ġ== Ġnull ) Ġ{ Ċĉĉ ĉ pending Queue Metadata Create Ġ= Ġ{ Ċĉĉĉ ĉ Log . d ( Spot ify App Controller . TAG , Ġ" Retry ing ĠArtist ĠS ongs Ġqueue Ġmetadata Ġcreation ") Ċĉĉĉ ĉ spotify App Controller . create Artist Top S ongs Queue Metadata () Ċĉĉ ĉ } ĊĊĉĉ ĉ return @ execute Api Call Ġempty List () Ċĉ ĉ } ĊĊĉ ĉ val Ġtop S ongs Ġ= Ġweb Api ?. artists ?. get Artist Top Tracks ( artist Uri ) Ċĉ ĉ return @ execute Api Call Ġtop S ongs ?. map Ġ{ Ċĉĉ ĉ create Spot ify Music Metadata From Track ( it , Ġspot ify App Controller ) Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ suspend Ġfun Ġsearch For Query ( spotify App Controller : ĠSpotify App Controller , Ġquery : ĠString ): ĠList < Spot ify Music Metadata >? Ġ= Ġexecute Api Call (" Failed Ġto Ġget Ġsearch Ġresults Ġfor Ġquery Ġ$ query ") Ġ{ Ċĉ ĉ if Ġ( web Api Ġ== Ġnull ) Ġ{ Ċĉĉ ĉ return @ execute Api Call Ġempty List () Ċĉ ĉ } ĊĊĉ ĉ val Ġsearch Results Ġ= Ġweb Api ?. search ?. search ( query , Ġ* Search Api . Search Type . values (), Ġlimit = 8 , Ġmarket = null ) ĊĊĉ ĉ // Ġrun Ġthrough Ġeach Ġof Ġthe Ġsearch Ġresult Ġcategories Ġand Ġcompile Ġfull Ġlist Ċĉ ĉ val Ġsearch Result Music Metadata : ĠArrayList < Spot ify Music Metadata > Ġ= ĠArrayList () ĊĊĉ ĉ val Ġalbum Results Ġ= Ġsearch Results ?. albums Ċĉ ĉ if Ġ( album Results Ġ!= Ġnull Ġ&& Ġalbum Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( album Results . items . map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . images ) Ċĉĉĉ ĉ val Ġartists Ġ= Ġresult . artists . join ToString (", Ġ") Ġ{ Ġit . name Ġ} Ċĉĉĉ ĉ val Ġtype Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġartists , Ġresult . name , Ġresult . name , Ġtype , Ġplay able = true , Ġbrowse able = false ) Ċĉĉ ĉ }) Ċĉ ĉ } ĊĊĉ ĉ val Ġsong Results Ġ= Ġsearch Results ?. tracks Ċĉ ĉ if Ġ( song Results Ġ!= Ġnull Ġ&& Ġsong Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( song Results . items . map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . album . images ) Ċĉĉĉ ĉ val Ġartists Ġ= Ġresult . artists . join ToString (", Ġ") Ġ{ Ġit . name Ġ} Ċĉĉĉ ĉ val Ġalbum Media Id Ġ= Ġresult . album . uri . uri Ċĉĉĉ ĉ val Ġtype Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġartists , Ġalbum Media Id , Ġresult . name , Ġtype , Ġplay able = true , Ġbrowse able = false ) Ċĉĉ ĉ }) Ċĉ ĉ } ĊĊĉ ĉ val Ġartist Results Ġ= Ġsearch Results ?. artists Ċĉ ĉ if Ġ( artist Results Ġ!= Ġnull Ġ&& Ġartist Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( artist Results . items . map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . images , Ġ 3 2 0 ) Ċĉĉĉ ĉ val Ġtype Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġresult . name , Ġnull , Ġresult . name , Ġtype , Ġplay able = false , Ġbrowse able = true ) Ċĉĉ ĉ }) Ċĉ ĉ } ĊĊĉ ĉ val Ġplaylist Results Ġ= Ġsearch Results ?. play lists Ċĉ ĉ if Ġ( playlist Results Ġ!= Ġnull Ġ&& Ġplaylist Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( playlist Results . items . map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . images , Ġ 3 2 0 ) Ċĉĉĉ ĉ val Ġtype Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġresult . name , Ġnull , Ġresult . name , Ġtype , Ġplay able = false , Ġbrowse able = true ) Ċĉĉ ĉ }) Ċĉ ĉ } ĊĊĉ ĉ val Ġshow Results Ġ= Ġsearch Results ?. shows Ċĉ ĉ if Ġ( show Results Ġ!= Ġnull Ġ&& Ġshow Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( show Results . items . filter NotNull (). map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . images ) Ċĉĉĉ ĉ val Ġsubtitle Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġresult . publisher , Ġnull , Ġresult . name , Ġsubtitle , Ġplay able = false , Ġbrowse able = true ) Ċĉĉ ĉ }) Ċĉ ĉ } ĊĊĉ ĉ val Ġepisode Results Ġ= Ġsearch Results ?. episodes Ċĉ ĉ if Ġ( episode Results Ġ!= Ġnull Ġ&& Ġepisode Results . isNotEmpty ()) Ġ{ Ċĉĉ ĉ search Result Music Metadata . addAll ( episode Results . items . filter NotNull (). map Ġ{ Ġresult Ġ-> Ċĉĉĉ ĉ val Ġmedia Id Ġ= Ġresult . uri . uri Ċĉĉĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( result . images ) Ċĉĉĉ ĉ val Ġsubtitle Ġ= Ġresult . type . replace First Char ( Char :: uppercase ) ĊĊĉĉĉ ĉ Spot ify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġnull , Ġnull , Ġresult . name , Ġsubtitle , Ġplay able = true , Ġbrowse able = false ) Ċĉĉ ĉ }) Ċĉ ĉ } Ċĉ ĉ return @ execute Api Call Ġsearch Result Music Metadata Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠSets Ġthe Ġspecified Ġplaylist 's Ġcover Ġart Ġimage Ġto Ġthe Ġspecified Ġimage . ĠThe Ġsupplied Ġimage Ġdata Ċĉ Ġ* Ġmust Ġbe Ġa ĠJ PG ĠBase 6 4 Ġstring Ġformat . Ċĉ Ġ*/ Ċ ĉ suspend Ġfun Ġset Playlist Image ( playlist Id : ĠString , Ġcover Art ImageData : ĠString ) Ġ= Ġexecute Api Call (" Failed Ġto Ġupload Ġthe Ġcover Ġart Ġimage Ġto Ġplaylist Ġ$ playlist Id ") Ġ{ Ċĉ ĉ web Api ?. play lists ?. upload Client Playlist Cover ( playlist Id , Ġimage Data Ġ= Ġcover Art ImageData ) Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠInitializes Ġthe Ġ[ Spot ify Client Api ] Ġinstance Ġand Ġupdates Ġthe Ġ[ Auth State ] Ġwith Ġthe Ġtoken Ġused . Ċĉ Ġ*/ Ċ ĉ fun Ġinitialize WebApi ( is Prob ing : ĠBoolean Ġ= Ġfalse ) Ġ{ Ċĉ ĉ if ( web Api Ġ!= Ġnull ) Ġ{ Ċĉĉ ĉ return Ċĉ ĉ } Ċĉ ĉ web Api Ġ= Ġcreate Web ApiClient () Ċĉ ĉ if Ġ( web Api Ġ!= Ġnull ) Ġ{ Ċĉĉ ĉ auth StateManager . update Token Response With Token ( web Api !!. token , ĠclientId ) ĊĊĉĉ ĉ pending Queue Metadata Create ?. invoke () Ċĉĉ ĉ clear Pending Queue Metadata Create () ĊĊĉĉ ĉ if Ġ(! is Prob ing ) Ġ{ Ċĉĉĉ ĉ update Spot ify App Info As Search able () Ċĉĉ ĉ } Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠReturns Ġif Ġthe Ġcurrent Ġ[ Auth State ] Ġis Ġauthorized . Ċĉ Ġ*/ Ċ ĉ fun Ġis Authorized (): ĠBoolean Ġ{ Ċĉ ĉ return Ġauth StateManager . is Authorized () Ċ ĉ } ĊĊ ĉ fun Ġclear Pending Queue Metadata Create () Ġ{ Ċĉ ĉ pending Queue Metadata Create Ġ= Ġnull Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠExec utes Ġthe Ġ[ Spot ify Client Api ] Ġcall Ġwith Ġexception Ġhandling Ġfor Ġfailures . ĠIn Ġthe Ġcase Ġof Ġa Ċĉ Ġ* Ġfailure Ġthe Ġsupplied Ġerror Ġmessage Ġwill Ġbe Ġthe Ġprefix Ġof Ġthe Ġappropriate Ġlog Ġmessage . Ċĉ Ġ* Ċĉ Ġ* ĠNote : ĠThis Ġshould Ġwrap Ġ** E VERY ** Ġ[ Spot ify Client Api ] Ġcall . Ċĉ Ġ*/ Ċ ĉ private Ġsuspend Ġfun Ġ< R > Ġexecute Api Call ( errorMessage : ĠString , Ġblock : Ġsuspend Ġ() Ġ-> ĠR ): ĠR ? Ġ{ Ċĉ ĉ try Ġ{ Ċĉĉ ĉ return Ġblock () Ċĉ ĉ } Ġcatch Ġ( e : ĠSpotify Exception . Authentication Exception ) Ġ{ Ċĉĉ ĉ Log . e ( TAG , ĠerrorMessage Ġ+ Ġ" Ġdue Ġto Ġauthentication Ġerror Ġwith Ġthe Ġmessage : Ġ${ e . message }") Ċĉĉ ĉ de authorize WebApi Session ( e ) Ċĉ ĉ } Ġcatch Ġ( e : ĠException ) Ġ{ Ċĉĉ ĉ if Ġ( e . message ?. contains (" Status ĠCode Ġ 4 0 3 ") Ġ== Ġtrue ) Ġ{ Ċĉĉĉ ĉ Log . e ( TAG , ĠerrorMessage Ġ+ Ġ" Ġdue Ġto Ġun authorized Ġerror Ġwith Ġthe Ġmessage : Ġ${ e . message }") Ċĉĉĉ ĉ de authorize WebApi Session ( e ) Ċĉĉ ĉ } Ġelse Ġ{ Ċĉĉĉ ĉ Log . e ( TAG , ĠerrorMessage Ġ+ Ġ" Ġdue Ġto Ġexception Ġwith Ġthe Ġmessage : Ġ${ e . message }") Ċĉĉ ĉ } Ċĉ ĉ } Ċĉ ĉ return Ġnull Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠDe - author izes Ġthe Ġcurrent Ġrunning ĠWeb ĠApi Ġsession , Ġperforming Ġthe Ġfollowing Ġactions : Ċĉ Ġ* Ġ Ġ- ĠAdding Ġauthorization Ġexception Ġto Ġthe Ġ[ Spot ify Auth StateManager ] Ċĉ Ġ* Ġ Ġ- ĠCreating Ġan Ġun authorized Ġnotification Ċĉ Ġ* Ġ Ġ- Ġsetting Ġthe Ġ[ Spot ify Client Api ] Ġinstance Ġto Ġbe Ġnull Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġde authorize WebApi Session ( e : ĠException ) Ġ{ Ċĉ ĉ auth StateManager . add AccessToken Authorization Exception ( e ) Ċĉ ĉ create Not Authorized Notification () Ċĉ ĉ web Api Ġ= Ġnull Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠUpdates Ġthe ĠSpotify Ġ[ Music App Info ] Ġsearch able Ġflag Ġto Ġtrue Ġif Ġit Ġis Ġset Ġto Ġfalse . Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġupdate Spot ify App Info As Search able () Ġ{ Ċĉ ĉ val Ġmusic App Discovery Ġ= ĠMusic App Discovery ( context , ĠHandler ( Looper . get Main Looper ())) Ċĉ ĉ music App Discovery . load Installed Music Apps () Ċĉ ĉ val Ġspot ify App Info Ġ= Ġmusic App Discovery . all Apps . first OrNull Ġ{ Ġit . packageName Ġ== Ġ" com . spotify . music " Ġ} ĊĊĉ ĉ // Ġif Ġapp Ġdiscovery Ġsays Ġwe Ġaren 't Ġable Ġto Ġsearch , Ġdiscover Ġagain Ċĉ ĉ if Ġ( spotify App Info ?. search able Ġ== Ġfalse ) Ġ{ Ċĉĉ ĉ music App Discovery . probe App ( spotify App Info ) Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠCreates Ġthe ĠWeb ĠAPI Ġclient Ġusing Ġthe ĠPK CE Ġauthentication Ġflow . ĠIf Ġthe Ġ[ Spot ify Client Api ] Ġcreation Ċĉ Ġ* Ġand Ġvalidation Ġprocess Ġencounter s Ġa Ġauthentication Ġfailure , Ġa Ġnull Ġinstance Ġis Ġreturned Ġand Ġthe Ċĉ Ġ* Ġ[ Auth State ] Ġupdated Ġwith Ġthe Ġexception . Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġcreate Web ApiClient (): ĠSpotify Client Api ? Ġ{ Ċĉ ĉ val ĠaccessToken Ġ= Ġauth StateManager . get AccessToken () Ċĉ ĉ if Ġ( accessToken Ġ!= Ġnull ) Ġ{ Ċĉĉ ĉ return Ġtry Ġ{ Ċĉĉĉ ĉ val Ġexpiration In Ġ= Ġauth StateManager . get AccessToken Expiration In () Ċĉĉĉ ĉ val Ġrefresh Token Ġ= Ġauth StateManager . get RefreshToken () Ċĉĉĉ ĉ val Ġscopes Ġ= Ġauth StateManager . get Scope String () Ċĉĉĉ ĉ val Ġtoken Ġ= ĠToken ( accessToken , Ġ" Bearer ", Ġexpiration In . toInt (), Ġrefresh Token , Ġscopes ) Ċĉĉĉ ĉ val Ġapi Builder Ġ= ĠSpotify Client Api Builder Helper . create Api Builder With AccessToken ( clientId , Ġtoken ) Ċĉĉĉ ĉ api Builder . options . on Token Refresh Ġ= Ġ{ Ċĉĉĉĉ ĉ Log . d ( TAG , Ġ" Updating ĠAuth State Ġwith Ġref reshed Ġtoken ") Ċĉĉĉĉ ĉ auth StateManager . update Token Response With Token ( it . token , ĠclientId ) Ċĉĉĉ ĉ } Ċĉĉĉ ĉ run Blocking Ġ{ Ċĉĉĉĉ ĉ api Builder . build () Ċĉĉĉ ĉ } Ċĉĉ ĉ } Ġcatch Ġ( e : ĠSpotify Exception . Authentication Exception ) Ġ{ Ċĉĉĉ ĉ Log . e ( Spot ify App Controller . TAG , Ġ" Failed Ġto Ġcreate Ġthe Ġweb ĠAPI Ġwith Ġan Ġaccess Ġtoken . ĠAccess Ġtoken Ġis Ġinvalid ") Ċĉĉĉ ĉ auth StateManager . add AccessToken Authorization Exception ( e ) Ċĉĉĉ ĉ create Not Authorized Notification () Ċĉĉĉ ĉ null Ċĉĉ ĉ } Ġcatch Ġ( e : ĠException ) Ġ{ Ċĉĉĉ ĉ Log . e ( Spot ify App Controller . TAG , Ġ" Failed Ġto Ġcreate Ġthe Ġweb ĠAPI Ġdue Ġto Ġthe Ġerror : Ġ${ e . message }") Ċĉĉĉ ĉ null Ċĉĉ ĉ } Ċĉ ĉ } Ġelse Ġ{ Ċĉĉ ĉ val Ġauthorization Code Ġ= Ġauth StateManager . get Authorization Code () Ċĉĉ ĉ if Ġ( authorization Code Ġ== Ġnull ) Ġ{ Ċĉĉĉ ĉ Log . e ( Spot ify App Controller . TAG , Ġ" Failed Ġto Ġcreate Ġthe ĠWeb ĠAPI Ġwith Ġan Ġauthorization Ġcode . ĠAuthorization Ġcode Ġis Ġinvalid Ġor Ġnot Ġfound ") Ċĉĉĉ ĉ create Not Authorized Notification () Ċĉĉĉ ĉ return Ġnull Ċĉĉ ĉ } ĊĊĉĉ ĉ return Ġtry Ġ{ Ċĉĉĉ ĉ val Ġapi Builder Ġ= ĠSpotify Client Api Builder Helper . create Api Builder With Authorization Code ( clientId , Ġauthorization Code ) Ċĉĉĉ ĉ api Builder . options . on Token Refresh Ġ= Ġ{ Ċĉĉĉĉ ĉ Log . d ( TAG , Ġ" Updating ĠAuth State Ġwith Ġref reshed Ġtoken ") Ċĉĉĉĉ ĉ auth StateManager . update Token Response With Token ( it . token , ĠclientId ) Ċĉĉĉ ĉ } Ċĉĉĉ ĉ run Blocking Ġ{ Ċĉĉĉĉ ĉ api Builder . build () Ċĉĉĉ ĉ } Ċĉĉ ĉ } Ġcatch Ġ( e : ĠSpotify Exception . Authentication Exception ) Ġ{ Ċĉĉĉ ĉ auth StateManager . add Authorization Code Authorization Exception ( e ) Ċĉĉĉ ĉ Log . e ( Spot ify App Controller . TAG , Ġ" Failed Ġto Ġcreate Ġthe Ġweb ĠAPI Ġwith Ġan Ġauthorization Ġcode . ĠAuthorization Ġfailed Ġwith Ġthe Ġerror : Ġ${ e . message }") Ċĉĉĉ ĉ create Not Authorized Notification () Ċĉĉĉ ĉ null Ċĉĉ ĉ } Ġcatch Ġ( e : ĠException ) Ġ{ Ċĉĉĉ ĉ Log . e ( Spot ify App Controller . TAG , Ġ" Failed Ġto Ġcreate Ġthe Ġweb ĠAPI Ġdue Ġto Ġthe Ġerror : Ġ${ e . message }") Ċĉĉĉ ĉ null Ċĉĉ ĉ } Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠCreates Ġand Ġdisplays Ġa Ġnotification Ġst ating Ġthat Ġthe Ġweb ĠAPI Ġneeds Ġto Ġbe Ġauthorized Ġonly Ġif Ġthe Ċĉ Ġ* Ġuser Ġhas Ġpreviously Ġsuccessfully Ġauthorized . Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġcreate Not Authorized Notification () Ġ{ Ċĉ ĉ if Ġ( app Settings [ AppSettings . KEYS . SP OT IFY _ SHOW _ UN AUTHENT IC ATED _ NOTIFICATION ] Ġ== Ġ" false ") Ġ{ Ċĉĉ ĉ return Ċĉ ĉ } Ċĉ ĉ val Ġnotify Intent Ġ= ĠIntent ( context , ĠSpotify Authorization Activity :: class . java ) Ċĉ ĉ notify Intent . flags Ġ= ĠIntent . FLAG _ ACTIVITY _ CLEAR _ TASK Ġor ĠIntent . FLAG _ ACTIVITY _ NEW _ TASK Ċĉ ĉ val Ġnotification Ġ= ĠNotification Compat . Builder ( context , ĠNOT IFICATION _ CHANNEL _ ID ) Ċĉĉĉ ĉ . setContent Title ( context . getString ( R . string . notification _ title )) Ċĉĉĉ ĉ . setContent Text ( context . getString ( R . string . txt _ spotify _ auth _ notification )) Ċĉĉĉ ĉ . set Small Icon ( R . drawable . ic _ notify ) Ċĉĉĉ ĉ . setContent Intent ( Pending Intent . getActivity ( context , ĠNOT IFICATION _ REQ _ ID , Ġnotify Intent , ĠPending Intent . FLAG _ UPDATE _ CURRENT )) Ċĉĉĉ ĉ . build () Ċĉ ĉ val Ġnotification Manager Ġ= Ġcontext . getSystem Service ( Notification Manager :: class . java ) Ċĉ ĉ if Ġ( Build . VERSION . SDK _ INT Ġ>= ĠBuild . VERSION _ CODES . O ) Ġ{ Ċĉĉ ĉ val Ġchannel Ġ= ĠNotification Channel ( NOTIFICATION _ CHANNEL _ ID , Ċĉĉĉĉ ĉ context . getString ( R . string . notification _ channel _ spotify ), Ċĉĉĉĉ ĉ Notification Manager . IMPORT ANCE _ HIGH ) Ċĉĉ ĉ notification Manager . create Notification Channel ( channel ) Ċĉ ĉ } Ċĉ ĉ notification Manager . notify ( NOTIFICATION _ REQ _ ID , Ġnotification ) Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠConstructs Ġthe Ġcover Ġart Ġuri Ġfrom Ġthe Ġlist Ġof Ġ[ Spot ify Image ] s Ġthat Ġworks Ġwith Ġthe ĠSpotify ĠApp Ċĉ Ġ* ĠRemote ĠImages Api . ĠIf Ġthere Ġare Ġno Ġimages Ġthat Ġare Ġfound Ġthat Ġmatch Ġthe Ġdimensions Ġthen Ġnull Ġis Ċĉ Ġ* Ġreturned . Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġget Cover Art Uri ( images : ĠList < Spot ify Image >, Ġheight To Match : ĠInt Ġ= Ġ 3 0 0 ): ĠString ? Ġ{ Ċĉ ĉ val Ġcover Art Index Ġ= Ġimages . indexOf First Ġ{ Ġit . height Ġ== Ġheight To Match Ġ} Ċĉ ĉ return Ġif Ġ( cover Art Index Ġ!= Ġ- 1 ) Ġ{ Ċĉĉ ĉ val ĠimageUrl Ġ= Ġimages [ cover Art Index ]. url Ċĉĉ ĉ val Ġcover Art Code Ġ= ĠimageUrl . substring ( imageUrl . lastIndexOf ("/") + 1 ) Ċĉĉ ĉ " spotify : image :$ cover Art Code " Ċĉ ĉ } Ġelse Ġ{ Ċĉĉ ĉ null Ċĉ ĉ } Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠCreates Ġa Ġ[ Spot ify Music Metadata ] Ġfrom Ġa Ġprovided Ġ[ Track ]. Ċĉ Ġ*/ Ċ ĉ private Ġfun Ġcreate Spot ify Music Metadata From Track ( track : ĠTrack , Ġspot ify App Controller : ĠSpotify App Controller ): ĠSpotify Music Metadata Ġ{ Ċĉ ĉ val Ġmedia Id Ġ= Ġtrack . uri . uri Ċĉ ĉ val Ġartists Ġ= Ġtrack . artists . join ToString (", Ġ") Ġ{ Ġit . name Ġ} Ċĉ ĉ val Ġalbum Ġ= Ġtrack . album Ċĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( album . images ) Ċĉ ĉ return ĠSpotify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġartists , Ġalbum . name , Ġtrack . name ) Ċ ĉ } ĊĊ ĉ private Ġfun Ġcreate Spot ify Music Metadata From Pod cast Episode Track ( episode : ĠPod cast Episode Track , Ġspot ify App Controller : ĠSpotify App Controller ): ĠSpotify Music Metadata Ġ{ Ċĉ ĉ val Ġmedia Id Ġ= Ġepisode . uri . uri Ċĉ ĉ val Ġartists Ġ= Ġepisode . artists . join ToString (", Ġ") Ġ{ Ġit . name Ġ} Ċĉ ĉ val Ġalbum Ġ= Ġepisode . album Ċĉ ĉ val Ġcover Art Uri Ġ= Ġget Cover Art Uri ( album . images ) Ċĉ ĉ return ĠSpotify Music Metadata ( spotify App Controller , Ġmedia Id , Ġmedia Id . hashCode (). to Long (), Ġcover Art Uri , Ġartists , Ġalbum . name , Ġepisode . name ) Ċ ĉ } ĊĊ ĉ /** Ċĉ Ġ* ĠDis connect Ġprocess Ġfor Ġshut ting Ġdown Ġthe Ġ[ Spot ify Client Api ]. Ċĉ Ġ*/ Ċ ĉ fun Ġdisconnect () Ġ{ Ċĉ ĉ instance Count -- Ċĉ ĉ Log . d ( TAG , Ġ" Disconnect ing ĠSpotify WebApi ") Ċĉ ĉ if Ġ( instance Count Ġ== Ġ 0 ) Ġ{ Ċĉĉ ĉ Log . d ( TAG , Ġ" All Ġinstances Ġof ĠSpotify WebApi Ġdisconnected . ĠSh ut ting Ġdown ĠWeb ĠAPI ") Ċĉĉ ĉ web Api ?. shutdown () Ċĉĉ ĉ is Using Spot ify Ġ= Ġfalse Ċĉĉ ĉ clear Pending Queue Metadata Create () Ċĉ ĉ } Ċ ĉ } Ċ }