@@ -143,7 +143,23 @@ def restart_launcher():
143143break
144144
145145class App :
146- def __init__ (self ,name ,publisher ,short_description ,long_description ,icon_url ,download_url ,fullname ,version ,category ,activities ,installed_path = None ):
146+ # ------------------------------------------------------------------ #
147+ # Regular constructor – use when you already have the data
148+ # ------------------------------------------------------------------ #
149+ def __init__ (
150+ self ,
151+ name = "Unknown" ,
152+ publisher = "Unknown" ,
153+ short_description = "" ,
154+ long_description = "" ,
155+ icon_url = "" ,
156+ download_url = "" ,
157+ fullname = "Unknown" ,
158+ version = "0.0.0" ,
159+ category = "" ,
160+ activities = None ,
161+ installed_path = None ,
162+ ):
147163self .name = name
148164self .publisher = publisher
149165self .short_description = short_description
@@ -153,79 +169,91 @@ def __init__(self, name, publisher, short_description, long_description, icon_ur
153169self .fullname = fullname
154170self .version = version
155171self .category = category
172+ self .activities = activities if activities is not None else []
173+ self .installed_path = installed_path
174+
175+ # Cached image fields (kept for compatibility)
156176self .image = None
157177self .image_dsc = None
158- self . activities = activities
159- self . installed_path = installed_path
178+
179+ # Find the main launcher activity once, at construction time
160180self .main_launcher_activity = self ._find_main_launcher_activity ()
161181
182+ # ------------------------------------------------------------------ #
183+ # Human-readable representation
184+ # ------------------------------------------------------------------ #
162185def __str__ (self ):
163- return (f"App(name='{ self .name } ', "
164- f"publisher='{ self .publisher } ', "
165- f"short_description='{ self .short_description } ', "
166- f"version='{ self .version } ', "
167- f"category='{ self .category } ', "
168- f"activities='{ self .activities } ', "
169- f"installed_path={ self .installed_path } )" )
170-
186+ return (
187+ f"App(name='{ self .name } ', "
188+ f"publisher='{ self .publisher } ', "
189+ f"short_description='{ self .short_description } ', "
190+ f"version='{ self .version } ', "
191+ f"category='{ self .category } ', "
192+ f"activities={ len (self .activities )} items, "
193+ f"installed_path={ self .installed_path } )"
194+ )
195+
196+ # ------------------------------------------------------------------ #
197+ # Private helper – locate the MAIN/LAUNCHER activity
198+ # ------------------------------------------------------------------ #
171199def _find_main_launcher_activity (self ):
172- result = None
173200for activity in self .activities :
174201if not activity .get ("entrypoint" )or not activity .get ("classname" ):
175- print (f "Warning: activity{ activity } has no entrypointand classname, skipping... " )
202+ print ("Warning: activitymissing entrypointor classname – skipping" )
176203continue
177- print ("checking activity's intent_filters..." )
178- for intent_filter in activity .get ("intent_filters" ):
179- print ("checking intent_filter..." )
180- if intent_filter .get ("action" )== "main" and intent_filter .get ("category" )== "launcher" :
181- print ("found main_launcher!" )
182- result = activity
183- break
184- return result
185204
205+ for intent_filter in activity .get ("intent_filters" , []):
206+ if (
207+ intent_filter .get ("action" )== "main"
208+ and intent_filter .get ("category" )== "launcher"
209+ ):
210+ print ("Found main launcher activity!" )
211+ return activity
212+ return None
213+
214+ # ------------------------------------------------------------------ #
215+ # Convenience check for launcher-type apps
216+ # ------------------------------------------------------------------ #
186217def is_valid_launcher (self ):
187- #print(f"checking is_valid_launcher for {app_obj}")
188- return self .category == "launcher" and self .main_launcher_activity
189-
218+ return self .category == "launcher" and self .main_launcher_activity is not None
219+
220+ # ------------------------------------------------------------------ #
221+ # Class-method constructor that builds an App from a manifest file
222+ # ------------------------------------------------------------------ #
223+ @classmethod
224+ def from_manifest (cls ,appdir ):
225+ """
226+ Parse <appdir>/META-INF/MANIFEST.JSON and return a fully-populated
227+ App instance. If the file cannot be read, a default App with
228+ placeholder values is returned.
229+ """
230+ print (f"parse_manifest({ appdir } )" )
231+ manifest_path = f"{ appdir } /META-INF/MANIFEST.JSON"
232+
233+ # Minimal default instance – guarantees every field has a fallback
234+ default = cls (installed_path = appdir )
190235
191- def parse_manifest (appdir ):
192- print (f"parse_manifest({ appdir } )" )
193- manifest_path = f"{ appdir } /META-INF/MANIFEST.JSON"
194- # Default values for App object
195- default_app = App (
196- name = "Unknown" ,
197- publisher = "Unknown" ,
198- short_description = "" ,
199- long_description = "" ,
200- icon_url = "" ,
201- download_url = "" ,
202- fullname = "Unknown" ,
203- version = "0.0.0" ,
204- category = "" ,
205- activities = [],
206- installed_path = appdir
207- )
208- try :
209- with open (manifest_path ,'r' )as f :
210- app_info = ujson .load (f )
211- #print(f"parsed app: {app_info}")
212- # Create App object with values from manifest, falling back to defaults
213- return App (
214- name = app_info .get ("name" ,default_app .name ),
215- publisher = app_info .get ("publisher" ,default_app .publisher ),
216- short_description = app_info .get ("short_description" ,default_app .short_description ),
217- long_description = app_info .get ("long_description" ,default_app .long_description ),
218- icon_url = app_info .get ("icon_url" ,default_app .icon_url ),
219- download_url = app_info .get ("download_url" ,default_app .download_url ),
220- fullname = app_info .get ("fullname" ,default_app .fullname ),
221- version = app_info .get ("version" ,default_app .version ),
222- category = app_info .get ("category" ,default_app .category ),
223- activities = app_info .get ("activities" ,default_app .activities ),
224- installed_path = appdir
225- )
226- except OSError :
227- print (f"parse_manifest: error loading manifest_path:{ manifest_path } " )
228- return default_app
236+ try :
237+ with open (manifest_path ,"r" )as f :
238+ data = ujson .load (f )
239+ except OSError as exc :
240+ print (f"parse_manifest: error loading{ manifest_path } –{ exc } " )
241+ return default
242+
243+ # Merge manifest data with defaults
244+ return cls (
245+ name = data .get ("name" ,default .name ),
246+ publisher = data .get ("publisher" ,default .publisher ),
247+ short_description = data .get ("short_description" ,default .short_description ),
248+ long_description = data .get ("long_description" ,default .long_description ),
249+ icon_url = data .get ("icon_url" ,default .icon_url ),
250+ download_url = data .get ("download_url" ,default .download_url ),
251+ fullname = data .get ("fullname" ,default .fullname ),
252+ version = data .get ("version" ,default .version ),
253+ category = data .get ("category" ,default .category ),
254+ activities = data .get ("activities" ,default .activities ),
255+ installed_path = appdir ,
256+ )
229257
230258
231259class Activity :